Подробные статьи о технологиях, определяющих будущее.

AI-сканеры безопасности будят наш технический долг

AI-сканеры безопасности превращают годами спавшие баги в критические уязвимости. Как разбирать поток найденных проблем и не утонуть в нём.

Клетка с птицей в угольной шахте, из стен позади которой расходятся светящиеся трещины
PHP2Shell стал канарейкой: баги-падения, которые мы годами игнорировали, оказались структурными трещинами.

Баг-репорт о PHP попал в трекер проекта ещё в 2024 году. В нём описывалось состояние out-of-memory, из-за которого процесс падает. Это неприятно, но в неформальной иерархии багов такое почти ничего не значит. Его оставили висеть. Потом, во время независимого аудита PHP, спонсированного OpenAI, исследователь из security-фирмы Calif, работая с AI-сканерами безопасности и собственной глубокой экспертизой, примерно две недели превращал эту досадную аварию в удалённое выполнение кода. Любой сервер с уязвимой сборкой PHP можно было заставить выполнить код злоумышленника или отдать чувствительные данные. В зону поражения попали NASA, Wikipedia и Белый дом, потому что WordPress и Drupal работают на PHP, а PHP работает на удивительно большой доле веба.

Патч вышел раньше, чем кто-либо злонамеренный до него добрался, так что история закончилась хорошо. Но исследователи, участвовавшие в этом, повторяют одно и то же: это начало, а не конец. Четыре десятилетия написанного людьми кода содержат океан багов, которые мы коллективно записали в разряд «падает, не будем чинить». AI-инструменты резко снизили стоимость выяснения того, какие из этих падений на самом деле заряженное оружие. Последние несколько недель я говорил с людьми, которые отвечают за безопасность реальных систем, о том, что это меняет на практике. Ниже не обзор, а набор ошибок, которые я уже видел у команд, и то, что сделали те, кто поступил правильно.

Ошибка первая: записывать баги-падения в разряд «неприятно, но безвредно»

Баг в PHP, о котором идёт речь, не был скрыт. Его зарепортили, подтвердили и понизили в приоритете, потому что он «всего лишь» вызывал ошибку out-of-memory. Это самая частая ошибка триажа в зрелых software-организациях, и разработка эксплойтов с помощью AI сделала её дорогой. Профессор Колумбийского университета Суман Джана, который занимается именно этим классом исследований, в интервью вокруг раскрытия уязвимости описал динамику прямо: в любой момент у каждого крупного проекта есть куча отчётов о падениях, которые никто всерьёз не воспринимает, потому что превращение падения в эксплойт требует длинных цепочек утомительных, узкоспециальных рассуждений. Люди избегают такой работы. Модели не скучают.

Вот механическая суть того, почему это различие важно. Ошибка с памятью, которая портит кучу неконтролируемыми данными, обычно просто убивает процесс: аллокатор замечает несогласованность и прерывает работу, или процесс разыменовывает мусор и падает с segfault. Но та же ошибка, получившая аккуратно подобранные по размеру и форме входные данные, становится примитивом записи:

void handle_request(const char *input, size_t len) {
char *buf = malloc(64);
memcpy(buf, input, len);   /* len never validated */
/* len = 65:            heap metadata trashed -> likely abort, filed as 'crash' */
/* len = attacker-tuned: adjacent object's function pointer overwritten */
process(buf);             /* calls through the corrupted pointer -> their code runs */
free(buf);
}

Граница между этими двумя исходами определяется не багом, а входными данными. Выяснить, какие биты перезаписать, в каком порядке и как привести выполнение к нужному адресу, можно кропотливо, но механически, а «кропотливо, но механически» как раз та середина, в которой языковые модели хороши. Исправление этой ошибки сначала концептуальное, а уже потом техническое: перестаньте использовать «оно только падает» как сигнал серьёзности для небезопасного с точки зрения памяти кода. Падение в C или C++ (или в C-ядре интерпретатора вроде PHP) это эксплойт, который ещё не разработали. Новый вопрос для триажа звучит не «падает ли оно?», а «может ли злоумышленник влиять на повреждение?». Если непроверенный ввод контролирует размер, смещение или содержимое некорректной записи, считайте уязвимость эксплуатируемой и приоритизируйте соответственно, даже если никто не продемонстрировал рабочий эксплойт. Особенно тогда, потому что продемонстрированные эксплойты теперь делаются неделями, а не годами.

Ошибка вторая: относиться к выводу сканера как к бэклогу

Второй режим отказа появляется в тот момент, когда команда действительно запускает современные AI-сканеры по парку, в котором есть код двадцатилетней давности. Инструмент выдаёт сотни находок, дашборд краснеет, и команда делает то, чему их учит бэклог: сортирует по оценке серьёзности и начинает сверху. Через полгода исправлено сорок штук, количество выросло ещё сильнее, а моральный дух в руинах. Инженеры, с которыми я говорил и которые справляются с этим хорошо, сказали примерно одно и то же: вывод AI-сканера не бэклог, а сырая разведка, и ей нужен проход аналитика по триажу, прежде чем будет заведён хоть один тикет.

Эффективный триаж в таком режиме задаёт вопросы о пути эксплуатации, а не о балле серьёзности:

  • Достижим ли уязвимый код из непроверенного ввода? Переполнение кучи в функции, которая обрабатывает только локальный конфиг-файл, совсем другое животное, чем то же переполнение в обработке запросов.
  • Доступен ли он из интернета сегодня? Не «должен ли быть», а есть ли на самом деле. Смотрите конфиги балансировщиков и DNS, а не архитектурную схему.
  • Контролирует ли ввод повреждение? Размер, смещение или содержимое некорректного обращения. Это и есть граница «падение против эксплойта» из предыдущего раздела.
  • Что даёт эксплойт? Падение в изолированном воркере, который перезапускается, отличается от падения в процессе, который держит учётные данные базы данных.
  • Есть ли эксплойт в дикой природе или публичный разбор? Это переносит что угодно в начало очереди независимо от оценки.

На последнем пункте стоит остановиться. Такие метрики, как CVSS, создавались для мира, где люди находили баги человеческими темпами, и систематически переоценивают теоретическую серьёзность относительно достижимости. Когда сканер способен генерировать теоретическую серьёзность в промышленных масштабах, вы тонете. Команды, которые справляются лучше всего, перевернули конвейер: сначала достижимость и экспозиция, потом серьёзность. Всё, что недостижимо по построению, документируется и закрывается, а не чинится, потому что выпущенный фикс несёт собственный риск регрессии, и этот бюджет риска нужен для находок, которые действительно важны. Именно здесь проверки понимания кода с AI окупаются на стороне защиты: тот же инструмент, который находит баг, часто может за минуты ответить на вопрос «покажи все пути вызовов от сетевой границы до этой функции». Раньше на такой вопрос старшему инженеру уходил день на каждую находку.

Работник сортирует светящиеся осколки на конвейере, отправляя к хранилищу только самые яркие
Триаж означает, что большинство находок уходит в боковую корзину, а достижимые, доступные из интернета единицы получают режим хранилища.

Ошибка третья: считать, что патчи могут двигаться со скоростью поиска

Вот асимметрия, которую стоило бы напечатать на плакате в каждой комнате команды безопасности. Найти путь эксплуатации в баге PHP занял одного опытного исследователя с помощью AI примерно две недели. Разработка патча заняла четырёх инженеров два месяца. Поиск это задача поиска: нужен один путь. Исправление похоже на доказательство: нужно закрыть каждый путь и добиться, чтобы миллиард установок, которые не атакуют, продолжали работать точно так же, как прежде. Работа защитника строго сложнее работы атакующего, и AI гораздо быстрее сокращает сторону атакующего, чем сторону защитника.

Команды, которые игнорируют эту асимметрию, делают одну из двух плохих ставок. Либо они спешат с патчами, чтобы успеть за давлением раскрытия, и выпускают регрессии: патч PHP затронул внутренности интерпретатора, которыми пользуются примерно три четверти сайтов, публикующих свой стек, так что радиус поражения плохого фикса общецивилизационный. Либо впадают в отчаяние и ничего не делают, ожидая идеального исправления. Практики, которые хорошо провели это окно, сделали менее героичное, но более эффективное: они разделили «смягчить сейчас» и «исправить правильно». Смягчения дешёвые, быстрые и обратимые: ограничить затронутый эндпоинт на обратном прокси, развернуть правило WAF, блокирующее форму входных данных эксплойта, вынести уязвимый путь кода за флаг, сузить круг тех, кто может достучаться до сервиса, пока настоящий патч созревает. Я уже писал о том, что решение между патчем и смягчением это настоящий компромисс, а не моральный провал, и этот инцидент тот же урок в планетарном масштабе.

Следствие, которое никто не хочет слышать: ваше среднее время до смягчения теперь главная метрика, потому что окно между «баг найден белыми хакерами» и «баг найден кем-то похуже» сокращается. В случае PHP окно оставалось открытым только потому, что исследователи были ответственными, а фонд двигался быстро. Библиотека сжатия Brotli рассказала менее лестную историю в тот же период: сопоставимый баг сидел в коде восемь лет, был исправлен в git, но не выпущен восемь месяцев и был независимо переоткрыт как минимум четырьмя исследователями. Когда обнаружение дешёвое и параллельное, «кто-нибудь найдёт в итоге» перестаёт быть утешительной мыслью и становится гарантией в расписании.

Ошибка четвёртая: доверять фиксу только потому, что его написала LLM

Оптимистичный контраргумент ко всему этому звучит так: ладно, AI находит баги быстрее, чем мы успеваем их чинить, но AI их и починит. Anthropic в октябре выпустила инструменты именно в этом духе: LLM генерирует патч, затем вторая модель генерирует проверяемое машиной доказательство того, что патч сохраняет задуманное поведение, а маленький, простой, заслуживающий доверия верификатор проверяет доказательство. Верификатор единственный компонент, который обязан быть корректным, и он может им быть, потому что почти ничего не делает. Джана искренне воодушевлён этим направлением, и я понимаю почему: это первый правдоподобный путь к тому, чтобы исправление стало почти таким же автоматизируемым, как поиск.

Но я уже наблюдал, как две команды внедряли рабочие процессы «патч безопасности, сгенерированный AI», и обе совершили одну и ту же ошибку: они считали патч модели решением, а не кандидатом. В одном случае патч закрыл описанное переполнение кучи, добавив проверку длины, и перенёс переполнение на второй путь выделения памяти в шести строках от первого, который модель не смоделировала. Фикс прошёл ревью, потому что выглядел очевидно правильным, и в этом суть сбоя: патчи LLM гладкие так, как человеческие не бывают, а гладкость подавляет скрупулёзность. Команды, которые извлекли пользу из сгенерированных патчей, добавили три проверочных шлюза в таком порядке:

  1. Регрессионный набор, включающий входные данные эксплойта. Proof-of-concept, который запустил находку, становится постоянным тестом. Если сканер дал падающий ввод, это ваш первый новый тест-кейс, бесплатно.
  2. Охота на варианты. До слияния кто-то (или что-то) отвечает: где ещё встречается этот же паттерн? Исправление одного экземпляра системного паттерна это путь к сиквелам CVE.
  3. Независимая проверка инварианта, а не диффа. Сформулируйте, что теперь должно быть истинно, например «нет пути от обработки запроса до memcpy с непроверенной длиной», и проверьте именно это тестами, статическим анализом или формальными инструментами. Ревью одного диффа просто перечитывает домашнее задание модели.

Формальная верификация будет полезна по мере созревания, но обратите внимание на разделение труда, которое делает её надёжной: творческим частям разрешено галлюцинировать, потому что окончательный арбитр механический и маленький. Это принцип, который стоит взять на вооружение независимо от инструментов. Всё, что производит LLM, будь то находка, патч или доказательство, должно завершаться чем-то, что не разделяет её режимов отказа. Если ваш шаг верификации это ещё один промпт, вы ничего не проверили.

Механический штамп и латунная лупа рядом со свёрнутыми чертежами, контраст между созданием и проверкой
Пусть модели генерируют, а финальную проверку делает что-то маленькое, простое и заслуживающее доверия.

Ошибка пятая: считать, что код аудировал кто-то другой

Под каждым техническим уроком здесь лежит экономический. PHP это инфраструктура масштаба коммунальной службы: на нём работают WordPress, Drupal и длинный хвост веба, включая космические агентства и сайты правительств. Его безопасность держится на PHP Foundation, небольшом финансируемом фонде, плюс на волонтёрах. Комфортный миф open source всегда гласил: «при достаточном количестве глаз все баги поверхностны». Но на деле тридцать лет все были уверены, что глаза принадлежат кому-то другому. Теперь AI поставляет эти глаза без разбора и атакующим, и защитникам, а значит, миф наконец становится правдой, только состязательно и с дедлайном.

Раскрытие PHP2Shell сработало благодаря необычной схеме: OpenAI спонсировала внешний аудит, солидная фирма провела работу, а Wiz помогла подтвердить и расширить находки. Это благотворительность, а не система, и она не масштабируется на тысячи критичных, но не финансируемых проектов в вашем дереве зависимостей. Если продакшн вашей организации зависит от open-source интерпретатора, фреймворка или библиотеки, а он зависит, решения, которые действительно важны, скучные: финансируйте фонды, спонсируйте аудиты, вкладывайте инженерные часы. Когда вы запускаете собственные сканеры и находите что-то в зависимости, сообщайте об этом upstream, а не патчите свой форк и не уходите дальше. Альтернатива это мир, который предсказывает Мэтью Грин из Johns Hopkins: ничего структурного не изменится, пока инцидент не окажется достаточно большим, чтобы потревожить обычных людей. Его совет на это время приземлённый и верный: когда появляется приглашение на обновление, ставьте его сейчас, а не на следующей неделе. Тот же урок мы усвоили трудным путём в последнем большом цикле раскрытий PHP, когда выдернуть вилку оказалось лучше умных фиксов для команд, которые не могли быстро обновиться.

Глаза пришли. Просто им всё равно, на какой они стороне.

Понедельничный плейбук для уязвимостей, найденных AI

Всё сказанное выше сжимается в короткий список того, что я действительно сделал бы в этом квартале, если бы руководил инженерной организацией с каким-либо легаси-кодом в продакшене. А если вы это читаете, у вас он есть:

  • Патчите агрессивно и измеряйте, сколько это занимает. Ваша задержка обновлений теперь мера безопасности с сокращающимся безопасным порогом. Знайте свою цифру до того, как она понадобится.
  • Заново проведите триаж существующего бэклога багов-падений, задавая вопрос об эксплуатируемости, а не оценку серьёзности: контролирует ли непроверенный ввод повреждение? Всё, что небезопасно для памяти и достижимо, поднимается выше.
  • Запустите AI-сканирование самостоятельно, начиная с самого старого кода. Предельная стоимость повторного обнаружения теперь близка к нулю, а значит, стоит считать, что всё, что можно найти в вашей поверхности атаки, будет найдено. Найдите первыми.
  • Разделяйте смягчение и исправление в процессе реагирования на инциденты. Заранее подготовьте плейбук: правила прокси, флаги фич, ограничение доступа. Покупка времени законная стратегия, а делать вид, что время не нужно, нет.
  • Пропускайте сгенерированные патчи через тесты на входные данные эксплойта и охоту на варианты, а не через то, насколько чистым выглядит дифф.
  • Вкладывайте деньги или часы в upstream-проекты, от которых зависите. Этот баг нашёл спонсируемый аудит. Это не совпадение: модель работает, и это должно перестать быть исключением.

Есть разумный спор о том, является ли эта эпоха «выплатой технического долга» или же она приговаривает нас к бесконечной беговой дорожке фиксов без новых функций. Вероятно, и то и другое: каждый нетривиальный патч несёт риск следующего тонкого бага, и ни одна система никогда не бывает завершённой. Но я предпочёл бы жить в мире, где долг взыскивают аудиторы и белые хакеры, чем в том, где мы продолжаем считать зыбучий песок фундаментом, потому что пока ничто нас не поглотило. PHP2Shell закончился патчем и вздохом облегчения. Следующий уже лежит в чьей-то очереди падений, помеченный как безвредный. Посмотрите свою.