CVE-2026-115908 — 脆弱性インテリジェンス
zip-rs — zip
公表日:
- CVSS
- 7.4
- CISA KEV
- 未掲載(悪用がないことを意味しません)
情報源の原文(翻訳ではありません)
zip: Arbitrary file write outside the extraction directory, because ZipArchive::extract validates symlink targets lexically.
### Summary
- **Verified version:** zip `9.0.0-pre3`
- **Affected versions:** `7.2.0` and later
- **Precondition:** The attacker knows (or can guess) the absolute path of the extraction destination
The validation applied to symlinks is purely lexical.
As a result, an attacker can write arbitrary files outside the extraction directory through a symlink.
Exploitation requires the attacker to know (or guess) the absolute path of the destination, but this is realistic in practice: CI workspaces, `/opt/app`, a fixed Downloads path, and so on.
> **Impact extends further on Windows (verified on real hardware for both the separate-drive and UNC-share cases)**:
> Because `simplified_components` discards the `Prefix` component, paths like `D:\<same tail as dest>` or a UNC path `\\attacker\share\<same tail>` pass the `starts_with` check.
> As a result, `outpath.push` gets replaced onto a different drive/remote share, so **a single symlink is enough — no intermediate symlink chain is required**.
> That said, creating a symlink on Windows requires `SeCreateSymbolicLinkPrivilege` (administrator rights or Developer Mode enabled).
> **Real-hardware verification (Windows 10 VM (19045.2604), `tests/poc_win.rs`)**:
> With the extraction target set to a temporary directory under `C:`, both of the following paths returned `Ok(())` from `extract()` and successfully wrote outside the extraction target.
> In both cases `extract()` returned no error (`Ok(())`), and it was confirmed that the contents of `pwned.txt` matched the attacker-specified value.
### Relationship to CVE-2025-29787
This is **not a duplicate** of the existing [CVE-2025-29787](https://github.com/zip-rs/zip2/security/advisories/GHSA-94vh-gphv-8pm8) (2025-03); rather, **the protection introduced for that CVE was subsequently lost**.
#### Timeline (git history, UTC)
| commit | Date | Description |
| ---------- | ---------- | ---------------------------------------------------------------------------------------------------------------- |
| `d4539d6b` | 2025-02-28 | Initial fix for CVE-2025-29787. Canonicalizes output paths and symlink targets and verifies they descend from the destination |
| `0199ac2c` | 2025-03-15 | **Before the v2.3.0 release**, the above is replaced with a lexical check based on `simplified_components` |
| `v2.3.0` | 2025-03-16 | Released as the CVE fix. **What actually shipped is the lexical check** |
| `d5cf4b55` | 2026-01-18 | Changes how `simplified_components` handles absolute paths, **from rejecting them to silently stripping them** (released in `v7.2.0`) |
#### Why v2.3.0 was not affected, and how the protection was lost
The `safe_prepare_path` in v2.3.0 performs essentially the same lexical check as the current code. However, `simplified_components` at that time **rejected any path containing an absolute component**, so an absolute symlink target was never accepted in the first place and this attack path did not work.
```rust
// v2.3.0: reject absolute paths
Component::Prefix(_) | Component::RootDir => return None,
// Current: ignore the fact that the path is absolute
Utf8WindowsComponent::Prefix(_) | Utf8WindowsComponent::RootDir => (),
```
`d5cf4b55` was intended to make archives containing absolute path names extractable, but as a side effect **absolute symlink targets now pass the lexical check**, which silently removed the protection against CVE-2025-29787.
#### Verification
The following three cases were compared using the same build (`9.0.0-pre3`):
| Payload / condition | Result |
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Original PoC for CVE-2025-29787 (relative symlink `parent -> ../`) | **Blocked** with `Err(InvalidArchive("Invalid symlink target path"))` (the CVE fix itself is effective) |
| PoC in this report (two-stage symlink using absolute paths) | **Succeeds** with `Ok(())`, writing outside the destination |
| PoC in this report, with the `d5cf4b55` change reverted to `return None` | **Blocked** with `Err(InvalidArchive("Invalid symlink target path"))` |
The first row shows that the fix for the CVE itself still works, confirming that this report is not a duplicate.
The third row confirms that this vulnerability originates from the change in `d5cf4b55`.
#### Differences from CVE-2025-29787
- **Stricter precondition:** This issue requires the absolute path of the destination to be known (`AC:H`). The CVE worked with a relative `../` and had no such precondition.
- **Narrower API surf