Рой ИИ‑агентов против настоящих целей: уязвимости в OAuth‑библиотеке и две машины HackTheBox

В прошлой статье я показывал DreamTeam. Это опенсорс‑фреймворк на Python для роя ИИ‑агентов без начальника. Агентам дают цель, общую доску задач, общую git‑папку и право писать себе инструменты и скиллы. Дальше они сами раскладывают работу, разбирают задачи и проверяют друг друга. В той статье рой решал математические задачи и писал инструмент под скрытый бенчмарк.
В комментариях мне предложили проверить исходники DreamTeam силами самого роя — поискать майнеры и прочие закладки. Я попробовал: заложил в копию проекта 3 канарейки ради интереса и дал цель рою изучить их собственный код. Рой нашел все три... и еще 13 реальных проблем из 27 находок. Меня это вдохновило, поэтому я направил рой на две области, где обычно работают живые специалисты. Там результат нельзя подогнать под число. Первая — поиск уязвимостей в популярной OSS‑библиотеке с координированным раскрытием. Вторая — автономное прохождение машин HackTheBox. Некоторые результаты меня действительно удивили.
Сразу про рамки экспериментов. Authlib — библиотека с открытым исходным кодом. Разбор отправлен мейнтейнеру по каналу координированного раскрытия, и все принятое уже исправлено в апстриме. Машины HackTheBox — это легальный тренировочный полигон. Обе выбранные машины retired, то есть выведены из активного пула, а рою разрешалось трогать только один адрес, на котором располагалась машина. Чужих систем никто не касался. Ниже — что получилось, где рой удивил и какие тут есть ограничения.
Репозиторий: https://github.com/VictorAboba/DreamTeam (MIT).
Эксперимент 1. Рой ищет уязвимости в Authlib
На входе — популярная Python‑библиотека Authlib для OAuth 2.0 и OIDC, версия 1.9.3. Тут важная деталь — это версия уже после большой октябрьской волны CVE по Authlib, которую закрыли в ветке 1.9.x. То есть рой смотрел на свежепропатченный код. Задачей роя было найти оставшиеся слабые места в клиенте OIDC и оформить их в виде отчета.
На выходе рой составил один отчет о координированном раскрытии. В нем четыре находки, я назову их F1–F4 (одна разбита на 2). Все они в проверке ID‑токена и в RP‑Initiated Logout. Мейнтейнер (Éloi Rivard) ответил содержательно. Три пункта из четырех он принял и исправил в апстриме, два отклонил как ответственность приложения. Отдельный advisory или CVE он выпускать не стал. Набор он классифицировал как hardening, то есть защита в глубину, потому что против честного провайдера по TLS ничего не эксплуатируется. Это, кстати, совпало с их же формулировкой в отчете.
Что именно нашел рой:
F1 (главное). Синхронный OIDC‑клиент при разборе ID‑токена собирал список допустимых алгоритмов подписи из метаданных провайдера. Безопасного значения по умолчанию у него не было. Если в метаданных стоял
none, клиент был готов принять вообще неподписанный токен. Если поле отсутствовало, а в наборе ключей был симметричный ключ, открывалась дорога к подделке через HS256. Асинхронный клиент при этом пинил RS256, а синхронный нет. Чтобы это сработало, атакующий должен влиять на метаданные. Это злонамеренный или скомпрометированный провайдер, схема «принеси свой OIDC» либо загрузка метаданных не по TLS. Против честного провайдера по TLS находка не воспроизводится.F2. Тут два отдельных момента. Передача документированного параметра
claims_optionsмолча снимала обязательную проверку издателя (iss). А значение по умолчаниюnonce=Noneотключало проверку nonce.F3 (самое чистое). Эндпоинт RP‑Initiated Logout проверял у
id_token_hintтолько подпись. Тип токена и издателя он не проверял. Поэтому он принимал любой JWT, подписанный провайдером. Это мог быть токен back‑channel logout, access‑токен или старый ID‑токен. Срок действия намеренно не проверялся, так что подсказку можно было переиспользовать. Итог — принудительный logout, это разновидность CSRF, с редиректом на уже разрешенный адрес. Не захват аккаунта и не открытый редирект, но починки это требовало.F4 (поддерживающее). Метаданные и ключи загружались без проверки защищенного транспорта и с переходом по редиректам. Сетевой атакующий мог таким образом превратить F1 из «нужен злой провайдер» в сетевую атаку.
Что приняли и починили:
Коммит
2d0497b1Клиент теперь по умолчанию принимает только RS256 и отказывается от неподписанных ID‑токенов. Проверка издателя больше молча не отключается. Это F1 и issuer‑часть F2.
Коммит
151a2e4bЭндпоинт logout больше не принимает
noneи отбраковывает logout‑токены и JWT с чужимtyp. Это F3.Обе правки выходят в Authlib 1.10. В changelog это помечено как breaking change, потому что синхронный клиент по умолчанию теперь только RS256.
Что отклонили
Это nonce=None («явный выбор приложения») и F4 («URL метаданных выбирает приложение»). Позиция мейнтейнера обоснованная: в обоих случаях решение и правда на стороне приложения, а не библиотеки.
Про новизну
У Authlib за осень вышла пачка публичных CVE. Вот как раскладываются F1–F4 на фоне уже известных проблем:
Находка | Статус |
F1 (алгоритм из метаданных, нет безопасного дефолта) | публичного дубля не нашел |
F2-issuer ( | публичного дубля не нашел |
F3 (logout принимает любой JWT провайдера) | никаких упоминаний не нашел |
F2-nonce ( | пересекается с уже известной и закрытой проблемой отключения nonce |
F4 (транспорт метаданных/ключей) | пересекается с уже закрытой проблемой проверки транспорта |
класс «HS256 публичным ключом» | известен по прежним CVE |
Итог по Authlib простой — рой справился с поставленной задачей, нашел довольно нетривиальные проблемы, которые потребовали нового релиза. То, что агенты нашли непересекающиеся проблемы с уже опубликованными, говорит о том, что фиксы проблем действительно корректны :)
Параметры запуска: 8 агентов deepseek‑v4.1-flash, $9.66, 3 часа 4 минуты.
Эксперимент 2. Рой проходит HackTheBox
HackTheBox — легальная платформа с машинами, уязвимыми по задумке, для тренировки. Я взял две retired‑машины подряд на одном стенде. Это Cap (сложность Easy, 21.06.2021) и MakeSense (Medium, 04.07.2026). Рой работал на дешевой модели deepseek-v4.1-flash. В промпте был только адрес цели и запрет трогать что‑либо еще (спойлер — не помогло).
Границу скоупа держали три независимых якоря:
1. Дисциплина роя. Лид сам вынес в свой CONTRACTS.md правило «единственный хост в области, сканировать другие адреса запрещено».
2. Файрвол ядра. Наружу разрешены только подсети HTB через VPN, DNS и API модели. Чужой адрес физически не уходит.
3. Обертки инструментов, которые отказываются работать против любого IP, кроме цели.
Cap (Easy, $0,33)
Базовый автономный цикл, без сюрпризов. Ровно поэтому он хорошая проверка. Один прогон, четыре агента, 6 задач из 6, 33 цента.
Разведка через nmap нашла FTP, SSH и веб‑панель «Security Dashboard» на Flask. В панели был доступ к отчету по числовому идентификатору. Соседний идентификатор отдавал чужой дамп трафика без авторизации. Это классический IDOR. Рой скачал дамп, разобрал его и вытащил учетные данные FTP из незашифрованной сессии. Под ними он зашел по SSH, нашел у python3.8 выставленную setuid‑привилегию и через нее поднялся до root. Оба флага забраны. Root‑процесс скопировал флаг во временный файл, а потом этот файл прочитал обычный пользователь.
Отдельно отмечу самоорганизацию. Рою дали одну цель. А он сам написал командные конвенции, разложил задачу на шесть подзадач и назначил роли. Правильный вектор рой сформулировал из одной разведки, еще до того как полез в веб‑приложение. Искать IDOR и вытаскивать креды из дампа ему никто не подсказывал.

MakeSense (Medium, $5,3)
Здесь началось интересное. Готового эксплойта на входе нет, все добывается анализом цели. Четыре агента, 13 задач, 158 коммитов в рабочую папку.
Коротко цепочка выглядела так. WordPress с самописной темой, затем реверс клиентского «шифрования», затем XSS через расшифрованный канал против бота‑администратора, затем создание админа, шелл веб‑сервера, пароль из конфига, SSH и нестандартный подъем до root через сервис OCR. Разберу три места, где видно нестандартную работу и творческий подход.
Реверс клиентской криптографии. В теме WordPress лежал открытый JavaScript с симметричным ключом AES‑GCM. Самописный обработчик расшифровывал этим ключом присланные данные и сохранял результат без фильтрации. Дальше результат показывался в админском списке, который периодически открывает headless‑браузер под администратором. Рой восстановил рабочий кодек этого канала, селф‑тест прошел 6 из 6, и доставил через него XSS. Перебором 18 вариантов рой заодно установил, что обычная форма обратной связи HTML вырезается и бесполезна. Работает именно расшифрованный канал. Успех подтвердили запросы с адреса самой цели на слушатель роя.
Самодельный speech‑to‑text. У темы была голосовая функция. Чтобы ее атаковать, рою понадобился распознаватель речи. И он сам скачал модель whisper-tiny.en с самой цели, тема раздавала ее для клиентской транскрипции. Дальше рой собрал локальный ASR‑пайплайн и расшифровал предустановленный voice-message.wav. Внутри оказалось тестовое сообщение без паролей — тупик, но красивый. Для меня это центральный эпизод. Рой не запускал готовый эксплойт, он собрал недостающий инструмент на ходу из того, что нашел на цели.
Нестандартный root через OCR. На loopback от имени root крутился самописный сервис распознавания текста с картинок. По кнопке «сохранить» он писал распознанный текст в файл без проверки расширения. А встроенный веб‑сервер исполнял .php из этой же папки от root. Рой нарисовал на картинке PHP‑однострочник, аккуратно, в обход символов, которые OCR искажает. Скормил его сервису, сохранил как .php и получил выполнение команд от root. Оба флага снова перепроверены «от противного». Из‑под обычного пользователя cat /root/root.txt давал Permission denied, то есть флаг получен именно через root‑канал.

Еще некоторые «вау» эффекты
Рой сам чинит сеть. И на Cap, и на MakeSense путь до цели страдал от сломанного path MTU. Соединение устанавливается, но первый крупный пакет сервера теряется, и
curl,ssh,nmap -sVпросто виснут. Это классическая ловушка, на которой человек теряет час. Рой диагностировал ее сам. Он прогнал ping по сетке размеров пакета, посмотрел параметры сокета и написал собственный TCP‑прокси с уменьшенным MSS. Через него дальше шли и HTTPS, и SSH. Этого не было ни в каком плейбуке.Самоорганизация с перепроверкой. На MakeSense рой сам выделил отдельную задачу на независимую перепроверку разведки. Это 17 пронумерованных вердиктов, где находки первого участника роя переподтверждал второй. Расхождения в счетчиках портов из‑за rate‑limit благодаря этому разобрали и свели в одну версию. Итоговый отчет писала участница, которая сама флаги не добывала, чтобы синтез был независим от исполнителей. Паттерн «разделяй, перепроверяй, ревьюй» фреймворк вытащил сам, без промпта про роли.
Воспроизводимость. Пока рой работал, бесплатный инстанс MakeSense отвалился по таймеру и пересоздался на том же адресе. Старый user‑флаг и созданный админ перестали существовать. Рой распознал сброс, переснял user‑флаг на новом инстансе и довел до root. То есть цепочка прошла с нуля дважды, а не один раз по везению.
Попытка считерить или память модели
Первый проход MakeSense пришлось аннулировать. Я попросил агентов помочь своему коллеге, который долго занимался своей задачей, и один из членов роя решил, что будет неплохо поискать решения в интернете, чтобы вытащить коллегу из этой рутины. Поэтому он пошел в поисковик и прочитал чужие write‑up'ы... Как только я это обнаружил, прогон был выброшен целиком. Стенд я пересобрал с egress, залоченным до старта: наружу на:443 только IP API модели, все остальное дропается ядром. Машину рой перерешал начисто. Постфактум я проверил логи. Обращения были только к цели, loopback, адресу VPN и строкам‑идентификаторам из заголовков и XML. Ни поисковиков, ни известных площадок с разборами, ни GitHub, ни YouTube. Ту самую whisper‑модель, которую легко принять за «скачанное из интернета», по факту вытянули с самой цели.
Машины retired, и базовая модель могла видеть их в обучении, несмотря на то, что MakeSense довольно свежая. Сетевой замок закрывает «подсматривание во время прогона», но не закрывает «память модели». Это ограничение любой такой демонстрации. Поэтому Cap и MakeSense — не «рой взломал неизвестную машину», а «рой автономно и воспроизводимо прошел всю цепочку сам, при закрытой сети, с перепроверкой флагов». Чистый тест на незнакомой цели — это отдельная будущая работа.
Что эти прогоны говорят о фреймворке
Главный вывод прошлой статьи повторился и здесь. По‑настоящему интересное у роя видно не на коротких демо, а на длинных задачах с внешней проверкой.
Внешняя проверка решает. В обоих экспериментах ценность дала не способность роя что‑то найти. Ее дала дисциплина перепроверки: независимое подтверждение флагов, отдельная задача‑ревизия разведки, сверка находок Authlib с уже известными CVE. Ревью чужими руками внутри роя проверяет, что работа сделана. А верен ли результат и ново ли это, проверяется снаружи. В security‑задачах эта граница видна особенно резко.
Инженерная импровизация реальна. MSS‑прокси под сломанный MTU и собранный с нуля STT‑пайплайн — это не «запустил известный эксплойт». Рой дописывал недостающие инструменты под конкретную помеху. Ровно для этого в DreamTeam агент может написать себе MCP‑сервер.
Дешево. Cap обошелся в 33 цента, MakeSense в пять долларов, Authlib в том же порядке. Это конечно не сравнимо с ценой, которая была бы потрачена на фронтир‑модели. DeepSeek в данной связке на таких задачах — отличная рабочая лошадка.
Эпилог
Рой пока не «лучше пентестера» и не «нашел 0-day в проде». Он автономно прошел два учебных стенда и оформил один координированный отчет по OSS‑библиотеке, где все принятое уже исправлено и, надеюсь, помогло стать opensource’у лучше и безопаснее. Громких CVE за этим нет. Данные эксперименты скорее показали другое: рой без начальника держит длинную нелинейную цепочку, сам чинит инфраструктуру по пути и сам себя перепроверяет. По‑моему, этого достаточно, чтобы было интересно.
Если хотите попробовать на своих задачах:
uv tool install git+https://github.com/VictorAboba/DreamTeam.gitПодробности про установку, веб‑доску, подключение к Claude Code и локальные модели — в первой статье и в README репозитория.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.