Кейс: как крупный ритейлер выявил доступ к чужим заказам и риски для корпоративной видеосвязи
Пилот AI-хакера Sentra: доступ к историческим заказам без клиентской авторизации и цепочка от записи файлов до входа в видеоконференции под чужим именем.

Пилот AI-хакера Sentra: доступ к историческим заказам без клиентской авторизации и цепочка от записи файлов до входа в видеоконференции под чужим именем.

Через цифровые сервисы ритейлера проходят заказы, оплаты и персональные данные покупателей. Одна ошибка в API может обернуться утечкой, мошенничеством от имени компании или сбоем в работе интернет-магазина.
Крупный российский ритейлер провёл пилот AI-хакера Sentra на внешнем периметре, чтобы проверить, какие из его активов доступны снаружи и чем реально может воспользоваться злоумышленник.
Количество активов и находок изменено для анонимизации. Технические сценарии и результаты проверок сохранены.
Периметр и границы проверки согласовали заранее. Первичная инвентаризация охватила 311 активов: домены, IP-адреса, веб-приложения, мобильные приложения, сертификаты и порты.
Проверка шла методом «чёрного ящика» — с позиции внешнего атакующего. По итогам пилота в реестре было 147 находок, включая 5 критических и 10 высокого уровня.
Для наиболее значимых сценариев проверили, какой доступ получает атакующий и к каким последствиям это может привести. Результаты на рабочей системе и в контрольной копии среды зафиксировали отдельно.
Названия маршрутов API, параметров и технических полей в примерах изменены. Последовательность проверок и границы подтверждённых результатов сохранены.
Проверить доступ анонимного device-токена к чужим заказам.
Прямой /example-api/shop/edge-b/device-session возвращает блок-страницу. Проверю нормализацию пути на соседнем маршруте: шлюз и backend могут обрабатывать его по-разному. После достижения API отдельно проверю авторизацию.
dast_replayОбезличенный фрагментGET /example-api/shop/edge-b/device-session?client_marker=<тестовый>
Контроль:
GET /example-api/shop/edge-a/..%2fedge-b/device-session?client_marker=<тот же>Прямой путь → блок-страница
Альтернативный → HTTP 200
{"device_session":{"value":"[скрыт]"}}Мобильный API: device-session, DEVICE_CONTEXT, archived-purchase и active-purchaseТокен устройства выдаётся до входа покупателя. В контракте есть два маршрута информации о заказе: archived-purchase и active-purchase. Наличие device-токена не устанавливает владельца заказа; эту связь нужно проверять отдельно на каждом маршруте.
computer_executeОбезличенный фрагментЛокальный разбор payload выданного токена
Токен и его подпись не публикуютсяsub: "DEVICE_CONTEXT"
Клиентский вход при выдаче не выполнялсяТокен получен без входа, субъект — DEVICE_CONTEXT. Это контекст устройства. Проверю archived-purchase: такой токен не должен получать заказ другого покупателя.
dast_replayОбезличенный фрагментGET /example-api/shop/edge-a/..%2fedge-b/archived-purchase
client_marker=<свой тестовый>
purchase_ref=<исторический заказ>
Authorization: Bearer [скрыт]HTTP 200
Имя, телефон, email, адрес доставки: [скрыты]
Состав покупки, сумма, статус оплатыdast_replayОбезличенный фрагментТот же анонимный device-контекст
Изменяется только purchase_ref в историческом диапазонеПолучены другие исторические карточки
Идентификаторы последовательные
Данные покупателей и номера заказов: [скрыты]Меняется purchase_ref — меняются покупатель и состав заказа, хотя device-контекст остаётся тем же. Это связывает раскрытие с отсутствием проверки владельца. Дальше отделю историческое хранилище от текущей системы.
В ответе полная карточка с контактами и оплатой. Проверю границу хранилища: старые записи и текущие заказы могут идти через разные API.
dast_replayОбезличенный фрагментСопоставление archived-purchase и active-purchase
Тот же анонимный device-контекстarchived-purchase → исторические записи
active-purchase → error: customer_login_required · требуется авторизация клиентаЗаказчик разрешил отмену и изменение оплаты на завершённых заказах прошлых лет. Воздействие на текущие заказы новой системы не подтверждено.
В контракте мобильного API есть void-purchase и payment-option. Проверю бизнес-результат на согласованном историческом заказе: HTTP 200 сам по себе недостаточен, нужно смотреть код и сообщение операции.
dast_replayОбезличенный фрагментPOST /example-api/shop/edge-a/..%2fedge-b/void-purchase
purchase_ref=<согласованный исторический заказ>
Authorization: Bearer [скрыт]HTTP 200
{"operation_state":"cancelled","purchase_ref":"[скрыто]"}dast_replayОбезличенный фрагментPOST /example-api/shop/edge-a/..%2fedge-b/payment-option
purchase_ref=<согласованный исторический заказ>
payment_mode=<проверяемый способ>HTTP 200
operation_state: payment_updated
payment_result.checkout_url: [скрыто]dast_observe_browserОбезличенный фрагментОткрытие полученной платёжной ссылки
Без выполнения оплатыHTTP 200
Действующая платёжная страницаЧтение и операции подтвердились в историческом контуре. В новую систему токен не прошёл. Разделю результаты в отчёте: этот сценарий не подтверждает управление текущими заказами.
Обход маршрута соединяется с отсутствием проверки владельца. Подтверждены раскрытие карточек и согласованные операции с историческими заказами.
Раскрытие данных покупателей и операции с чужими заказами
GET /example-api/shop/edge-b/device-session?client_marker=<тестовый>
Контроль:
GET /example-api/shop/edge-a/..%2fedge-b/device-session?client_marker=<тот же>НаблюдениеПрямой путь → блок-страница
Альтернативный → HTTP 200
{"device_session":{"value":"[скрыт]"}}GET /example-api/shop/edge-a/..%2fedge-b/archived-purchase
client_marker=<свой тестовый>
purchase_ref=<исторический заказ>
Authorization: Bearer [скрыт]НаблюдениеHTTP 200
Имя, телефон, email, адрес доставки: [скрыты]
Состав покупки, сумма, статус оплатыТот же анонимный device-контекст
Изменяется только purchase_ref в историческом диапазонеНаблюдениеПолучены другие исторические карточки
Идентификаторы последовательные
Данные покупателей и номера заказов: [скрыты]Сопоставление archived-purchase и active-purchase
Тот же анонимный device-контекстНаблюдениеarchived-purchase → исторические записи
active-purchase → error: customer_login_required · требуется авторизация клиентаPOST /example-api/shop/edge-a/..%2fedge-b/void-purchase
purchase_ref=<согласованный исторический заказ>
Authorization: Bearer [скрыт]НаблюдениеHTTP 200
{"operation_state":"cancelled","purchase_ref":"[скрыто]"}POST /example-api/shop/edge-a/..%2fedge-b/payment-option
purchase_ref=<согласованный исторический заказ>
payment_mode=<проверяемый способ>НаблюдениеHTTP 200
operation_state: payment_updated
payment_result.checkout_url: [скрыто]Открытие полученной платёжной ссылки
Без выполнения оплатыНаблюдениеHTTP 200
Действующая платёжная страницаПодтверждены исторические заказы и согласованные операции с ними. Текущая система требует клиентскую авторизацию.
Получена чужая карточка заказа, подтверждены согласованные отмена и изменение оплаты. Текущая система active-purchase требует авторизации.
Проверять клиентскую авторизацию и принадлежность заказа при чтении и каждой операции.
Применять одинаковую обработку пути и правила доступа на шлюзе и в приложении.
Защитный шлюз и приложение по-разному обрабатывали путь запроса. Через альтернативный маршрут удалось получить служебный токен устройства без входа в учётную запись покупателя. Сервис заказов принимал этот токен, но не проверял принадлежность запрашиваемого заказа.
Проверочные запросы вернули карточки чужих заказов: имя покупателя, адрес доставки, телефон, электронную почту, состав покупки, сумму и статус оплаты. Последовательная нумерация создавала возможность массового сбора этих данных.
С согласия заказчика на завершённых заказах прошлых лет проверили отмену и изменение способа оплаты. Сервис подтвердил обе операции и вернул действующую платёжную ссылку. Саму оплату не выполняли.
Такой доступ создавал риск утечки персональных данных и мошеннических обращений от имени магазина с деталями настоящего заказа. Цепочка затрагивала историческое хранилище. Новая система требовала клиентскую авторизацию; доступ к текущим заказам и операции с ними не подтвердились.
Проверить влияние записи файлов в сервисе видеоконференций.
У /example-api/meeting/profile-store нет проверки сессии. Поле пользователя участвует в пути записи. Проверю выход за каталог и формат полученного файла — произвольная запись ещё не доказывает исполнение.
dast_replayОбезличенный фрагментPOST /example-api/meeting/profile-store
Контрольный маркер, без авторизацииHTTP 200
Ответ: okdast_replayОбезличенный фрагментКонтроль пути вне каталога данных
Сопоставление отсутствующего пути и файла-маркераОтсутствующий путь → Errno 2: No such file
Файл + <контрольный дочерний сегмент> → Errno 20: Not a directoryprofile-store: сериализация файла, Python .pth/sitecustomize, условия исполненияОбработчик сериализует содержимое, поэтому запись по исполняемому пути не равна исполнению. Для Python .pth важен исполняемый import в начале строки; отдельный строковый литерал в sitecustomize не выполняет вложенный текст.
ENOTDIR показывает файл по целевому пути. Но обработчик сериализует содержимое: неподходящий формат останется строкой. Полную эскалацию продолжу в контрольной копии. В live-результате пока подтверждена запись.
В копии проверяются формат записи, триггер исполнения, права контейнера и принятие токена. Рабочая система и копия фиксируются отдельно.
computer_executeОбезличенный фрагментКонтрольная копия обработчика
Сопоставление входного значения и полученного файлаСериализованный формат воспроизведён
Кавычки влияют на интерпретацию содержимогоcomputer_executeОбезличенный фрагментКонтрольная копия: сопоставление формата с .pth и sitecustomize
Без проверки этой ветки на рабочей системе.pth: нужный исполняемый import не получается
sitecustomize: самостоятельный строковый литерал
Автоматическое исполнение этой веткой не подтверждаетсяPython-ветку отбрасываю: путь подходит, но формат записи не создаёт исполняемый код. Для оставшейся цепочки нужен отдельный триггер. Его проверю в копии и зафиксирую, при каком действии появляется результат.
computer_executeОбезличенный фрагментКонтрольная среда:
запись → согласованный триггер → проверка контрольного маркераКонтрольный маркер появился после триггера
Права: администратор контейнера
Самопроизвольное исполнение на рабочей системе не заявляетсяВ копии получено выполнение команд с правами администратора контейнера. Проверю, доступен ли из этого контекста ключ, которым сервис подписывает токены конференций.
computer_executeОбезличенный фрагментПроверка конфигурации и механизма подписи
Среда: контрольная копияКлюч подписи: [скрыт]
Контекст: администратор контейнераКорректная подпись ещё не означает вход. Проверю токен с выбранной личностью на компонентах конференций в той же копии. Ключ и токен не публикую.
computer_executeОбезличенный фрагментКонтрольная копия:
создание токена → проверка доступа к конференцииПодпись принята
Доступ от имени выбранного сотрудника подтверждёнНа рабочей системе подтверждена произвольная запись. Полная цепочка от команд до принятия поддельного токена воспроизведена в контрольной копии.
Вход в конференции под выбранной личностью
POST /example-api/meeting/profile-store
Контрольный маркер, без авторизацииНаблюдениеHTTP 200
Ответ: okКонтроль пути вне каталога данных
Сопоставление отсутствующего пути и файла-маркераНаблюдениеОтсутствующий путь → Errno 2: No such file
Файл + <контрольный дочерний сегмент> → Errno 20: Not a directoryКонтрольная копия обработчика
Сопоставление входного значения и полученного файлаНаблюдениеСериализованный формат воспроизведён
Кавычки влияют на интерпретацию содержимогоКонтрольная среда:
запись → согласованный триггер → проверка контрольного маркераНаблюдениеКонтрольный маркер появился после триггера
Права: администратор контейнера
Самопроизвольное исполнение на рабочей системе не заявляетсяПроверка конфигурации и механизма подписи
Среда: контрольная копияНаблюдениеКлюч подписи: [скрыт]
Контекст: администратор контейнераКонтрольная копия:
создание токена → проверка доступа к конференцииНаблюдениеПодпись принята
Доступ от имени выбранного сотрудника подтверждёнРабочая система: произвольная запись. Контрольная копия: команды, ключ подписи и принятие токена.
На рабочей системе подтверждена запись файла. Команды, доступ к ключу и принятие токена подтверждены только в контрольной копии.
Добавить авторизацию и проверять канонический путь записи внутри разрешённого каталога.
Ограничить права контейнера и доступ к ключу; заменить ключ при подтверждённом раскрытии.
В сервисе видеоконференций обнаружили обработчик без проверки авторизации. Через него можно было создавать и перезаписывать файлы за пределами каталога пользовательских данных. Произвольную запись файлов подтвердили на рабочей системе.
Дальнейшие шаги воспроизвели в контрольной копии среды: запись файла и отдельный триггер приводили к выполнению команд с правами администратора контейнера. Из этого контекста был доступен ключ подписи токенов конференций.
С полученным ключом создали токен от имени выбранного сотрудника. Компоненты видеосвязи в контрольной копии приняли его и предоставили доступ к конференции. Полная цепочка была подтверждена в копии; на рабочей системе проверку ограничили записью файлов.
Сценарий показывал риск входа на закрытые переговоры под чужим именем, раскрытия конфиденциальных обсуждений и нарушения работы корпоративной видеосвязи.
Ритейлер получил технический отчёт с оценкой критичности уязвимостей, доказательствами, шагами воспроизведения и разбором цепочек атак. Рекомендации разделили на первоочередные и дополнительные. Sentra автоматически определила приоритеты устранения с учётом подтверждённых сценариев эксплуатации и риска для бизнеса.
Исправления начались ещё во время пилота. Повторная проверка подтвердила закрытие уязвимости в конфигураторе. Непрерывный контроль также обнаружил регрессию: вернулась ранее устранённая проблема, позволявшая определить наличие клиента по номеру телефона.
Команда получила конкретные задачи по защите данных покупателей и корпоративных сервисов, а результаты повторных проверок помогли отслеживать исправления и замечать возврат уже устранённых проблем.