Instructions to use EnigmaConsultant/huntr-poc-nemo-target-rce with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- NeMo
How to use EnigmaConsultant/huntr-poc-nemo-target-rce with NeMo:
# tag did not correspond to a valid NeMo domain.
- Notebooks
- Google Colab
- Kaggle
YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
NeMo .nemo restore RCE via legacy top-level target key β incomplete fix of CVE-2025-23304
Severity: High (config-injection RCE on model load; bypasses the post-CVE allow-list)
Affected tool: nemo_toolkit 2.7.3 (latest; branch also present on NVIDIA-NeMo/NeMo main). Entry point: nemo.core.ModelPT.restore_from / from_pretrained β SaveRestoreConnector.load_config_and_state_dict β Serialization.from_config_dict.
Category: Model-loader RCE on .nemo (NVIDIA's primary distribution format).
Summary
CVE-2025-23304 was fixed by adding safe_instantiate() / _is_target_allowed() / _validate_config_targets_recursive() that validate the Hydra _target_ key against an allow-list before instantiation. The fix never inspects the legacy target key. from_config_dict has a third, unguarded branch: when a config has top-level target (but no params/_target_) it calls import_class_by_path(config['target']) β an unrestricted __import__ + getattr β then imported_cls(cfg=config), with no allow-list check. Because ModelPT.__init__ injects cfg.target = "<module>.<Class>" into every saved config (and _convert_config deliberately leaves target unconverted), every real .nemo lands in exactly this branch on restore. An attacker swaps the target: line in any .nemo's model_config.yaml to an arbitrary importable path β import-time code exec + arbitrary class construction.
Root cause
- Vulnerable branch:
nemo/core/classes/common.py:605-623βelsepath:imported_cls = import_class_by_path(config['target'])(:614, unrestricted, confirmed atmodel_utils.py:564), thenimported_cls(cfg=config[, trainer])(:621/:623). No allow-list. - Allow-list that misses it:
common.py:90-170(_is_target_allowed/_validate_config_targets_recursive/safe_instantiate) only ever inspects_target_. - Universal reachability:
modelPT.py:128-131injects top-leveltargetinto every saved config;model_utils._convert_configconverts onlyclsβ_target_and explicitly leavestargetunchanged. - Entry point:
save_restore_connector.py:168(OmegaConf.loadof the attacker-controlledmodel_config.yaml) and:191(calling_cls.from_config_dict(config=conf)) β the exact callrestore_frommakes. The hardened tar guard (CVE-2024-0129/CVE-2025-23360) andweights_only=True(CVE-2025-23303) do not touch this config path.
Reproduce
python poc/poc_real.py β uses 100% real NeMo code (only unrelated nv_one_logger telemetry stubbed). Builds a malicious .nemo whose model_config.yaml has target: evil_gadget.Pwn, runs the real guarded extract + OmegaConf.load + from_config_dict. Output (exit 0): arbitrary module imported = True, attacker class constructed = True, attacker file written = True, safe_instantiate() consulted = False, _is_target_allowed('evil_gadget.Pwn') = False (proving it would be blocked as _target_). poc/poc_realworld_gadget.py proves it with real stdlib: target='this.X' fires import-time exec (prints the Zen of Python); target='argparse.Namespace' constructs a real arbitrary class. WebFetch of NVIDIA-NeMo/NeMo main confirms the unguarded if 'target' in config: import_class_by_path(...) branch is still present.
Impact
Loading an untrusted .nemo via the documented restore_from() / from_pretrained() API executes attacker-chosen code with the victim's privileges β no pickle, bypassing the allow-list users now rely on post-CVE-2025-23304. The malicious file is a normal-looking tar whose model_config.yaml simply swaps its target: line (survives casual inspection + current pickle/tar scanners). High blast radius: .nemo is NVIDIA's primary format with 700+ HF models (parakeet/Canary ASR), and restore_from auto-downloads remote checkpoints.
Dup-check
Novel incomplete-fix sibling of CVE-2025-23304 (the _target_ Hydra bug, Unit42; the Unit42 writeup uses _target_ and does not mention the legacy target key, ModelPT injection, import_class_by_path, or the from_config_dict fallback). Other NeMo CVEs are unrelated vectors: CVE-2024-0129/CVE-2025-23360 (tar traversal), CVE-2025-23303 + CVE-2026-24157/24159 (pickle/torch.load deserialization, already patched in 2.7.3). PR #13607 (CVE fix) and PR #14540 (_is_target_allowed hardening) both touched only _target_. gh search for import_class_by_path target / from_config_dict safe_instantiate returned no matching reports. No CVE/GHSA covers the legacy target path.
Scope caveat (honest): shares the instantiation-RCE surface with CVE-2025-23304 and could be triaged as an extension of that fix β but it is a genuinely separate, unguarded branch present on latest +
main.
- Downloads last month
- -