Downloads · 30 days
0
Draichi/pytorch-pickle-rce-poc
pytorch-pickle-rce-poc is a machine learning model from Draichi. Use it for the machine learning task on the model card, and read the license before you ship it in a product. The card lists the license as mit.
This is an educational security research artifact, not a functional model. It was built as part of hands-on AI Red Teaming coursework (Hack The Box Academy) to demonstrate, end-to-end, a well-known and already publicl…
Downloads · 30 days
0
Access
Public
Updated Jul 20, 2026
Repo size
39 MB
Likes
0
Public
Click a slice to open those files.
.mp438.8 MB · 99%
From the Hugging Face model README
This is an educational security research artifact, not a functional model. It was built as part of hands-on AI Red Teaming coursework (Hack The Box Academy) to demonstrate, end-to-end, a well-known and already publicly documented class of vulnerability: arbitrary code execution via unsafe pickle deserialization of ML checkpoints. Hugging Face's own security docs describe this exact mechanism — see "Pickle Scanning" — and this repo is a hands-on, disclosed reproduction of that documented risk, built for learning and awareness rather than for use against any target.
Video demo: unedited screen recording of loading this exact checkpoint and watching the payload fire.
<video controls width="100%" src="https://huggingface.co/Draichi/pytorch-pickle-rce-poc/resolve/main/rickrolled-poc.mp4"> Your browser does not support the video tag. Download the demo here: <a href="https://huggingface.co/Draichi/pytorch-pickle-rce-poc/resolve/main/rickrolled-poc.mp4">rickrolled-poc.mp4</a> </video>To keep the demonstration unambiguous and safe to publish, the embedded "payload" carries zero harmful capability by design: it opens the default web browser to a YouTube video (Rick Astley's "Never Gonna Give You Up"). No network callback, no filesystem access, no credential access, no persistence, no shell. The vulnerability class demonstrated is real and serious; the payload used to illustrate it deliberately is not.
Who this is for: ML engineers, MLOps/platform teams, and security
practitioners who want to see — concretely and safely — what "just load the
checkpoint" can mean on an untrusted file, so they can make informed decisions
about weights_only, safetensors, and model provenance in their own
pipelines.
torch.load() trigger this automatically?Not by default, and that matters. Since PyTorch 2.6, torch.load()
defaults to weights_only=True, which uses a restricted unpickler and blocks
exactly this class of attack. Loading this file with a modern, unmodified
torch.load(path) will raise an UnpicklingError and refuse to execute the
payload — verified against PyTorch 2.11.
The payload does fire if the checkpoint is loaded via:
torch.load(path, weights_only=False) — often used to "get an old
checkpoint to load" or because the loading code predates PyTorch 2.6.pickle.load(open(path, "rb")) — has never had this restriction and
is still fully unsafe.In other words: the platform-level fix exists and works, but it's opt-out, not
universal. A huge amount of real-world code still explicitly sets
weights_only=False, or uses pickle directly, or predates the change.
Underneath the trap, rickroll_trojan_model.pth wraps a small, legitimately
trained SimpleNet (a couple of nn.Linear layers). It has no real-world use
as a model — its only purpose is to be loaded.
webbrowser.open(url))
is embedded into the least significant bits (LSBs) of one of the model's
weight tensors, 1–2 bits per float32 value. The perturbation is small enough
to be statistically invisible — it does not show up as an anomalous file
size, hash mismatch, or obvious weight distribution shift.__reduce__ hijack. The object actually saved to disk is not a
plain state_dict, it's a wrapper class whose __reduce__ method returns
(exec, (loader_code,)). Python's pickle module calls this automatically
during unsafe deserialization, which means the loader code runs as a side
effect of unpickling, not of any model call.state_dict, extracts the
poisoned tensor, decodes the hidden payload from its LSBs, and executes it
via exec().torch.load("rickroll_trojan_model.pth", weights_only=False)
(or plain pickle.load) is sufficient to trigger execution. No
model.eval(), no inference call, no user click beyond loading the file.This is the exact same mechanism used by real-world malicious model attacks
(e.g., reverse shells, credential stealers, cryptominers hidden in .pth/.pkl
files uploaded to public model hubs) — the only thing swapped out here is the
payload.
Never load an untrusted checkpoint with weights_only=False or plain
pickle.load(). To verify what a .pth/.pkl file actually does first:
fickling
or picklescan to disassemble the
pickle stream and flag dangerous opcodes (GLOBAL, REDUCE, calls to exec,
os.system, subprocess, etc.) without executing anything.safetensors
stores only tensor data, no executable pickle opcodes, and cannot do this by
construction. If you don't control the source of a checkpoint, insist on
.safetensors.weights_only at its default (True) on PyTorch >= 2.6. This
file is a good test case: loading it this way should fail with an
UnpicklingError rather than execute anything.weights_only=False from a trusted-but-unverified source, do it in an
isolated VM/container with no network access and no sensitive credentials
mounted.Public model hubs (including this one) work on trust: anyone can upload a
.pth file, and depending on how it's loaded, that can be equivalent to
running arbitrary code on your machine. weights_only=True closes the default
path, but plenty of real pipelines still opt out of it, and pickle.load()
was never protected. This model is a controlled, disclosed, harmless
demonstration of that gap, so that teams can see the failure mode before they
encounter it with a payload that isn't a YouTube link.
This checkpoint is published openly and with full disclosure as part of
AI Red Teaming study material. The embedded payload is intentionally
non-destructive, makes no network connections, exfiltrates nothing, and leaves
no persistence. It is safe to trigger deliberately (e.g., in a VM or throwaway
environment, explicitly passing weights_only=False) to see the technique in
action, but it should not be loaded that way inside any pipeline, notebook, or
environment holding real credentials or sensitive data — the point is exactly
that you cannot tell from the file itself that this is happening.
Do not strip this model card, rename the file to obscure its origin, or re-upload the payload with anything other than a harmless action. Repurposing this technique against a target without authorization is illegal in most jurisdictions (unauthorized access / computer misuse statutes) — this artifact is for authorized security research, red teaming, and education only.
Built with PyTorch, LSB steganography over float32 weight tensors, and a
pickle.__reduce__ loader. Full notebook walkthrough (training the base
model, encoding the payload, building the malicious wrapper) is available here:
https://github.com/Draichi/htb/blob/main/data_attacks/steganography_rickroll_attack.ipynb