Downloads · 30 days
0
LWa7ch/poc-modelscan-io-open-filewrite
poc-modelscan-io-open-filewrite is a machine learning model from LWa7ch. Use it for the machine learning task on the model card, and read the license before you ship it in a product.
Proof of concept for the Protect AI / huntr.com Model Format Vulnerability program.
Downloads · 30 days
0
Access
Public
Updated Jul 18, 2026
Repo size
170 B
Likes
0
Public
Click a slice to open those files.
.py7.4 KB · 54%
From the Hugging Face model README
Proof of concept for the Protect AI / huntr.com Model Format Vulnerability program.
Two independent weaknesses combined into an arbitrary file write chain.
ModelScan blocks builtins.open at CRITICAL severity. However, io.open is the identical function object (io.open is open evaluates to True in Python), and the io module is entirely absent from the unsafe_globals blocklist.
Referencing builtins.open as io.open produces the global ('io', 'open') in the pickle stream. This is not matched by the blocklist rule "builtins": ["open"]. ModelScan reports clean despite calling the exact same blocked function.
Pickle protocol 4 includes the NEWOBJ_EX opcode (0x92), which calls cls.new(cls, *args, **kwargs) with full keyword-argument support. This opcode is not covered in any known ModelScan bypass research.
Using NEWOBJ_EX to construct functools.partial with a file= keyword argument allows calling builtins.print(content, file=fileobj) at deserialization time, writing arbitrary content to the opened file.
builtins.print is not in the blocklist. Only eight specific builtins are listed: eval, compile, getattr, apply, exec, open, breakpoint, import.
io.open(path, 'a') -> file_obj
NEWOBJ_EX(
functools.partial,
args=(builtins.print, content),
kwargs={file: file_obj, end: ''}
) -> partial_obj
partial_obj() -> print(content, file=file_obj)
-> writes content to path
Globals extracted by ModelScan:
('io', 'open') not in unsafe_globals
('functools', 'partial') not in unsafe_globals
('builtins', 'print') not in unsafe_globals
ModelScan verdict: No issues found.
Install requirements:
pip install modelscan joblib
Generate the PoC files:
python3 poc_io_open_filewrite.py
This creates poc_filewrite_bypass.pkl and poc_filewrite_bypass.joblib and confirms that the target file is written at deserialization time.
Scan with ModelScan:
modelscan -p poc_filewrite_bypass.pkl
Expected output: No issues found
Writing to ~/.ssh/authorized_keys with an attacker public key gives persistent SSH access. Writing to /etc/cron.d/ (as root) gives scheduled code execution. Writing to ~/.bashrc or ~/.profile gives code execution on the next shell session. Replace WRITE_TARGET and WRITE_CONTENT in the PoC script with the desired path and content.
io.open(path, 'a') -> file_obj [memo 6]
builtins.print -> print_func [memo 9]
functools.partial -> partial_cls [memo 12]
NEWOBJ_EX(partial_cls,
(print_func, content),
{file: file_obj, end: ''}) -> partial_obj
EMPTY_TUPLE
REDUCE(partial_obj, ()) -> print(content, file=file_obj) -> file write
STOP
The blocklist approach has two gaps demonstrated here.
First, it matches function references by module name and attribute name, but does not account for the same function being accessible under multiple module paths. builtins.open and io.open refer to the same underlying C function but only the former is blocked.
Second, the NEWOBJ_EX opcode (protocol 4) is not analyzed for kwargs-based dangerous construction. It enables creating functools.partial with arbitrary keyword arguments including file handles, bypassing the requirement for operator.methodcaller or operator.attrgetter.
Add to unsafe_globals:
"io": ["open"],
"_io": ["open"],
"functools": "*",
Or alternatively, build a whitelist approach rather than a denylist.
Payload writes a benign string to /tmp/ml_filewrite_indicator. Submitted exclusively through the Protect AI / huntr.com bug bounty program.