Кейс: как разработчик B2B-систем нашёл и закрыл критические уязвимости в CMS и CRM
За две недели AI-хакер проверил внешний периметр, вошёл в учётную запись администратора CMS и нашёл в открытом доступе ключ к CRM. Ретест подтвердил, что критические уязвимости закрыты.
Команда Sentra20 сентября 20265 мин чтения
Разработчик B2B-систем отвечает не только за свои данные. Успешная атака на его продукт задевает бизнес-процессы клиентов — а значит, и доверие к самому разработчику.
Поверхность атаки меняется с каждым релизом, новой версией API и подключённым сервисом. Поэтому защищённость нужно проверять непрерывно: замечать новые риски, выяснять, можно ли ими воспользоваться на деле, и убеждаться, что исправления работают.
Крупный разработчик B2B-систем решил посмотреть, как с этим справится AI-хакер Sentra, и запустил двухнедельный пилот: внешний периметр и веб-приложения.
Масштаб пилота
Количество активов и находок изменено для анонимизации. Технические сценарии и результаты проверок сохранены.
Периметр и границы проверки согласовали до старта. За две недели AI-хакер проанализировал 868 активов.
Обзор активов
868Всего активов
Проверка шла методом «чёрного ящика» — глазами внешнего атакующего. Всего AI-хакер зафиксировал 423 находки: 22 уязвимости разного уровня и 401 информационную находку.
Уязвимости
423Всего
Все 10 уязвимостей критического, высокого и среднего уровней эксперт Sentra дополнительно перепроверил вручную. Самые значимые сценарии довели до практического подтверждения: выяснили, какой доступ они дают и к чему это может привести.
Подтверждённые цепочки атак
Названия маршрутов API, параметров и технических полей в примерах изменены. Последовательность проверок и границы подтверждённых результатов сохранены.
1. Вход в учётную запись администратора Strapi
AI-хакер Sentra
Цель
Проверить административный доступ через публичный Strapi API.
Подтверждён
3 из 3 подтверждены
Content APIcms.example
Оракул фильтровeditor_link
Reset-кодrecovery_code
Администратор CMSВход подтверждён
01АнализВ публичном API есть /example-api/cms/records. Начну с baseline и фильтра по editor_link.contact_address: если скрытое поле участвует в запросе, количество записей должно зависеть от условия, даже когда само поле не возвращается.
В публичном API есть /example-api/cms/records. Начну с baseline и фильтра по editor_link.contact_address: если скрытое поле участвует в запросе, количество записей должно зависеть от условия, даже когда само поле не возвращается.
02Повтор HTTP-запросаContent API отвечает без сессии Завершено
dast_replayОбезличенный фрагмент
Вход
GET /example-api/cms/records
Без Cookie и Authorization
Результат
HTTP 200
match_count: N (N > 0)
03Проверка входных данныхФильтры дают разное число совпадений Завершено
dast_fuzzОбезличенный фрагмент
Вход
filters[editor_link][contact_address][$contains] = @
Контроль: то же поле, значение <контроль без совпадений>
Вложенный фильтр может участвовать в запросе к БД, хотя поле не возвращается в JSON. В LIKE символ _ — wildcard. Восстановленный email нужно проверить через $eq; принятие фильтра по токену само по себе не означает, что токен уже существует.
06АнализПолучился дифференциал совпадения есть → совпадений нет, неизвестное поле даёт 500. Сервер разбирает вложенное поле пользователя. Проверю, принимает ли он recovery_code: значение можно восстанавливать по ответам, даже если оно не выводится в JSON.
Получился дифференциал совпадения есть → совпадений нет, неизвестное поле даёт 500. Сервер разбирает вложенное поле пользователя. Проверю, принимает ли он recovery_code: значение можно восстанавливать по ответам, даже если оно не выводится в JSON.
07Повтор HTTP-запросаФильтр по recovery_code принимается Завершено
08Повтор HTTP-запросаДо восстановления reset-кода нет Завершено
dast_replayОбезличенный фрагмент
Вход
filters[editor_link][recovery_code][$notNull] = true
До запроса account/recovery
Результат
HTTP 200
match_count: 0
09АнализВ извлечении email есть ловушка: _ в LIKE совпадает с любым символом. Проверю восстановленный адрес через $eq, иначе можно получить похожее, но неверное значение.
В извлечении email есть ловушка: _ в LIKE совпадает с любым символом. Проверю восстановленный адрес через $eq, иначе можно получить похожее, но неверное значение.
10Проверка входных данныхEmail восстановлен по суффиксам Завершено
dast_fuzzОбезличенный фрагмент
Вход
Проверка editor_link.contact_address через $endsWith
Уточнение адреса после неоднозначного LIKE-поиска
Результат
Кандидат email: [скрыт]
Совпадение по суффиксу получено
11Повтор HTTP-запросаПолный email совпадает через $eq Завершено
HTTP 200
match_count: N (N > 0)
Точное совпадение подтверждено
12
Решение·Согласовать запрос восстановления
Запрос отправит письмо, смена пароля изменит учётную запись. Эти действия в кейсе согласованы с заказчиком.
13Повтор HTTP-запросаПосле восстановления появился reset-код Завершено
dast_replayОбезличенный фрагмент
Вход
POST /example-api/cms/account/recovery
{"contact_address":"[скрыто]"}
Повтор recovery_code[$notNull]
Результат
HTTP 204
До восстановления: match_count 0
После восстановления: match_count N > 0
14АнализКод появился и доступен через тот же оракул. Доведу проверку до согласованной смены пароля и входа. Дальнейшие уязвимости CMS — отдельная область, в этот результат они не входят.
Код появился и доступен через тот же оракул. Доведу проверку до согласованной смены пароля и входа. Дальнейшие уязвимости CMS — отдельная область, в этот результат они не входят.
15Проверка входных данныхКод восстановления получен через оракул Завершено
dast_fuzzОбезличенный фрагмент
Вход
Ограниченная проверка префиксов recovery_code
Полученное значение не публикуется
Результат
recovery_code: [скрыт]
Код передан для согласованной проверки
16
Находка·Доступ к учётной записи администратора
По результатам кейса выполнены согласованная смена пароля и административный вход. После исправления критических уязвимостей проведён ретест.
Цепочка атаки
Подтверждён
Захват администратора CMS через Content API
Посторонний получает административный доступ к CMS
Бизнес-риск
80/100
Вероятность реализации
95%
Маршрут атаки
Исходный доступ
Внешний посетитель без учётной записи. Смена пароля согласована с заказчиком.
01Фильтрация по скрытым полям администратораContent API Оракул фильтровПодтверждён
Предпосылка
Content API доступен без сессии и принимает вложенные фильтры.
Результат
Количество записей зависит от условия по editor_link; служебное поле recovery_code принимается.
Content API отвечает без сессии
Обезличенный фрагмент материалов исследования
Запрос / действие
GET /example-api/cms/records
Без Cookie и Authorization
Посторонний получает административный доступ к CMS
Административный вход подтверждён по согласованию. Ретест критических уязвимостей после исправлений прошёл.
Условие реализации
Подтверждён административный вход после согласованного восстановления пароля.
Меры защиты
Закрыть фильтрацию служебных полей
Запретить публичному Content API фильтры по административным отношениям и обновить CMS.
Отозвать раскрытые коды и проверить доступ
Инвалидировать коды восстановления и провести ретест административного доступа.
Цепочка начиналась с уязвимости CVE-2026-27886. Публичный API принимал фильтры по скрытым полям и по-разному отвечал на совпадение и его отсутствие. По этим различиям AI-хакер восстановил адрес администратора, а затем и код восстановления пароля.
С согласия заказчика пароль сменили и вошли в учётную запись. Захват администратора подтвердился на практике.
2. Административный webhook Bitrix24 в открытом доступе
AI-хакер Sentra
Цель
Проверить полномочия webhook из публичной клиентской конфигурации.
Подтверждён
3 из 3 подтверждены
Публичная страницаportal.example
Webhookclient_settings
АдминистраторПрофиль интеграции
CRM APIРазрешённые методы
01АнализВ app-bootstrap есть client_settings.crm_hook_url. URL похож на входящий webhook: /example-api/integration/<actor>/<key>/. Начну с actor-info без сессии: нужен действующий ключ и его пользователь.
В app-bootstrap есть client_settings.crm_hook_url. URL похож на входящий webhook: /example-api/integration/<actor>/<key>/. Начну с actor-info без сессии: нужен действующий ключ и его пользователь.
02Просмотр HTTP-трафикаWebhook находится в HTML публичной страницы Завершено
03Обращение к памятиКак различить пользователя ключа и его полномочия Контекст найден
Поисковый запрос
Входящий webhook Bitrix24: actor-info, permission-list, operation-list; проверка без изменения CRM
Источник · Материалы проверки · служебные методы интеграции
actor-info определяет пользователя webhook, permission-list — разрешённую область, operation-list — доступные методы. Эти ответы нужно сопоставить: признак is_admin не заменяет проверку области CRM и не доказывает выполнение каждой операции.
04АнализСекрет уже находится в пути /example-api/integration/<actor>/<key>/; браузерная сессия для этого способа доступа не нужна. В повторе уберу Cookie и Authorization, чтобы административный профиль нельзя было объяснить моей собственной сессией.
Секрет уже находится в пути /example-api/integration/<actor>/<key>/; браузерная сессия для этого способа доступа не нужна. В повторе уберу Cookie и Authorization, чтобы административный профиль нельзя было объяснить моей собственной сессией.
GET /example-api/integration/<actor>/[key]/actor-info
Без Cookie и Authorization
Результат
HTTP 200
{"actor_context":{"is_admin":true}}
06АнализWebhook живой, профиль вернул is_admin: true. Теперь проверю permission-list: признак администратора ещё не говорит, какие области разрешены этому ключу.
Webhook живой, профиль вернул is_admin: true. Теперь проверю permission-list: признак администратора ещё не говорит, какие области разрешены этому ключу.
GET /example-api/integration/<actor>/[key]/permission-list
Результат
HTTP 200
{"access_areas":["crm"]}
08Повтор HTTP-запросаoperation-list возвращает разрешённые методы Завершено
dast_replayОбезличенный фрагмент
Вход
GET /example-api/integration/<actor>/[key]/operation-list
Результат
HTTP 200
Получен список методов для этого webhook
09Выполнение командыВ списке есть операции с CRM и групповые запросы Завершено
computer_executeОбезличенный фрагмент
Вход
Локальный разбор сохранённого operation-list
Отбор операций с объектами CRM и групповых запросов; без вызова самих операций
Результат
Операции с обращениями, сделками, контактами,
организациями и активностями CRM
Групповые запросы
10АнализСекрет в HTML, actor-info подтверждает администратора, permission-list и operation-list — CRM. Этого достаточно для отчёта. Создавать или удалять клиентские объекты для проверки полномочий не нужно.
Секрет в HTML, actor-info подтверждает администратора, permission-list и operation-list — CRM. Этого достаточно для отчёта. Создавать или удалять клиентские объекты для проверки полномочий не нужно.
11
Решение·Остановиться на служебных запросах
Выполнены только actor-info, permission-list и operation-list. Создание, изменение и удаление CRM-данных не выполнялись.
12
Находка·Публичный административный webhook Bitrix24
Внешний посетитель получает действующий секрет интеграции с областью CRM. Ключ нужно отозвать и убрать из клиентской конфигурации.
Цепочка атаки
Подтверждён
Публичный административный webhook Bitrix24
Посторонний получает ключ интеграции с областью CRM
Бизнес-риск
80/100
Вероятность реализации
95%
Маршрут атаки
Исходный доступ
Внешний посетитель публичной страницы без сессии.
01Получение секрета из HTMLПубличная страница WebhookПодтверждён
Предпосылка
client_settings включён в app-bootstrap публичной страницы.
Результат
В конфигурации найден URL webhook с секретом интеграции.
Посторонний получает ключ интеграции с областью CRM
is_admin=true, access_areas=crm. Проверены служебные методы; объекты CRM не изменялись.
Условие реализации
Webhook действующий: actor-info возвращает is_admin=true, permission-list включает CRM. Изменение CRM-объектов не выполнялось.
Меры защиты
Отозвать опубликованный webhook
Заменить секрет интеграции и проверить обращения к нему.
Перенести секрет на сервер
Убрать webhook из HTML и клиентской конфигурации; ограничить полномочия интеграции.
В клиентской конфигурации, доступной любому посетителю, нашёлся действующий webhook Bitrix24. Несколько служебных запросов показали, что ключ рабочий, у него права администратора и доступ к CRM и её методам API.
С таким ключом объекты CRM можно читать и изменять — то есть данные могли как утечь, так и быть подменены. Сами данные CRM при проверке не трогали: хватило служебных запросов.
Чем закончился пилот
Заказчик получил подробный технический отчёт:
найденные уязвимости с оценкой критичности;
доказательства и шаги воспроизведения для подтверждённых сценариев;
разбор цепочек атак и их возможных последствий;
рекомендации по устранению, разделённые на первоочередные и дополнительные.
Sentra автоматически приоритизировала уязвимости с учётом подтверждённых сценариев эксплуатации и риска для бизнеса. Заказчик закрыл критические уязвимости, и ретест подтвердил, что исправления работают.
Теперь компания готовится встроить AI-хакер Sentra в непрерывный контроль защищённости.
Пилот
Проверьте работу AI-хакера на своей инфраструктуре — бесплатно