This repository contains a proof-of-concept for an unfixed path-traversal vulnerability in ONNX 1.21.0 (latest official release at time of submission).
Summary
onnx.checker and onnx.load_external_data_for_model skip all security checks (verify_path_containment, is_regular_file, hard_link_count > 1) whenever the resolved external-data path starts with '#'. That prefix is meant as a marker for in-memory tensors, but the check is performed on the full resolved path (base_dir / location) instead of on the original location string. When base_dir is empty — which is exactly what onnx.load("model.onnx") produces when called with a bare filename — the resolved path equals the attacker-controlled location. A location starting with # therefore bypasses every check.
The result is an arbitrary file read via a symlink whose name starts with #, on the current latest release.
Versions
Tested vulnerable: onnx==1.21.0 (latest at submission)
Bypasses the fix for CVE-2026-27489 (the fix introduced the data_path_str[0] != '#' check that this PoC exploits).
Files
File
Description
poc_bundle.tar.gz
Self-contained tarball with model.onnx, a symlink #hidden_link → secret_dir, and secret_dir/passwd with a distinctive marker. The tarball is used because git/HF do not always preserve symlinks.
model.onnx
Same model as inside the tarball (for inspection). External-data location = "#hidden_link/passwd".
reproduce.py
Automated reproducer. Extracts the tarball into a temp directory and demonstrates the leak.
setup.sh / setup.ps1
Manual symlink creator if you prefer not to use the tarball.
Reproduction (one-liner)
bash
1pip installonnx==1.21.0
2python reproduce.py
Expected output:
[+] extracted poc_bundle.tar.gz into /tmp/onnx_poc_xxxxxx
[!!!] LEAK: onnx.load() returned 16 bytes from the symlink target:
b'TOPSECRET_DATA_L'
The 16 bytes are read from secret_dir/passwd via the symlink #hidden_link that the malicious model.onnx references as its external-data location. In a real attack the symlink would target /etc/passwd, ~/.ssh/id_rsa, an AWS credentials file, etc.
Attacker bundles a model release as a zip/tarball containing model.onnx (with external_data.location = "#hidden_link/<target>") and a symlink #hidden_link → /etc (or ~/.ssh, or /proc/self/environ, etc.).
Victim downloads the release, extracts it (preserving the symlink), and runs onnx.load("model.onnx") from the extracted directory — a typical first line in any ONNX consumer script.
ONNX reads the symlink target's contents into model.graph.initializer[0].raw_data without any of the safety checks the v1.21.0 fix was supposed to provide.