
Сквозное воспроизведение и кросс-слойное обнаружение CVE-2026-53576 — неаутентифицированной RCE в Kestra, — доведённое дальше базового PoC, чтобы показать, как распространённая ошибка конфигурации Docker-сокета превращает root в контейнере в полную компрометацию хоста.
Kestra, платформа оркестрации рабочих процессов с открытым исходным кодом, поставляла фильтр аутентификации, который определял, нужны ли запросу учётные данные, проверяя, заканчивается ли URL на /configs — проверка, предназначенная для открытия одной безобидной публичной конечной точки.
Поскольку проверка смотрела только на хвост строки, любой запрос, URL которого случайно заканчивался таким образом, полностью пропускал аутентификацию, включая конечные точки, которые создают и выполняют произвольный код. Результат: любой, кто может достичь уязвимого экземпляра Kestra по сети, может выполнять команды от имени root без каких-либо учётных данных, без фишинга, без подбора пароля, без необходимости в отдельном шаге повышения привилегий.
В этом упражнении этот единственный пробел был проведён от начала до конца против самостоятельно размещённого лабораторного экземпляра:
Каждый этап был зафиксирован с помощью защитной телеметрии (сетевой IDS, безопасность среды выполнения хоста, аудит Linux и системные журналы).
Влияние на бизнес при отсутствии патча: полная компрометация хоста, на котором работает Kestra, а не только приложения — и, соответственно, всего остального, что достижимо с этого хоста.
Исправление: обновитесь до Kestra 1.0.45 / 1.3.21 или более поздней версии (один только патч закрывает основную уязвимость; этапы повышения привилегий и закрепления требуют отдельного, независимого исправления — не монтируйте Docker-сокет в контейнеры приложений, см. раздел 8).
В этом отчёте документируется CVE-2026-53576 — уязвимость неаутентифицированного удалённого выполнения кода с оценкой CVSS 10.0 в Kestra ≤1.3.20, вызванная обходом аутентификации по суффиксу пути (AuthenticationFilter.java, endsWith("/configs")).
Злоумышленник, который называет пространство имён и ID рабочего процесса configs, может создавать и выполнять произвольные shell-команды без учётных данных, от имени root, внутри контейнера Kestra. Исправлено в 1.0.45 и 1.3.21. Идентичная ошибка также была независимо зарегистрирована под CVE-2026-49869 — это номер из каталога CISA KEV, связанный с реальной эксплуатацией в дикой природе. Оба следует указывать вместе, поскольку публичные источники и сканеры могут ссылаться на любой из них.
| Уязвимые | Kestra ≤ 1.3.20 (и до 1.0.45 в ветке 1.0.x) |
| Исправленные | 1.0.45 / 1.3.21+ |
| CVE | CVE-2026-53576 |
| Парный advisory | CVE-2026-49869 — та же ошибка, в каталоге CISA KEV (в дикой природе) |
| Дата | Событие |
|---|---|
| 2026-06-02 | Выпущена Kestra 1.3.21; в changelog упоминается «potential authentication bypass in the authentication filter» |
| 2026-06-03 | Выпущена Kestra 1.0.45 в ветке 1.0.x |
| 2026-09-02 | CVE-2026-49869 (парный advisory) добавлен в каталог CISA KEV |
| 2026-09-22 — 09-24 |
Самостоятельно размещённый домашний стенд:
- Хост Debian (имя хоста docker, ядро 6.12.107+deb13-amd64),
- Docker Engine 29.8.1.
- Kestra развёрнута как официальный образ kestra/kestra:v1.3.20, доступна через обратный прокси Caddy по адресу kestra.int.atlasvec.com.
- Тестируемый стек телеметрии: Falco (runtime/eBPF, уровень хоста),
- Suricata 8.0.3 IDS (пассивное зеркало SPAN), пересылка в Splunk.
Прежде чем что-либо трогать, я убедился, что цель действительно уязвима, а стек телеметрии работает. Я также сделал снимок состояния до атаки, чтобы можно было откатиться после завершения.
Kestra, работающая на уязвимой версии:

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

Юниты Falco, загруженные на хосте:

Falco активно генерирует события (режим modern eBPF):

Служба Suricata активна:

И её eve.json записывает события в реальном времени:

Здесь совпали две ошибки, а не одна.
Во-первых, фильтр аутентификации определяет, может ли запрос пропустить аутентификацию, сопоставляя конец пути запроса:```java // AuthenticationFilter.java — the vulnerable check, confirmed against the self-hosted v1.3.20 source if (path.endsWith("/configs")) { // treated as a public, unauthenticated path }
Эта проверка существует, чтобы открыть одну безобидную конечную точку — публичную конфигурацию экземпляра. Поскольку `endsWith` читает только хвост строки, он не может отличить один безопасный путь от любого другого пути, который случайно заканчивается так же.
Во-вторых, маршрутизатор Kestra всё равно направляет эти похожие пути к их реальным, чувствительным обработчикам. `/api/v1/main/flows/configs` достигает обработчика создания потока. `/api/v1/main/executions/configs/configs` достигает обработчика выполнения. Фильтр пропускает оба как публичные, и маршрутизатор всё равно их выполняет. Назовите namespace и ID потока `configs`, и конечная точка, выполняющая код, теперь заканчивается на `/configs`, так что она наследует бесплатный пропуск публичного пути.
Захваченная пара запросов показывает это наглядно. `GET /api/v1/main/flows/search` без учётных данных возвращает `401`. `POST /api/v1/main/flows/configs` с тем же пустым заголовком авторизации возвращает `200` и сохраняет поток (см. §5.1).
## 5. Симуляция атаки
> Каждый шаг ниже запечатлён на отдельном скриншоте. Всё это выполняется в моей собственной изолированной домашней лаборатории против самостоятельно размещённого экземпляра Kestra за обратным прокси.
### 5.1 Этап 1 — обход аутентификации -> root внутри контейнера Kestra
**Контроль :** `GET /api/v1/main/flows/search` без учётных данных → `401 Unauthorized`.
Подтверждает, что аутентификация применяется на обычном маршруте.

_____
**Закладка :** Отправка `POST /api/v1/main/flows/configs` без заголовка авторизации. Тело — это YAML потока, у которого `namespace` и `flow id` оба равны `configs`, содержащий задачу Commands, использующую Process task runner. Сервер возвращает `200 OK` и сохраняет поток. Суффикс `/configs` на в остальном защищённом маршруте — это то, что запускает обход **(см. Раздел 4).**

**Слушаем и ждём** : Запускаю простой слушатель с помощью nc для целей тестирования, чтобы поймать обратную оболочку.

**Триггер / Выполнение** `POST /api/v1/main/executions/configs/configs`, без заголовка авторизации → `200 OK`, создано новое выполнение.

**БaaaaM :** Мы получили оболочку, но помните — мы «изолированы», `root` **НО** внутри docker-контейнера Kestra, так что мы «не должны» иметь возможность вырваться отсюда. «**ну, так я думал**»

Команда задачи выполняется как прямой дочерний процесс JVM-процесса Kestra, что подтверждено деревом процессов на стороне хоста, и успешно завершается согласно собственному журналу выполнения Kestra.
**Панель журналов Kestra**

**Дерево процессов Kestra**

**Результат:** неаутентифицированное удалённое выполнение кода от имени root внутри контейнера Kestra. Это полное, самодостаточное доказательство **CVE-2026-53576**.
### 5.2 Этап 2 — повышение привилегий через открытый Docker-сокет (специфично для домашней лаборатории, не является частью самой CVE)
Из оболочки Этапа 1 было обнаружено, что `/var/run/docker.sock` смонтирован в контейнер — паттерн Docker-outside-of-Docker (DooD), распространённый, когда настроен собственный `Docker` task runner Kestra, поскольку ему нужен демон для взаимодействия.
Сам доступ к этому сокету эквивалентен **root** на хосте: Docker API позволяет клиенту настраивать произвольные привязки хостовых файловых систем и хостовые PID/сетевые пространства имён для контейнеров, которые он запускает, а демон за сокетом уже работает от имени root на хосте.
Это не эксплойт ядра или выхода из контейнера — это Docker API, делающий то, для чего он предназначен, доступный оттуда, откуда он не должен был быть доступен.
Используя сокет, я создал соседний контейнер с привязанной файловой системой хоста и хостовыми PID и сетевыми пространствами имён, затем запустил его.
**Примечание:** во время тестирования также всплыл второй, более тихий вариант, хотя я не сделал для него отдельный скриншот. Вместо постоянного создания нового соседнего контейнера тот же доступ к сокету позволяет выполнить `POST /containers/{id}/exec` против уже запущенного контейнера, так что события создания контейнера вообще нет. На этом Docker-хосте у меня есть и Kestra, и Portainer, любой из которых я мог бы использовать для того же выхода. Это важно для обнаружения: любое правило, сосредоточенное на создании нового привилегированного контейнера, пропускает этот вариант.
Подтверждаю root и обнаруживаю сокет, лежащий там, не ограниченный никакими ограничениями:

Проверяю, что Docker-демон действительно доступен через сокет:

Перечисляю, какие образы уже есть локально, чтобы не нужно было ничего подтягивать:

Первая попытка: привилегированный контейнер с привязанной файловой системой хоста:

Вторая попытка, на этот раз с добавлением хостовых PID и сетевых пространств имён:

Третья попытка, chroot прямо в смонтированную файловую систему хоста:

Слушатель поднят и ждёт на порту, на который будет звонить оболочка выхода:

Запускаю контейнер:

**Root, но на этот раз это хост, а не контейнер:**

Версия ядра соответствует реальному хосту, а не изолированному представлению контейнера:

Повышаю до полноценной интерактивной оболочки для остальной части сессии:

### 5.3 Этап 3 — закрепление, подтверждённый сработавший
Из оболочки с root на хосте я настроил два независимых механизма закрепления,
**Первый**, SSH-ключ. Я проверил, у кого уже был доступ :

Затем проверил собственную конфигурацию sshd на предмет всего, что могло бы заблокировать новый ключ:

Сгенерировал пару ключей на атакующей машине:

Подтвердил, что sshd действительно слушает:

Поместил публичный ключ в `authorized_keys` root:

И вошёл с соответствующим приватным ключом:

**PS:** Это root-доступ, независимый от исходной цепочки RCE. Даже если Kestra будет пропатчена, этот ключ всё ещё работает.
**Второй**, cron-маяк. Я поместил callback уровня root, настроенный на выполнение через интервал, затем протестировал со свежим слушателем, чтобы увидеть, действительно ли он срабатывает по расписанию:

**Он сработал. Вторая точка опоры на хосте, независимая как от RCE, так и от SSH-ключа.**
### 5.4 Проверенная временная шкала kill-chain.
Приведённая ниже временная шкала отличается: она полностью реконструирована из независимой телеметрии на стороне защитника (сетевой IDS + runtime-безопасность хоста), извлечённой и проверенной вживую против реальных данных Splunk.
**Основной RCE — сеть обнаруживает доставку, хост подтверждает выполнение, с разницей в 103 секунды:**
| Время (EDT) | Уровень | Событие |
| ------------ | ------------------ | -------------------------------------------------------------------------------------------- |
| 02:46:09.778 | Сеть (Suricata) | Срабатывает публичная сигнатура ET для этой конкретной CVE |
| 02:47:52.277 | Хост (Falco) | Обратная оболочка подтверждена внутри контейнера Kestra, захвачены точная команда и ID контейнера |
**Повышение привилегий — сеть и хост независимо друг от друга подтверждают разные части одного и того же события:**
| Время (EDT) | Уровень | Событие |
| ------------ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| 03:48:55.503 | Хост (Falco) | Обратная оболочка из контейнера повышения привилегий |
| 03:51:57.360 | Сеть (Suricata) | TCP-поток **и** контентная сигнатура — буквальная строка `uid=0(root)` была замечена покидающей хост в открытом виде, когда выполнялась `id` |
## 6. Инженерия обнаружения
Я построил обнаружения против реальной захваченной телеметрии, затем протестировал каждое против воспроизведения. Матрица показывает, где покрытие существовало до того, как я начал, и где его не было.
### 6.0 Матрица покрытия обнаружения
| Этап | Сеть (Suricata) | Хост (Falco) | Хост (auditd/journald) | Статус |
| --- | --- | --- | --- | --- |
| Запрос первоначального эксплойта | Срабатывает публичная сигнатура ET | Нет видимости на уровне приложения | - | Покрыто (существовало ранее) |
| Выполнение RCE | - | Нативное правило с тегом T1059 | - | Покрыто (существовало ранее) |
| Повышение привилегий через Docker-сокет (сама техника) | - | Пробел: ловит только результирующую оболочку, а не API-вызовы злоупотребления сокетом | Записи EXECVE захватывают точные вызовы `curl --unix-socket` | Пробел найден, закрыт с помощью auditd + пользовательского правила Falco |
| Подтверждение воздействия повышения привилегий | Контентная сигнатура на утёкший вывод `id` | То же общее правило для оболочки | - | Покрыто (существовало ранее, кросс-уровневое) |
| SSH-закрепление | - | - | journald: полный жизненный цикл PAM плюс отпечаток ключа | Покрыто (только хост) |
| Cron-закрепление | - | - | journald и linux_audit, два источника, точная 5-минутная периодичность | Покрыто (только хост) |
Пробел — это повышение привилегий через docker-сокет. Правила Falco по умолчанию ловят оболочку, которую порождает скомпрометированный контейнер, но ничто в стабильном наборе по умолчанию не помечает процесс, обращающийся к `/var/run/docker.sock` для прямого управления Docker API. Правила, которые касались бы запуска привилегированных контейнеров, `Launch Privileged Container` и `Launch Sensitive Mount Container`, поставляются со зрелостью `incubating` и `sandbox`, так что набор правил по умолчанию их тоже не загружает. Я закрыл пробел корреляционным поиском auditd (Обнаружение 03) и пользовательским правилом Falco (§6.3).
### 6.1 Splunk — пять корреляционных поисков, развёрнутых и запланированных
Все пять работают вживую в Splunk (`*/5 * * * *`, отслеживание алертов включено, throttling по полям), а не просто написаны и оставлены как текст. Полный SPL, точные заметки о FP, серьёзность, теги MITRE и процедуры реагирования для каждого находятся в `detections/splunk/*.spl`. Таблица статуса ниже — это проверенный вживую результат для каждого, включая реальный баг, который я нашёл и исправил.
| # | Обнаружение | MITRE | Статус |
| --- | ---------------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| 01 | Сетевая сигнатура эксплойта (потребляет алерт Suricata ET) | T1190 | **Сработало** — историческое воспроизведение подтверждено, новых событий с тех пор нет (вне текущего окна поиска) |
| 02 | Выполнение RCE на хосте (правило Falco redirect-to-network) | T1059 | **Сработало** — историческое воспроизведение подтверждено |
| 03 | Злоупотребление Docker-сокетом (auditd, закрывающий пробел) | T1610, T1611 | **Сработало на самом живом планировщике** — `triggered_alert_count: 1` подтверждено на реальном запуске `*/5 * * * *`, а не только на ручном тесте |
| 04 | SSH-закрепление через root-логин | T1098.004, T1021.004 | **Сработало** — историческое воспроизведение подтверждено |
| 05 | Cron-закрепление через маяк | T1053.003 | **Развёрнуто с багом, который я нашёл и исправил вживую.** Окно отправки `-10m` содержало только два из трёх 5-минутных интервалов, необходимых порогу `distinct_intervals >= 3`, так что оно никогда не могло сработать, несмотря на то что маяк всё ещё работал в реальных данных. Расширил его до `-30m` и переразвернул — и с тех пор планировщик запускал его 53 раза, так что исправление держится. |
Разделение труда в Обнаружении 03 намеренное: вместо повторной реализации собственной логики сигнатур Suricata в SPL или попытки заставить Falco поймать технику уровня сокета, для которой он не создавался, слой SIEM здесь потребляет запись EXECVE от auditd о точных вызовах `curl --unix-socket` — источник телеметрии, доказанно наиболее надёжный для этой конкретной техники.
1. **Обнаружение 01** — срабатывание сигнатуры ET (алерт `suricata:eve`):```spl
index=main sourcetype=suricata:eve event_type=alert
alert.signature="ET WEB_SPECIFIC_APPS Kestra Unauthenticated Remote Code Execution (CVE-2026-53576)"
| stats count min(_time) as first_seen max(_time) as last_seen
values(src_ip) as source_ips
by dest_ip dest_port
| eval first_seen=strftime(first_seen,"%Y-%m-%d %H:%M:%S")
| eval last_seen=strftime(last_seen,"%Y-%m-%d %H:%M:%S")

Detection 02 — правило Falco о перенаправлении в сеть, разбитое по контейнерам. Исходный IP смещается с bridge-адреса контейнера Kestra (172.17.0.3, первичный RCE на порту 4489) на сам хост (172.66.66.67, эскалация на порту 4491) — эскалация из контейнера на хост становится видимой — тогда как назначение остаётся неизменным: слушатель атакующего на 172.66.66.125:

Detection 03 — записи EXECVE в auditd для curl --unix-socket docker.sock:

Detection 04 — вход root по SSH через journald, отпечаток ключа зафиксирован:

Detection 05 — маяк cron со скрытым dotfile (/usr/local/bin/.sysmon), работающий от root с точной 5-минутной периодичностью: 79 различных интервалов за ~6,5 часов (04:30–11:00 EDT). Порог distinct_intervals >= 3 — это то, что отличает периодический маяк от разового задания cron:

Все пять развёрнуты как запланированные сохранённые поиски (*/5 * * * *):

Доказательство того, что планировщик cron действительно запускает их, а не просто что они существуют: все пять выполнились 54 раза. Detection 03 сработал один раз (злоупотребление docker-сокетом, 06:35 EDT), а Detection 05 сработал 53 раза (всё ещё активный маяк cron). Detections 01/02/04 показывают 0 срабатываний, поскольку это разовые исторические события (запрос эксплойта, RCE, вход по SSH), которые вышли за пределы окна -10m, тогда как живой или повторяющийся экземпляр всё ещё подавал бы сигнал:

Паттерн reverse-shell через /dev/tcp/ — использованный как в первичном RCE, так и в обратном вызове при эскалации. SigmaHQ поставляет для него правило: "Suspicious Reverse Shell Command Line" (id 738d9bcf-6999-4fdb-b4ac-3033037db8ab, автор Florian Roth из Nextron Systems), и одно из его ключевых слов — bash -i >& /dev/tcp/ — именно то, что появилось в нашем захвате.
Поэтому я взял это правило без изменений (detections/sigma/lnx_shell_susp_rev_shells.yml) и проверил его ключевое слово на том же событии Falco, которое уже захватил Detection 02. Sigma не «срабатывает» сама по себе — это переносимая сигнатура, а не работающий движок — но я могу показать, как её ключевое слово попадает на реальную телеметрию из этого запуска, а не на хрестоматийный пример.
Публичное правило SigmaHQ "Suspicious Reverse Shell Command Line" — его ключевое слово bash -i >& /dev/tcp/ против того же реального события, что и Detection 02:

detections/falco/docker_socket_abuse.yaml — развёрнуто на живом экземпляре Falco и подтверждено срабатыванием на реальном воспроизведении, 2026-09-24T10:27:29Z.
Набор правил Falco по умолчанию это не покрывает. Обнаружение процесса, обращающегося к /var/run/docker.sock, — не новая идея: собственные примеры Falco от Sysdig делают это, отслеживая open_write по пути сокета — но такого правила нет в поставляемом/стандартном наборе (у проекта есть открытый запрос на него, falcosecurity/falco #2940), а единственное смежное стандартное правило ловит только CLI docker/kubectl, но не сырой curl --unix-socket.
Загвоздка: этот стандартный подход с open_write по сокету не сработал в данной конфигурации — с modern_bpf и пассивным зеркалом сокет достигается через connect(), а не open(). Поэтому вместо этого я сопоставляю командную строку процесса при порождении (spawned_process + proc.cmdline contains "docker.sock") — тот же класс событий, который Falco здесь уже надёжно обрабатывает. Он срабатывает дважды за вызов — обёртка shell и листовой curl — с полным контекстом:```
priority=Critical, container_id=d417e027d444, image=kestra/kestra:v1.3.20, user=root, cmdline="curl -s --unix-socket /var/run/docker.sock http://localhost/version", tags=[T1610, T1611]
Пользовательское правило Falco сработало - "Unexpected Process Accessing Docker Socket" (T1610/T1611):

Сработавшие стандартные правила Falco - в наборе по умолчанию нет правила для docker-socket, и это и есть пробел:

### 6.4 Suricata - существующее публичное покрытие, пользовательское правило отложено
Сетевой уровень для основной эксплуатируемой уязвимости уже покрыт публичной сигнатурой Emerging Threats, `ET WEB_SPECIFIC_APPS Kestra Unauthenticated Remote Code Execution (CVE-2026-53576)`, которая сработала на реальном трафике во время Фазы 2.
Ниже показан тот же обход, видимый на сетевом уровне, и я построил запрос исходя из шаблона уязвимости. Каждый запрос, путь которого заканчивается на `/configs` на эндпоинте `flows` или `executions`, возвращал `200`, тогда как обычный маршрут (`/flows/search`) возвращал ожидаемый `401`.
Столбец `Attacker IP (XFF)` - это реальный источник HTTP, считанный из заголовка `X-Forwarded-For`: `10.10.10.106`, мой Mac с запущенным Burp. Собственный `src_ip` Suricata видит только хоп обратного прокси (`172.66.66.1`). Это другая машина, отличная от Kali-бокса `172.66.66.125`, который позже принял обратные оболочки и выполнил вход по SSH, поэтому HTTP-эксплуатация и обратные вызовы исходили с разных хостов.

### 6.5 Дашборд
`cve_2026_53576_kill_chain` развёрнут в Splunk и содержит: ключевые KPI (слои обнаружения, техники ATT&CK, развёрнутые обнаружения, механизмы персистентности), столбчатую диаграмму временной шкалы атаки с раскраской по слою телеметрии, карту kill-chain MITRE ATT&CK с живым подсчётом доказательств по каждой стадии, коррелированную временную шкалу сети и хоста из §5.4, покрытие обнаружения по запланированным сохранённым поискам, разбивку по технике docker-socket и таблицу индикаторов компрометации, получаемую в реальном времени из логов.
- KPI, диаграмма временной шкалы атаки и начало карты ATT&CK:

- Остальная часть карты ATT&CK, коррелированная kill-chain, покрытие обнаружения рядом с разбивкой по docker-socket и таблица IOC:

## 7. Сопоставление с ATT&CK
| Тактика | Техника | Доказательство |
| -------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Initial Access | T1190 - Exploit Public-Facing Application | Запросы control/plant/execute (§5.1); срабатывание сигнатуры Suricata ET, §5.4 |
| Execution | T1059.004 - Command and Scripting Interpreter: Unix Shell | Задача `Commands` в Kestra, дерево процессов хоста (`07-kestra-process-tree.png`); правило Falco, §6.1 Обнаружение 02 |
| Command and Control | T1095 - Non-Application Layer Protocol | Обратная оболочка через сырой TCP `/dev/tcp/` - фрейминг C2 прикладного уровня не использовался |
| Discovery | T1613 - Container and Resource Discovery | Перечисление образов Docker через сокет (`10-docker-images-enum.png`) |
| Privilege Escalation | T1610 - Deploy Container | Создание/запуск соседнего контейнера через Docker API (`11`–`15`); записи auditd EXECVE, §6.1 Обнаружение 03 |
| Privilege Escalation | T1611 - Escape to Host | Подтверждение root на хосте, совпадение hostname/ядра (`16`, `17`) |
| Persistence | T1098.004 - Account Manipulation: SSH Authorized Keys | Внедрение ключа в `/root/.ssh/authorized_keys` (`23`) |
| Lateral Movement | T1021.004 - Remote Services: SSH | Успешный вход root по ключу (`24`); journald, §6.1 Обнаружение 04 |
| Persistence | T1053.003 - Scheduled Task/Job: Cron | Cron-маячок, подтверждено срабатывание по расписанию (`25`); §6.1 Обнаружение 05 |
## 8. Устранение и усиление защиты
**Основное исправление.** Обновитесь до Kestra 1.0.45 (ветка 1.0.x) или 1.3.21+ (ветка 1.3.x). Это само по себе закрывает обход аутентификации - Стадию 1 данного упражнения - и является
единственным исправлением, которое после применения не требует дополнительных компенсирующих мер. См. [GHSA-2q47-568g-9h4f](https://github.com/kestra-io/kestra/security/advisories/GHSA-2q47-568g-9h4f).
**Если немедленное патчирование невозможно**, компенсирующие меры в порядке убывания эффекта:
1. **Принудительная проверка на обратном прокси** - поскольку уязвимый фильтр даёт сбой только на сыром суффиксе пути, обратный прокси перед Kestra (Caddy, в этой лаборатории) может независимо требовать аутентификацию для любого пути, соответствующего `/api/v1/*/(flows|executions)/.*`, независимо от того, чем он заканчивается, закрывая обход на уровне, до которого ошибка приложения не дотягивается.
2. **Ограничьте монтирование Docker-сокета** - это полностью закрывает Стадии 2–3, независимо от патча Kestra. Не выполняйте bind-mount `/var/run/docker.sock` в контейнер Kestra. Если `Docker` task runner действительно необходим, используйте ограниченный прокси-сокет (например, `docker-socket-proxy`), который разрешает только определённые вызовы API вместо предоставления полного доступа к демону - полный доступ к сокету эквивалентен root на хосте, как показано в §5.2.
3. **Усиление SSH** - первый механизм цепочки персистентности зависел от того, что root вообще доступен по SSH с ключом. `PermitRootLogin no` (или требование bastion/MFA для root) заблокировало бы этот конкретный путь персистентности независимо от первоначального RCE - стоит сделать независимо от данной CVE.
4. **Промежуточное покрытие обнаружением** - корреляционные поиски Splunk и пользовательское правило Falco, все развёрнутые и проверенные в ходе этого упражнения, обеспечивают промежуточное покрытие для стадий этой конкретной цепочки, пока запланировано патчирование.
**Гигиена после инцидента**, если этот шаблон обнаружен уже эксплуатируемым: рассматривайте это как полную компрометацию хоста, а не только компрометацию приложения - смените все учётные данные и секреты, к которым имел доступ экземпляр Kestra (записи KV-хранилища, учётные данные подключений к нижестоящим системам), а не только собственный доступ Kestra.
**Статус в реальном мире.** `CVE-2026-49869`, парный advisory для этой же ошибки, указан в каталоге KEV CISA и связан с наблюдаемыми кампаниями по криптомайнингу и краже облачных учётных данных. `CVE-2026-53576` (этот отчёт) не имеет собственной записи в KEV, но это та же уязвимость. Инструменты управления уязвимостями, отслеживающие только один из двух номеров CVE, могут занижать оценку подверженности.
## 9. Ссылки
- Kestra Security Advisory - [GHSA-2q47-568g-9h4f](https://github.com/kestra-io/kestra/security/advisories/GHSA-2q47-568g-9h4f) (CVE-2026-53576, основной источник этого отчёта)
- Парный advisory - [GHSA-5vc5-wxxq-3fjx](https://github.com/kestra-io/kestra/security/advisories/GHSA-5vc5-wxxq-3fjx) (CVE-2026-49869, идентичная ошибка, указана в CISA KEV)
- CVE.org - [CVE-2026-53576](https://www.cve.org/CVERecord?id=CVE-2026-53576), [CVE-2026-49869](https://www.cve.org/CVERecord?id=CVE-2026-49869)
- CISA Known Exploited Vulnerabilities Catalog - https://www.cisa.gov/known-exploited-vulnerabilities-catalog (запись CVE-2026-49869, добавлена 2026-09-02)
- Исправленные релизы, оба подтверждены как существующие и помеченные тегами - [v1.3.21](https://github.com/kestra-io/kestra/releases/tag/v1.3.21) (2026-06-02, changelog явно ссылается на "potential authentication bypass in the authentication filter"), [v1.0.45](https://github.com/kestra-io/kestra/releases/tag/v1.0.45) (2026-06-03)
- Техника эскалации через Docker-сокет (§5.2) - [HackTricks: Docker Breakout / Privilege Escalation](https://hacktricks.wiki/en/linux-hardening/privilege-escalation/docker-security/docker-breakout-privilege-escalation/index.html), [Trail of Bits: Understanding Docker Container Escapes](https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/), [MindPatch: Docker Escape](https://www.mindpatch.net/posts/docker-escape/)
- Правило Sigma (§6.2) - публичное правило, которое я использовал вместо написания собственного: [SigmaHQ "Suspicious Reverse Shell Command Line"](https://github.com/SigmaHQ/sigma/blob/master/rules/linux/builtin/lnx_shell_susp_rev_shells.yml) (id `738d9bcf-6999-4fdb-b4ac-3033037db8ab`, Florian Roth / Nextron Systems), используется по [Detection Rule License 1.1](https://github.com/SigmaHQ/sigma/blob/master/LICENSE.Detection.Rules.md)
- Правило Falco (§6.3) - пробел с docker-socket является открытым запросом в upstream ([falcosecurity/falco #2940](https://github.com/falcosecurity/falco/issues/2940)); пользовательское правило следует шаблону из [примеров Sysdig по Docker + Falco](https://www.sysdig.com/blog/docker-falco-security)
| Данное лабораторное воспроизведение, построение обнаружения и кросс-уровневая валидация |