# [CRIT] [GHSA / CRITICAL] CVE-2026-53753: Crawl4AI: AST Sandbox Escape via gi_frame.f_back Chain - Pre-Auth RCE in Docker API

**Source:** GitHub Security Advisories
**Published:** 2026-06-16
**Article:** https://github.com/advisories/GHSA-qxjp-w3pj-48m7

## Threat Profile

Crawl4AI: AST Sandbox Escape via gi_frame.f_back Chain - Pre-Auth RCE in Docker API

### Summary

The `_safe_eval_expression()` function in the computed fields feature uses an AST validator that only blocks attributes starting with underscore. Python generator and frame object attributes (`gi_frame`, `f_back`, `f_builtins`) do NOT start with underscore, enabling a complete sandbox escape to achieve arbitrary code execution.

The attack requires no authentication (JWT disabled by default) and is …

## Indicators of Compromise (high-fidelity only)

- **CVE:** `CVE-2026-53753`

## MITRE ATT&CK Techniques

- **T1190** — Exploit Public-Facing Application
- **T1059.006** — Command and Scripting Interpreter: Python
- **T1059.004** — Command and Scripting Interpreter: Unix Shell
- **T1552.001** — Unsecured Credentials: Credentials In Files
- **T1552.005** — Unsecured Credentials: Cloud Instance Metadata API
- **T1083** — File and Directory Discovery
- **T1082** — System Information Discovery

## Kill chain phases observed

_(none detected from narrative keywords)_

## Recommended hunts

### Crawl4AI CVE-2026-53753: POST /crawl body with AST sandbox-escape payload (gi_frame/f_back/f_builtins)

`UC_27_1` · phase: **exploit** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
`web` sourcetype IN ("nginx:plus:kv","apache:access","ms:iis:default","stream:http","bro:http","aws:waf") method=POST uri_path="*/crawl*" (form_data=*gi_frame* OR form_data=*f_back* OR form_data=*f_builtins* OR form_data=*JsonCssExtractionStrategy* OR _raw=*gi_frame* OR _raw=*f_back* OR _raw=*f_builtins*) | stats count min(_time) as first_seen max(_time) as last_seen values(src_ip) as src_ips values(http_user_agent) as user_agents values(uri_path) as uris by site, host | where count >= 1
```

**Defender KQL:**
```kql
let payload_signatures = dynamic(["gi_frame","f_back","f_builtins","__import__","JsonCssExtractionStrategy"]);
DeviceNetworkEvents
| where Timestamp > ago(7d)
| where RemotePort in (8000, 8001, 11235, 80, 443)
| where InitiatingProcessFileName in~ ("python.exe","python3.exe","uvicorn.exe","gunicorn.exe","crawl4ai.exe")
   or InitiatingProcessCommandLine has_any ("crawl4ai","crawl4ai.server","uvicorn")
| extend AdditionalFieldsParsed = parse_json(AdditionalFields)
| where tostring(AdditionalFieldsParsed) has_any (payload_signatures)
| project Timestamp, DeviceName, RemoteIP, RemotePort, LocalIP, LocalPort, InitiatingProcessFileName, InitiatingProcessCommandLine, AdditionalFields
| order by Timestamp desc
```

### Crawl4AI/uvicorn (python) parent spawning shell or reconnaissance binaries (post-RCE CVE-2026-53753)

`UC_27_2` · phase: **install** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats summariesonly=t count min(_time) as first_seen max(_time) as last_seen values(Processes.process) as cmds values(Processes.process_path) as paths from datamodel=Endpoint.Processes where Processes.parent_process_name IN ("python","python3","python3.10","python3.11","python3.12","uvicorn","gunicorn") AND (Processes.parent_process=*crawl4ai* OR Processes.parent_process=*uvicorn*crawl4ai* OR Processes.parent_process=*server* AND Processes.parent_process=*11235*) AND Processes.process_name IN ("sh","bash","dash","zsh","ash","curl","wget","nc","ncat","netcat","socat","whoami","id","uname","hostname","ifconfig","ip") by host, Processes.user, Processes.parent_process_name, Processes.parent_process, Processes.process_name, Processes.process | `drop_dm_object_name(Processes)` | where NOT match(parent_process,"(?i)(pip install|setup\\.py|pytest|jupyter|pre-commit)")
```

**Defender KQL:**
```kql
let crawl4ai_parents = dynamic(["python","python.exe","python3","python3.10","python3.11","python3.12","uvicorn","uvicorn.exe","gunicorn"]);
let recon_children = dynamic(["sh","bash","dash","zsh","ash","curl","wget","nc","nc.exe","ncat","netcat","socat","whoami","whoami.exe","id","uname","hostname","hostname.exe","ifconfig","ip","ipconfig.exe"]);
DeviceProcessEvents
| where Timestamp > ago(7d)
| where InitiatingProcessFileName in~ (crawl4ai_parents)
| where InitiatingProcessCommandLine has_any ("crawl4ai","uvicorn","server","11235","app:app")
| where FileName in~ (recon_children)
| where not(InitiatingProcessCommandLine has_any ("pip install","setup.py","pytest","jupyter","pre-commit","build"))
| project Timestamp, DeviceName, AccountName,
          ParentImage = InitiatingProcessFolderPath,
          ParentCmd   = InitiatingProcessCommandLine,
          ChildImage  = FolderPath,
          ChildCmd    = ProcessCommandLine,
          SHA256
| order by Timestamp desc
```

### Crawl4AI post-RCE credential-file, /proc/self/environ, or cloud-metadata access

`UC_27_3` · phase: **actions** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats summariesonly=t count min(_time) as first_seen max(_time) as last_seen values(Processes.process) as cmds from datamodel=Endpoint.Processes where Processes.parent_process_name IN ("python","python3","python3.10","python3.11","python3.12","uvicorn","gunicorn","sh","bash","dash") AND (Processes.process=*"/etc/passwd"* OR Processes.process=*"/etc/shadow"* OR Processes.process=*"/proc/self/environ"* OR Processes.process=*printenv* OR Processes.process=*"169.254.169.254"* OR Processes.process=*"/latest/meta-data"* OR Processes.process=*"metadata.google.internal"* OR Processes.process=*"169.254.170.2"* OR Processes.process=*".aws/credentials"* OR Processes.process=*".ssh/id_rsa"*) by host, Processes.user, Processes.parent_process_name, Processes.parent_process, Processes.process_name, Processes.process | `drop_dm_object_name(Processes)`
```

**Defender KQL:**
```kql
let crawl4ai_lineage = dynamic(["python","python.exe","python3","python3.10","python3.11","python3.12","uvicorn","uvicorn.exe","gunicorn","sh","bash","dash","zsh"]);
let secret_markers = dynamic(["/etc/passwd","/etc/shadow","/proc/self/environ","printenv","169.254.169.254","/latest/meta-data","metadata.google.internal","169.254.170.2",".aws/credentials",".ssh/id_rsa",".kube/config",".docker/config.json"]);
let ProcHits = DeviceProcessEvents
    | where Timestamp > ago(7d)
    | where InitiatingProcessFileName in~ (crawl4ai_lineage)
         or InitiatingProcessParentFileName in~ (crawl4ai_lineage)
    | where InitiatingProcessCommandLine has_any ("crawl4ai","uvicorn","server","11235","app:app")
         or ProcessCommandLine has_any (secret_markers)
    | where ProcessCommandLine has_any (secret_markers)
    | project Timestamp, DeviceName, AccountName,
              ParentImage = InitiatingProcessFolderPath,
              ParentCmd = InitiatingProcessCommandLine,
              ChildImage = FolderPath,
              ChildCmd = ProcessCommandLine;
let NetHits = DeviceNetworkEvents
    | where Timestamp > ago(7d)
    | where RemoteIP in ("169.254.169.254","169.254.170.2","100.100.100.200")
    | where InitiatingProcessFileName in~ (crawl4ai_lineage)
         or InitiatingProcessParentFileName in~ (crawl4ai_lineage)
    | project Timestamp, DeviceName,
              ParentImage = InitiatingProcessFolderPath,
              ParentCmd = InitiatingProcessCommandLine,
              ChildImage = "network",
              ChildCmd = strcat("net:", RemoteIP, ":", tostring(RemotePort));
union ProcHits, NetHits
| order by Timestamp desc
```

### IOC-driven hunts (use shared templates)

These are standard IOC-substitution hunts — the canonical SPL and KQL live once in [`_TEMPLATES.md`](../_TEMPLATES.md), so we don't repeat the same boilerplate on every CVE / hash / network-IOC briefing.

- **Asset exposure — vulnerability matches article CVE(s)** ([template](../_TEMPLATES.md#asset-exposure)) — phase: **recon**, confidence: **High**
  - CVE(s): `CVE-2026-53753`


## Why this matters

Severity classified as **CRIT** based on: CVE present, 4 use case(s) fired, 7 technique(s) inferred. Read the full article for actor attribution, tooling details, and any defanged IOCs in the body that aren't visible in the RSS summary.
