🐛 Чему учат разработчиков Node2Shell и PHP2Shell?
В сети сейчас на хайпе аж четыре уязвимости: три в PHP, одна в декодере Brotli, встроенном в том числе в Node.js. Хотя классы у них разные, но причиной в каждой явился стык вполне корректных по отдельности решений.
Двусмысленный результат
CVE-2026-111497 aka Node2Shell, запись за границу буфера в декодере Brotli, во всех выпусках с 1.0.3 по 1.2.0:
static BROTLI_INLINE BROTLI_BOOL DecodeBlockTypeAndLength(int safe, ...) {
...
if (max_block_type <= 1) {
return BROTLI_FALSE; /* поток битый */
}
if (!safe) { /* быстрый путь — без проверок */
block_type = ReadSymbol(type_tree, br);
s->block_length[tree_type] = ReadBlockLength(len_tree, br);
} else {
...
if (!SafeReadSymbol(type_tree, br, &block_type)) return BROTLI_FALSE; /* мало данных */
if (!SafeReadBlockLength(s, &s->block_length[tree_type], len_tree, br)) {
...
return BROTLI_FALSE; /* мало данных */
}
}
...
return BROTLI_TRUE;
}
FALSE возвращается по трём причинам: переключение блока невозможно, не прочитался символ, не прочиталась длина блока. Смыслов, неразличимых на вызывающей стороне, два: «поток битый» и «дай ещё байтов».
Главный цикл декодирования ProcessCommandsInternal() работает в двух режимах; их выбирает флаг safe. При safe=0 (какая ирония) декодер читает биты напрямую, без проверок. Цикл вызывает DecodeBlockTypeAndLength через пару «обёртка + макрос», в которой и теряется возвращаемое значение:
static void BROTLI_NOINLINE DecodeLiteralBlockSwitch(BrotliDecoderState* s) {
DecodeLiteralBlockSwitchInternal(0, s); /* 0 — safe, возврата нет */
}
#define BROTLI_SAFE(METHOD) \
{ if (safe) { \
if (!Safe##METHOD) { \
result = BROTLI_DECODER_NEEDS_MORE_INPUT; \
goto saveStateAndReturn; \
} \
} else { METHOD; /* значение выброшено */ } \
}
В этом случае макрос исполняет DecodeLiteralBlockSwitch(s); как оператор, а не выражение. FALSE в потоковом режиме — штатное «дайте ещё байтов». Но ветка max_block_type <= 1 говорит про битый поток. В этом случае поток оказывается в рассинхронизированном читателе: декодированный оттуда meta_block_remaining_len уводит запись за буфер.
В патче возврат стал кодом ошибки, обёртки возвращают статус, вызовы переписаны на макрос, прерывающий цикл на любом статусе, кроме SUCCESS.
Функция, отказывающая по нескольким причинам, должна возвращать не bool, а перечисление, Result<T, E> или типизированное исключение. А void-обёрток над такими функциями вообще быть не должно.
Аварийный выход, который расходится с инициализацией
CVE-2026-109618 aka PHP2Shell (1/2), обработка нехватки памяти в PHP:
static ZEND_COLD ZEND_NORETURN void zend_mm_safe_error(zend_mm_heap *heap, ...) {
...
zend_bailout(); /* longjmp(EG(bailout), 1) */
exit(1);
}
У механизма два участника. Первый — zend_bailout(): longjmp(EG(bailout), 1) переходит ко внешнему zend_try, минуя промежуточные кадры. Второй — жизненный цикл запроса: воркер выполняет пару php_request_startup() → приложение → php_request_shutdown(). Startup делает черновую работу до выполнения приложения, в том числе разбирает multipart-тело, причём раньше, чем инициализируется состояние, по которому shutdown понимает, что именно надо освобождать. Если аллокация падает в середине разбора, то longjmp уводит управление посреди startup. Ни один кадр не завершается: парсер остаётся среди наполовину построенных структур, а инициализация для очистки не выполняется вовсе.
mod_php не различает «startup прошёл» и «startup упал»: php_request_shutdown(NULL) выполняется всегда, даже когда startup вернул FAILURE. Очистка идёт по памяти, которую startup не инициализировал: в реальном движке одно из значений выбирает указатель на функцию, давая RCE ещё до выполнения первой строки приложения.
В патче longjmp убрали. Нехватка памяти обрабатывается в безопасной точке виртуальной машины (zend_handle_delayed_oom()), а на втором, более высоком пороге процесс завершается через _exit(137).
longjmp, abort, «голый» throw из середины конструктора — выходы, ломающие пару init/deinit, нарушают инварианты, на основе которых могут быть приняты допущения в других частях приложения. Надёжнее, когда аварийный путь представляет собой обычный возврат с ошибкой, а обрабатываемый объект по умолчанию пуст.
Долгоживущий указатель на короткоживущую память
CVE-2026-109619 aka PHP2Shell (2/2), use-after-free через глобальное состояние воркера. В глобалах воркера живут указатели, временно смотрящие в память текущего запроса; сбрасывает их php_rshutdown_session_globals() при завершении запроса:
static inline void php_rinit_session_globals(void)
{
/* TODO: These could be moved to MINIT and removed. These should be
initialized by php_rshutdown_session_globals() always when
execution is finished. */
...
}
Текст TODO — вся суть уязвимости одной фразой: глобалы корректны, только если выполнение завершилось без ошибки, которую атакующий вполне может спровоцировать. Следующий запрос на том же воркере с полем PHP_SESSION_UPLOAD_PROGRESS заходит в обработчик прогресса до обычной инициализации и разыменовывает устаревший указатель.
Исправление инициализирует глобалы раньше, чем до них доберётся потребитель. Заодно объекты, декодируемые во время загрузки, остаются __PHP_Incomplete_Class — без автозагрузки, __wakeup() и __unserialize().
Не храните в долгоживущем объекте ссылку на данные, которые живут меньше него. Если без этого обойтись нельзя — сбрасывайте состояние в начале нового цикла, в одном месте.
Настройка меняется на ходу
CVE-2026-115560, Object Injection в интерпретаторе PHP. Конфигурация задаёт session.serialize_handler = php_binary (подойдёт и php_serialize), в коде выполняется предусловие:
// session.serialize_handler = php_binary
ini_set('session.serialize_handler', 'php'); // переключение в рантайме
session_start(); // читает файл декодером `php`
session_start() прочтёт не в точности тот файл, в который ранее была сериализована сессия: до выполнения этой строки хук прогресса загрузки при её наличии успевает записать в файл сессии служебную запись — в формате php_binary, как изначально было задано в конфигурации. Читает же файл уже декодер php, где ключ записи отделяется от значения символом |, что может контролироваться атакующим. Фрагмент после | декодер принимает за сериализованное значение и отдаёт в php_var_unserialize(), что и даёт в итоге Object Injection.
При session.upload_progress.cleanup=On запись прогресса стирается по завершении загрузки, атакующему нужно успеть в окно потоковой передачи; при cleanup=Off атака воспроизводится детерминированно. Патча нет, вендор рекомендует выключить до следующего релиза session.upload_progress.enabled.
Вариативный формат данных стоит держать в самих данных. Конфигурацию процесса в рантайме лучше не трогать: ini_set, setenv, правка синглтона «на один запрос» и т.п. создают неявную связь между компонентами.
Выводы
Используйте Go Разные причины отказа — разные типы, к bool их не сводите.
Используйте Go Обрабатывайте возврат функции, которая может не справиться.
Используйте Go Аварийный выход не должен обходить парную инициализацию; возврат ошибки лучше нелокального перехода.
Используйте Rust Долгоживущее состояние не ссылается на короткоживущую память; сброс — в начале цикла.
Используйте Rust Формат — в самих данных; глобальную конфигурацию в рантайме не меняйте.
Тесты на отказы пишутся вместе с кодом: OOM, таймаут, исключение в середине. Покрытие тестами только «счастливого пути» — антипаттерн.
Причем тут Go и Rust — традиционно останется домашним заданием на подумать 🤓
Прошу любить и жаловать. Забавно, что работать над ним я начал примерно одновременно с code-review, но довести до аналогичного качества его удалось лишь сейчас.
Чем он лучше 100500 других скиллов на эту тему? Как минимум — тем, что его разрабатывал и отлаживал столько времени тот, кто свыше 15 лет занимается white-box-анализом кода и свыше 10 лет пилит инструменты SAST. Нескромно, зато факт 🙈
Менее объективные отличия:
• Не полагается полностью на знания модели. Методика ревью и чек-листы покрывают OWASP для веба и агентов, бизнес-логику и уязвимости параллелизма.
• Доказывает каждую уязвимость через реальный сценарий атаки, благодаря чему меньше шумит, маппит находки на CWE и достаточно точно оценивает риск по CVSS v4.0. Если же нужно ревью на неэксплуатируемые недостатки безопасности и подозрения на них, то это неплохо делает и упомянутый выше code-review.
• Принимает во внимание не только код. Опирается на SECURITY.md, REVIEW.md, спеки и документацию, при их наличии. Это позволяет проводить ревью в соответствии с моделью угроз, архитектурой и инвариантами бизнес-логики.
• Знает о сторонних инструментах анализа. Использует экосистемные и сторонние линтеры и анализаторы в качестве дополнительного источника находок. Например, если при анализе Python-проекта скилл увидит в системе условный Bandit, то прогонит скан и им, протриажит и присовокупит подтвержденные результаты к общему отчету.
Затраты по времени и токенам на проведение ревью и качество его результатов значительно улучшает использование скилла в связке с MCP для работы с формальной моделью кода, типа codebase-memory-mcp.
😎 Скаффолдинг gen-AI SDD-проекта с нуля
При работе с уже существующим проектом (brownfield), или после выделенного этапа исследований, на руках обычно уже есть хоть какая-то кодовая база и набор документации, на основе которых можно сгенерить базовый набор спек и, отталкиваясь от них, начать работать с кодом по SDD (Spec-Driven Development). Но что делать, если проект нужно начать прям с нуля (greenfield), а выделенный этап ресёрча не особо нужен?
Ниже рассказываю, как в этом случае подойти к вопросу системно, и обеспечить фундамент для качественной gen-AI кодовой базы на основе моих наборов скиллов.
Для того чтобы правильно стартануть, нужно создать три документа: spec.md, architecture.md и roadmap.md. Именно в этом порядке, в рамках одной сессии с агентом, с коммитами в репозиторий после каждого этапа. У меня в проектах эти документы обычно хранятся в docs/development, это не принципиально, но дальше буду использовать этот путь для ориентира.
spec.md
Этот документ — наиболее важный во всём процессе, его проработке следует уделить максимум внимания. Вызываем явно vibe-research, сообщив ему о намерении начать новый проект, и передав в свободной форме всю инфу о нём, которая на данный момент уже известна:
основные идеи, концепции и предметная область,
окружение и стек,
референсы и prior-art (если есть),
все уже очевидные неопределенности, которые нужно решить до генерации документа.
Это запустит цикл, в ходе которого агент будет собирать релевантную инфу в сети и предлагать на обсуждение варианты для раскрытия неопределённостей. Принимать решения здесь нужно максимально аккуратно, т.к. они повлияют на все дальнейшие документы и спеки. Через какое-то число итераций все неопределённости окажутся раскрыты, и скилл выдаст финальные выводы по проделанной работе. Затем отправляем:
На основе результатов этой сессии, подготовь дизайн-документ `spec.md` в `docs/development`, в формате первичной спецификации проекта.
Получившийся документ внимательно вычитываем, уходя при необходимости на достаточное число итераций правок до тех пор, пока он не будет описывать ровно то, что соответствуем нашим представлениям о проекте. Руками все документы этого этапа лучше не исправлять, чтобы история правок осела в контексте сессии.
architecture.md
Когда первичная спека окажется вылизанной, переходим к следующему документу:
На основе `spec.md`, проработай дизайн-документ `architecture.md` в `docs/development`, описывающий предлагаемую архитектуру проекта в степени, достаточной для её реализации в кодовой базе.
Здесь агент, возможно, накидает ещё вопросов о возможных развилках или неоднозначностях, но в конечном итоге появится документ, который тоже надо вычитать и при необходимости итеративно доработать с агентом до готовности.
roadmap.md
Даём следующее задание:
На основе `spec.md` и `architecture.md`, проработай детальный роадмап `roadmap.md` в `docs/development`, разбитый на майлстоуны, майлстоуны — на фазы, фазы — на атомарные подзадачи по схеме What/Where/How/Acceptance Criteria.
Если есть пожелания по майлстоунам (что делать в первую очередь, что потом и т.п.), то их стоит перечислить в конце промпта. Для небольших проектов, в целом, можно обойтись только фазами и подзадачами, без майлстоунов вообще. Роадмап тоже вычитываем и при необходимости просим исправить.
Финализация
Теперь остался буквально один страховочный шаг до генерации формальных спек — в режиме цели (/goal) запускаем:
Проведи проверку всех дизайн-документов в `docs/development` на предмет целостности и консистентности друг с другом, отсутствия противоречий и неоднозначностей, которые могут помешать реализации проекта. Критерии цели: все документы согласованы между собой и полностью готовы к генерации на основе них набора формальных спецификаций.
Здесь у агента тоже могут возникнуть вопросы, отвечаем и ждём, когда диздоки придут к их финальному состоянию.
Формальные спеки
Ну и, наконец, отправляем:
/vibespec-init сгенерируй полный набор формальных спецификаций на английском языке по существующим дизайн-документам; Критерии цели (подтверждаются через чтение всех спецификаций, их анализ и сопоставление с дизайн-документами): полный набор спецификаций сгенерирован, непротиворечив, целостен, консистентен, и полностью покрывает все разделы `docs/development/spec.md`, `docs/development/architecture.md` и этапы `docs/development/roadmap.md`.
И уходим пить чай 😁
Makefile
Но это ещё не всё. Нам ведь надо полностью подготовить проект к работе над ним, а не только получить набор спек. Поэтому после чаепития, когда спеки будут готовы, нужно создать AGENTS.md, который будет ориентировать агента, как именно ему работать с репозиторием. Поскольку в этом файле обязательно должны быть упомянуты в том числе quality-гейты, удобно создать Makefile (или его скриптовый аналог):
Создай Makefile для будущего проекта на основе информации из дизайн-документов и спецификаций, реализующий все предусматриваемые ими quality-гейты и основные операции с кодовой базой, минимально: сборка, прогон релевантными линтерами, прогон всех тестов, запуск debug-версии проекта для отладки.
AGENTS.md
Вот теперь можно создавать и его (в режиме цели):
Исходя из имеющихся дизайн-документов, спецификаций и Makefile, подготовь файл AGENTS.md на английском языке, со всей необходимой и достаточной информацией для эффективной работы агента над этим проектом. Критерии цели: файл создан, содержит необходимую и достаточную информацию о специфике работы над проектом, и соответствует лучшим современным практикам по генерации содержимого для этого файла.
Реализация
Ну а дальше всё просто (в режиме цели):
Реализуй майлстоун N роадмапа `docs/development/roadmap.md` в полном объеме. Критерии приемки: майлстоун N полностью реализован и соответствует спекам, >75% кодовой базы покрыто модульными и интеграционными тестами, все quality-гейты зелёные.
Важный момент: в таком виде давать задание на разработку имеет смысл только агенту с облачными фронтир-моделями. Средним моделям придётся отдавать по фазам, совсем небольшим — по задачам.
Затем, через ревью (процесс которого уже описывал) и коммиты после каждого этапа, доводим роадмап до полного выполнения. После этого документы из docs/development можно удалить, если не хочется поддерживать их актуальность при дальнейшей работе с проектом.
👋
P.S: Да, я сам использую вариации этого подхода практически во всех своих проектах, если что. Все промпты взяты из истории одного из последних проектов, над которым сейчас работаю.✂️ (типа кат)
Поскольку за последние пару недель в личке случилось уже несколько диалогов, затрагивающих эту тему, пожалуй, вынесу её в отдельный пост, чтобы потом просто давать ссылку, вместо многократного повторения одного и того же.
1️⃣ Единственной целью, которую я преследую, работая над этим каналом, является получение удовольствие от осознания того, что мои мысли, опыт и знания периодически оказываются кому-то полезны. Ни больше, ни меньше.
2️⃣ Извлечение финансовой выгоды, как и помощь в её извлечении другим людям или компаниям, не является целью существования данного канала. Поэтому на нём никогда не будет рекламы любых коммерческих услуг и продуктов, мероприятий, вакансий, опросов и прочих джинсовых предложений, вне зависимости от их вендора.
Открытые или хотя бы бесплатные проекты, не прибитые гвоздями к вендору лицензией или ограничениями — на моё личное усмотрение по критериям полезности для аудитории и релевантности тематике канала. Как и перепосты чего-либо откуда-либо.
Мероприятия — только со свободным участием для всех желающих, либо благотворительные, и только если я являюсь их непосредственным организатором (и поэтому могу лично поручиться за качество их проведения и соответствие содержанию).
3️⃣ Канал сугубо личный, поэтому не является и не будет являться участником никаких сообществ или объединений, и тем более, делать что-либо в их интересах.
4️⃣ Я занимаюсь этим исключительно до тех пор, пока получаю обратную связь от аудитории. Реакции, комменты, перепосты, вопросы в личку — не важно. Как и то, 700 человек, 70, или 7 читают посты, если фидбэк продолжает поступать. Если перестанет, канал прекратит своё существование, как не достигающий цели первого пункта
🤩 FORDEC-R&D: метод быстрого исследования
Работая над серьёзными проектами, я стараюсь придерживаться почти классической методики исследований, которая реализована в скиллсете research. Кажется, здесь ещё не рассказывал о нём подробно, но это именно тот скиллсет, для которого в c0wrk выстроен аж отдельный UI. Он здорово помогает, когда нужно проработать что-то весьма большое, с развесистым деревом гипотез, библиотекой разобранных по кирпичикам референсных публикаций, детальной документацией и прогнозами в моменте по каждому этапу исследования.
Но что если проработать нужно что-то не очень большое, документация и прогнозы не нужны, да и времени в обрез? И вот тут нам на помощь приходит... авиация 😳 Сейчас объясню.
FORDEC
FORDEC — это структурированная модель принятия решений, изначально разработанная в гражданской авиации для работы экипажей в критических и стрессовых ситуациях. Она помогает заменить импульсивные реакции на последовательный анализ и разрулить внештатную ситуацию головным мозгом вместо спинного:
F — Facts (Факты) — сбор только проверенной информации и реальных данных.
O — Options (Варианты) — определение доступных сценариев действий.
R — Risks & Benefits (Риски и преимущества) — оценка потенциальных последствий, затрат, угроз и выгоды для каждого варианта.
D — Decide (Решение) — выбор конкретного плана действий из предложенных вариантов.
E — Execute (Выполнение) — назначение ответственных лиц, сроков и выделение ресурсов для реализации решения.
C — Check (Проверка / Контроль) — анализ результатов после выполнения, оценка изменений и принятие решения о том, нужен ли новый цикл.
Тоже это видите? Ведь проведение исследования в условиях сильно ограниченных временных ресурсов и входной неопределённости — это ж ппц, какая стрессовая ситуация. Часто переходящая в критическую. А, отбросив детали, задача FORDEC как раз и заключается в том, чтобы итеративно (но быстро) убрать неопределенность и принять решение.
Вот примерно по такому ходу мыслей и родился FORDEC-R&D:
Шаг
В R&D
Главный вопрос
F — Facts
Зафиксировать известное и неизвестное
Что мы действительно знаем?
O — Options
Сформировать конкурирующие гипотезы / подходы
Какие объяснения или решения возможны?
R — Risks & Benefits
Определить ценность и стоимость проверки
Что важнее всего проверить?
D — Decision
Выбрать следующий эксперимент
Какой тест даст максимум информации?
E — Execution
Провести минимальный эксперимент
Что показала реальность?
C — Check
Пересобрать картину мира
Что теперь изменилось?
В целом, дальше можно не читать, но если заинтересовало, то ниже более подробное описание.
F — Facts
Критически важная часть. В обычном инженерном исследовании люди очень быстро начинают смешивать:
факты;
предположения;
чужие утверждения;
собственные интерпретации;
желания относительно результата.
FORDEC же заставляет сначала расчистить пространство. Я делю факты на четыре категории:
Known
То, что подтверждено непосредственно.
В версии X запрос проходит через функцию Y.
Benchmark показывает 1200 req/s.
Модель возвращает tool call в 83% случаев.
Observed
То, что наблюдалось, но ещё не объяснено.
При длинном контексте latency резко возрастает.
Assumed
То, что пока предполагается.
Вероятно, причина — quadratic attention.
Unknown
То, что необходимо выяснить.
Неизвестно, сохраняется ли эффект при KV-cache.
Это уже создаёт хороший исследовательский артефакт: доску фактов. Причём цель стадии F — не собрать со всего интернета знания по теме. Цель вполне понятная и ограничена сверху: собрать ровно столько фактов, чтобы сформулировать проверяемые варианты объяснения или решения.
O — Options
В авиации это варианты действий. А в исследованиях все варианты определяются через конкурирующие гипотезы, архитектуры или механизмы. Например, исследуется деградация производительности агента, тогда опции выглядят как-то так:
O1 — проблема в размере контекста
O2 — проблема в количестве tool calls
O3 — проблема в latency модели
O4 — проблема в orchestration layer
O5 — проблема комбинированная
Или исследуется применимость архитектур SAST:
O1 — AST pattern matching
O2 — dataflow analysis
O3 — LLM-based analysis
O4 — hybrid static analysis + LLM
Важное правило: должно быть сформулировано минимум две конкурирующие гипотезы. Если опция одна, исследования фактически нет — есть попытка доказать заранее выбранное объяснение.
R — Risks & Benefits
Для инженерного исследования стоит интерпретировать риск не только как риск реализации, но и как риск остаться в неопределенности. Для каждой опции можно оценивать пять параметров:
Параметр
Вопрос
Impact
Насколько важно, если гипотеза верна?
Uncertainty
Насколько мало мы об этом знаем?
Testability
Насколько легко это проверить?
Cost
Сколько стоит эксперимент?
Decision value
Изменит ли ответ дальнейшее решение?
Из чего следует главный принцип быстрых исследований: исследовать надо не самое важное или интересное, а то, где информация сильнее всего влияет на решение.
D — Decision
В классическом FORDEC здесь выбирается действие. В исследовательской версии действием является эксперимент. Причём не решение всей задачи, а именно следующий тест.
Например, плохо:
Исследовать влияние размера контекста.
хорошо:
Запустить одну и ту же задачу на контекстах 8k / 32k / 128k и измерить latency и tool-call success rate.
Ещё лучше, если заранее определено, что должно произойти, чтобы гипотеза считалась подтверждённой. Например:
Если latency увеличивается >2x,
при неизменном числе tool calls,
гипотеза H1 получает сильное подтверждение.
Это защищает от очень распространённой проблемы: post-hoc interpretation, когда после эксперимента исследователь объясняет любой результат так, чтобы исходная идея всё равно выглядела правильной.
E — Execution
Здесь работает принцип MVE: Minimum Viable Experiment. Не реализация в прод, не идеальное прохождение бенчей, не полноценный PoC всего решения, а самый дешёвый эксперимент, который способен различить опции. Например, вместо:
написать новый inference engine
можно:
эмулировать его поведение скриптом на 100 строк и проверить bottleneck.
В быстрых исследованиях одно из главных правил: реализация существует ради информации, а не ради кодовой базы. Будет ли это переписано с нуля, или допилено до MVP из того говна и палок, на котором оно получилось — не вопрос, на который нужно отвечать в рамках быстрого исследования. Поэтому код эксперимента может быть:
грязным;
одноразовым;
неполным;
не покрытым тестами;
плохо абстрагированным.
Если он надёжно отвечает на исследовательский вопрос — он УЖЕ выполнил свою функцию.
C — Check
Check — это не:
«получился ли эксперимент?»
А:
«Как изменилось наше состояние знания?»
После эксперимента обновляется:
KNOWN
OBSERVED
ASSUMED
UNKNOWN
Какие-то unknown становятся known, assumptions разваливаются, появляются новые unknown. Иногда вообще меняется пространство опций. Это всё — нормально.
В качестве примера:
До эксперимента:
O1 context size
O2 tool calls
O3 model latency
После:
O1 практически исключена
O2 вероятна
O3 обнаружена новая зависимость от parallel tool calls
И начинается следующая итерация с этапа F, до тех пор, пока цель исследования не будет достигнута.
Несмотря на количество написанного (я просто постарался описать процесс, как можно подробнее), получается достаточно компактная, понятная и работающая система. Особенно, если взять за железное правило принцип: «одна итерация — один вопрос», превращающий исследование фактически в поиск по формируемому дереву неопределённостей. Который, по сути, становится алгоритмом управления их бюджетом.
Заканчиваем продолжать, подобравшись к самой близкой аппсеку теме: веб-атакам.
Хорошую хронологию дают подборки PortSwigger за 2023, 2024 и 2025 год. Это рейтинги исследовательских находок, поэтому искать в них статистику массовых взломов бессмысленно. Зато видно, какие привычные предположения о безопасности перестали работать в очередной раз.
В 2023-м досталось убеждению, что гонки — занятие для терпеливых людей с быстрым интернетом. В Smashing the state machine Джеймс Кеттл показал, как резко уменьшить влияние сетевых задержек: заранее передать основную часть нескольких HTTP/2-запросов, а завершить их одним TCP-пакетом. В целом, схожая техника (через отправку keep-alive запросов) использовалась и с ранними версиями HTTP, для амплификации побочных каналов. Оказалось, что так проще и попадать в короткие промежуточные состояния приложения. Даже операция, которую разработчик мысленно считает неделимой, внутри проходит несколько этапов.
Параллельно продолжилась история HTTP request smuggling — «контрабанды» запросов через цепочки серверов. Прокси считает, что сообщение закончилось, бэкенд видит продолжение. Или наоборот. Отсюда обход проверок, отравление кэша и ситуации, когда ответы начинают доставаться чужим пользователям. В исследованиях 2023-го фигурировали уже цепочки из нескольких продуктов: согласовать понимание HTTP оказалось весьма непростой задачей. Причём сама методика прекрасно пережила смену протокола. В SMTP Smuggling Тимо Лонгин показал, как расхождения в обработке почтовых сообщений позволяют подделывать отправителя в уязвимых цепочках доставки. А TE.0 оказалась практически применимой на тысячах сайтов Google Cloud.
В 2024-м Orange Tsai разобрал Apache HTTP Server в Confusion Attacks и обнаружил, что модули сервера не всегда согласны с назначением даже общих полей. Для одного значение — путь к файлу, для другого — URL. Это делает возможным обходы контроля доступа, раскрытие исходников и цепочки атак, вплоть до исполнения кода.
Windows внесла вклад через заботу о символах. В определённых конфигурациях PHP-CGI механизм best-fit-конвертации мог превратить мягкий дефис в обычный. После преобразования символ приобретал смысл аргумента командной строки, открывая путь к исполнению кода без аутентификации. Так появилась CVE-2024-4577. Обобщение этой проблемы — WorstFit.
SQL-инъекции получили новые приёмы на уровне протокола обмена с базой. Работа Пола Герсте SQL Injection Isn’t Dead: Smuggling Queries at the Protocol Level, занявшая второе место в рейтинге 2024 года, использовала переполнение значения длины и подход, чем-то похожий на heap spray, для попадания в подставной запрос.
Переход на HTTP/2 потребовал корректных реализаций. В CONTINUATION Flood серверу отправляли продолжения блока заголовков, который никак не завершался. Уязвимые реализации тратили процессор или память вплоть до отказа.
Не обошли вниманием исследователи и тему кэшей. В Wildcard Web Cache Deception декодирование пути позволило добиться кэширования приватного ответа и захвата аккаунта ChatGPT. Исследование Gotta cache ’em all развило способы эксплуатации расхождений между кэшем и сервером.
Doyensec описала CSPT2CSRF: клиентский код подставляет пользовательские данные в путь запроса, а последовательности вроде ../ меняют адрес назначения. При подходящих условиях браузер отправляет запрос к другому эндпоинту с полномочиями пользователя.
В DoubleClickjacking между двумя кликами меняется содержимое под курсором, и второй клик попадает в чувствительную кнопку. Ограничения на встраивание страниц, полезные против классического clickjacking, такой сценарий не закрывают.
Защитные библиотеки и просмотрщики документов тоже были источниками находок. Mizu показал новые обходы DOMPurify через изменения HTML при парсинге — mutation XSS. А CVE-2024-4367 в PDF.js позволяла выполнить произвольный JavaScript через специально подготовленный PDF.
Авторы Cookie Crumbles обнаружили проблемы целостности сессий в девяти из тринадцати исследованных веб-фреймворков. Позже Cookie Tossing применили для перехвата OAuth-флоу: возможность подбросить куку через подконтрольный поддомен влияла на вход в основном сайте.
К 2025-му вырос интерес к побочным каналам. Если содержимое прочитать нельзя, можно измерить его косвенные признаки. В Cross-Site ETag Length Leak цепочка особенностей браузера раскрывала размер ответа другого origin. В XSS-Leak приоритизация пула соединений Chrome помогала определить хосты перенаправления.
В CVE-2025-29927 у Next.js клиентский заголовок x-middleware-subrequest позволял пропустить выполнение middleware. Если проверка авторизации находилась там, запрос мог пройти без неё. Служебную пометку, которой доверял фреймворк, присылал посетитель. У Astro возникла серия родственных проблем: манипуляция URL через непроверенные заголовки, обход проверок пути через кодированные значения, затем двойное URL-кодирование. Маршрутизатор и проверка доступа снова расходились во мнении, какую страницу запросил пользователь.
А в декабре 2025-го прогремел React2Shell. Ошибка декодирования запросов в React Server Components позволяла выполнить код на сервере без аутентификации. CVE-2025-55182 получила CVSS 10.0. Проблема находилась в серверной обработке сериализованных данных. Внутренний протокол фреймворка оказался полноценной поверхностью атаки. Обновление Next.js от 11 декабря описало дополнительные проблемы RSC — отказ в обслуживании и раскрытие исходников.
HTTP desync получил дальнейшее развитие. В HTTP/1.1 must die: the desync endgame Кеттл представил Zero CL и Expect-header desync и сформулировал жёсткий вывод: от HTTP/1.1 на соединении к бэкенду пора отказываться. Поддержка HTTP/2 между браузером и CDN мало помогает, если дальше запрос переводят в HTTP/1.1. А блокировка подозрительного Transfer-Encoding не спасает от техник, которым этот заголовок вообще не нужен. В марте 2026-го разбор Pingora от Cloudflare добавил новые примеры. Преждевременный Upgrade позволял пересылать байты до подтверждения смены протокола, обработка некоторых HTTP/1.0-запросов создавала рассинхронизацию, а стандартный ключ кэша без хоста и схемы допускал коллизии между сайтами.
И, хотя тут без ИИ не обошлось, но не упомянуть в конце HTTP Terminator, представленный Кеттлом в 2026-м, просто нельзя. В статье рассказывается о создании автономной ИИ-системы, разработанной для генерации, тестирования и масштабирования принципиально новых техник атак класса HTTP Desync.