# Archive extraction path traversal

Archive entry names or symlinks reaching file writes without canonical-path containment

_Area: Web injection and code execution. Seen in 7 disclosed reports; 3 confirmed bounties, median $15,000, range $5,000-$29,000; latest 2024-06-23. Updated 2026-10-23._

## How it works
Extraction code trusts names inside an archive. It joins an entry name (or follows an extracted symlink) to a destination directory and writes, assuming the result stays inside that directory. Entry names with `../`, absolute or drive-relative paths, or symlinks pointing outside break that assumption, so the write (or read) lands anywhere the process can reach.

## Where it shows up
- Entry name concatenated with a base dir: `new File(dir, entry.getName())`, `File.join`, `Pathname#+`, string concat [E3](https://bugbountylibrary.com/r/blog%3Akarmainsecurity-com-zip-slip-meets-artifactory-a-bug-bounty-story.md)[E4](https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2019-04-24-rubyzip-bug-html.md)
- Extraction that keeps symlinks/hardlinks, then globs and opens extracted paths [E1](https://bugbountylibrary.com/r/h1%3A1439593.md)
- Guards gated on caller-supplied destination paths, or run only when dest is nil [E4](https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2019-04-24-rubyzip-bug-html.md)
- Prefix stripping by substring on parsed roots; caches that gate symlink checks [E2](https://bugbountylibrary.com/r/blog%3Arobertchen-cc-blog-2021-09-20-npm-rce.md)
- Custom upload formats (ZIP+XML packages) unpacked server-side [E5](https://bugbountylibrary.com/r/blog%3Aspaceraccoon-dev-a-tale-of-two-formats-exploiting-insecure-xml-and-zip-file-parsers-to-create-a.md)
- Package-manager "dependency rewrite"/proxy features unpacking upstream archives [E3](https://bugbountylibrary.com/r/blog%3Akarmainsecurity-com-zip-slip-meets-artifactory-a-bug-bounty-story.md)
- Framework wrappers with a custom fallback backend alongside a safe native one [E7](https://bugbountylibrary.com/r/h1%3A205481.md)

## How to review
Find every sink consuming an archive entry name or extracted path, and classify it as a read sink (glob/open) or write sink (extract/copy). The invariant: after real-path resolution, the canonical target must be a strict descendant of the intended extraction root, and no symlink/hardlink may be created or followed. Check that validation runs unconditionally (not only when a destination is absent), uses real path resolution rather than substring prefix matching, and that symlinks are stripped before any glob/open. Audit every backend of a wrapper, not just the native one [E7](https://bugbountylibrary.com/r/h1%3A205481.md). Compare against a sibling importer that already does this correctly [E1](https://bugbountylibrary.com/r/h1%3A1439593.md).

## Variants seen
- Symlink-following untar in a bulk import pipeline (read sink) [E1](https://bugbountylibrary.com/r/h1%3A1439593.md)
- Parser-differential prefix stripping and cache desync in node-tar/arborist [E2](https://bugbountylibrary.com/r/blog%3Arobertchen-cc-blog-2021-09-20-npm-rce.md)
- Zip Slip via `../` entry names in a proxy rewrite feature [E3](https://bugbountylibrary.com/r/blog%3Akarmainsecurity-com-zip-slip-meets-artifactory-a-bug-bounty-story.md)
- Library guard bypassed when caller passes a destination path [E4](https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2019-04-24-rubyzip-bug-html.md)
- ZIP+XML upload with unsanitized entry names overwriting a template [E5](https://bugbountylibrary.com/r/blog%3Aspaceraccoon-dev-a-tale-of-two-formats-exploiting-insecure-xml-and-zip-file-parsers-to-create-a.md)
- Traversal in both zip entries and CSV image fields [E6](https://bugbountylibrary.com/r/h1%3A316713.md)
- Framework wrapper whose PclZip fallback omits the containment the native backend performs [E7](https://bugbountylibrary.com/r/h1%3A205481.md)

## Why fixes fail
Guards with preconditions (dest_path nil) leave the common caller path unvalidated [E4](https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2019-04-24-rubyzip-bug-html.md). `Pathname#+` resolves `../` before the check, so validation sees an already-escaped path [E4](https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2019-04-24-rubyzip-bug-html.md). Substring prefix stripping and filesystem-desynced caches skip symlink checks [E2](https://bugbountylibrary.com/r/blog%3Arobertchen-cc-blog-2021-09-20-npm-rce.md). A safe native backend does not protect a custom fallback that omits the same check [E7](https://bugbountylibrary.com/r/h1%3A205481.md).

## Impact and severity
Arbitrary file read of secrets [E1](https://bugbountylibrary.com/r/h1%3A1439593.md), arbitrary file write leading to RCE via cron, webapp deploy or template overwrite [E3](https://bugbountylibrary.com/r/blog%3Akarmainsecurity-com-zip-slip-meets-artifactory-a-bug-bounty-story.md)[E4](https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2019-04-24-rubyzip-bug-html.md)[E5](https://bugbountylibrary.com/r/blog%3Aspaceraccoon-dev-a-tale-of-two-formats-exploiting-insecure-xml-and-zip-file-parsers-to-create-a.md)[E7](https://bugbountylibrary.com/r/h1%3A205481.md). High to critical; worse when extraction runs as root or writes into deploy/web-root paths.

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A1439593.md
- E2: https://bugbountylibrary.com/r/blog%3Arobertchen-cc-blog-2021-09-20-npm-rce.md
- E3: https://bugbountylibrary.com/r/blog%3Akarmainsecurity-com-zip-slip-meets-artifactory-a-bug-bounty-story.md
- E4: https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2019-04-24-rubyzip-bug-html.md
- E5: https://bugbountylibrary.com/r/blog%3Aspaceraccoon-dev-a-tale-of-two-formats-exploiting-insecure-xml-and-zip-file-parsers-to-create-a.md
- E6: https://bugbountylibrary.com/r/h1%3A316713.md
- E7: https://bugbountylibrary.com/r/h1%3A205481.md
