Skip to content

Fix extract path traversal check#355

Open
GaneshVG18 wants to merge 1 commit into
ZJONSSON:masterfrom
GaneshVG18:fix/extract-path-traversal
Open

Fix extract path traversal check#355
GaneshVG18 wants to merge 1 commit into
ZJONSSON:masterfrom
GaneshVG18:fix/extract-path-traversal

Conversation

@GaneshVG18

Copy link
Copy Markdown

Summary

  • Replace the extraction path containment check with path.resolve() and path.relative().
  • Add a regression test for a zip entry that escapes into a sibling directory whose path shares the destination prefix.

Root cause

Extract() used extractPath.indexOf(opts.path) != 0 to decide whether a zip entry stayed inside the destination directory. That is string-prefix matching, so a resolved target such as /tmp/extractEvil/hacked.txt can pass when the intended destination is /tmp/extract.

Fix

The new check resolves the destination path and entry target, computes the relative path from destination to target, and skips entries that resolve outside the destination (.. or absolute relative paths).

Fixes #353.

Verification

  • npx eslint lib/extract.js test/uncompressed.js
  • npx tap test/uncompressed.js --no-coverage --reporter=spec
  • npx tap $(find test -name '*.js' ! -name 'openS3.js' ! -name 'openS3_v3.js' | sort) --no-coverage --reporter=dot
  • git diff --check

Note: full npm test reaches the S3 v3 integration test and fails locally because AWS credentials are not configured.

@12122J

12122J commented May 27, 2026

Copy link
Copy Markdown

The same indexOf check exists in lib/Open/directory.js at line 161, which is the code path for the unzipper.Open.*().extract() API (random-access extraction). That path is not covered by this PR.

Verified independently — both code paths are exploitable with the same PoC:

// Works with both Extract() and Open.file().then(d => d.extract())
const zip = new JSZip();
zip.file('../out-evil/pwned.txt', 'escaped');
// extract to /tmp/out → file appears at /tmp/out-evil/pwned.txt

lib/Open/directory.js:160-163 currently:

const extractPath = path.join(opts.path, entry.path);
if (extractPath.indexOf(opts.path) != 0) {
  return;
}

Needs the same path.relative() fix applied here. Happy to help test if useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] Path Traversal Bypass in extract() via indexOf Prefix String Matching

2 participants