Downloads · 30 days
0
EnigmaConsultant/huntr-poc-pickle-dual-central-directory-desync
huntr-poc-pickle-dual-central-directory-desync is a machine learning model from EnigmaConsultant. 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.
Targets (all current PyPI releases, verified 2026-07-20): picklescan 1.0.5, modelscan 0.8.8, fickling 0.1.12 Real loader: torch.load(..., weightsonly=False) — PyTorch 2.13.0+cpu (caffe2::serialize::PyTorchStreamReader…
Downloads · 30 days
0
Access
Public
Updated Jul 20, 2026
Repo size
4.2 KB
Likes
0
Public
Click a slice to open those files.
.py6.5 KB · 30%
From the Hugging Face model README
data.pkl is read from a DIFFERENT central directory by miniz (torch) than by stdlib zipfile (every scanner), bypassing picklescan 1.0.5, modelscan 0.8.8 and fickling 0.1.12 while torch.load executes itTargets (all current PyPI releases, verified 2026-07-20): picklescan 1.0.5, modelscan 0.8.8, fickling 0.1.12
Real loader: torch.load(..., weights_only=False) — PyTorch 2.13.0+cpu (caffe2::serialize::PyTorchStreamReader / miniz)
Class: CWE-436 Interpretation Conflict — ZIP central-directory location divergence → scanner evasion → RCE on model load
Impact: A .pt/.bin checkpoint that all three scanners certify CLEAN runs attacker os.system(...) the moment a victim runs an ordinary torch.load(weights_only=False).
cd_ofs (forward); stdlib zipfile anchors it at EOCD − size_cd (backward)The PoC is a valid ZIP that contains two physically-separate central directories of equal byte-size S:
[ local file headers + member data ]
[ MAL cd ] @ file offset cd_ofs <- malicious data.pkl record (+ aux records)
[ BEN cd ] @ file offset (EOCD_pos − S) <- benign data.pkl record (+ aux records)
[ EOCD ] cd_ofs = MAL cd , size_cd = S , entries = 6
mz_zip_reader_read_central_dir) parses exactly entries central
records starting at the EOCD cd_ofs field, and only validates cd_ofs + size_cd <= filesize.
It does not require the central directory to end at the EOCD. → it parses MAL cd →
mz_zip_reader_locate_file("archive/data.pkl") → malicious pickle → os.system runs.zipfile (_RealGetContents) computes
concat = EOCD_pos − size_cd − cd_ofs and seeks to start_dir = cd_ofs + concat = EOCD_pos − size_cd,
i.e. it anchors the directory to the size_cd bytes immediately before the EOCD (its
self-extracting-archive / "prepended junk" support). → it parses BEN cd → benign pickle.picklescan (RelaxedZipFile), modelscan and fickling all enumerate members with stdlib
zipfile, so all three scan BEN cd and never see the malicious data.pkl that miniz loads.
verify.py)[struct] entries=6 size_cd=403 cd_ofs=814 eocd_pos=1620
[struct] miniz reads CD at cd_ofs=814; stdlib reads CD at eocd-size_cd=1217 <-- different directories
[stdlib] data.pkl -> BENIGN
[torch.load] RCE marker present: True
| File | torch.load(weights_only=False) | picklescan 1.0.5 | modelscan 0.8.8 | fickling 0.1.12 |
|---|---|---|---|---|
dualcd_mal.pt (mal in miniz cd, benign in stdlib cd) | RCE — marker written | Dangerous globals: 0, exit 0 CLEAN | No issues found 🎉, exit 0 | LIKELY_SAFE |
control_mal.pt (single malicious data.pkl) | RCE | Infected: 1 DETECTED | CRITICAL: 1 (posix.system) | LIKELY_OVERTLY_MALICIOUS |
reverse.pt (benign in miniz cd, malicious in stdlib cd) | benign — no marker | Infected: 1 DETECTED | DETECTED | LIKELY_OVERTLY_MALICIOUS |
The negative control proves the payload is trivially detectable in isolation. The reverse
control swaps which directory holds the malicious record and flips every verdict — direct proof
that miniz resolves data.pkl via cd_ofs while every scanner resolves it via EOCD − size_cd.
# loader side needs torch; scanner side needs picklescan==1.0.5 modelscan==0.8.8 fickling==0.1.12 (Python 3.10-3.12)
python build_poc.py # writes dualcd_mal.pt / .bin, control_mal.pt, reverse.pt (self-contained)
python verify.py # structural proof + torch RCE + all three scanners clean
python -m picklescan -p dualcd_mal.pt # Dangerous globals: 0 -> exit 0 (CLEAN)
modelscan -p dualcd_mal.pt # No issues found 🎉
python -c "from fickling.loader import scan_zip_archive as s; print(s('dualcd_mal.pt'))" # LIKELY_SAFE
Captured runs are in scan_results.txt.
weights_only=False (PyTorch ≥ 2.6 defaults to True, an orthogonal mitigation). This is the same
precondition the entire picklescan/modelscan/fickling threat model assumes, and it remains pervasive
in training-resume, from_pretrained, and legacy-checkpoint call sites.
This is a distinct fourth mechanism, not a variant of the three previously filed:
zipfile.is_zipfile
router on a file that is not really a torch zip. This PoC is a fully valid torch zip every
router agrees on; the divergence is inside central-directory parsing.data.pkl (dupcd) uses one central directory containing two records of the same
name and exploits first-match (miniz) vs last-wins (NameToInfo) resolution. This PoC uses two
separate central directories and needs no duplicate name within a directory; the lever is the
anchor point of the whole directory — cd_ofs (miniz, forward, count-based) vs EOCD − size_cd
(stdlib, backward, concat-based). A scanner hardened to reject duplicate member names (the dupcd fix)
is still fully bypassed here.size_cd are honest for the miniz interpretation, the split
comes purely from the two anchor points, and all three scanners are bypassed end-to-end.Require the central directory to end at the EOCD (cd_ofs + size_cd == EOCD_position) and reject
archives containing more than one central directory / any bytes between the directory and the EOCD, in
both the scanners and PyTorchStreamReader. Equivalently, have scanners resolve members with the same
cd_ofs-anchored walk miniz uses so the scanned bytes equal the loaded bytes.