Commits
Tarun Sukhani authored 8090330dd9f
fix(security): use component-wise path containment in unzip and FileUpload
Both sites guarded a path with String.startsWith on the canonical path. That
also accepts a sibling whose name merely shares the prefix - "/out/x" passes a
check for "/outEVIL/x" - so a crafted name escapes the intended directory while
satisfying the guard (CWE-22 partial path traversal). Path.startsWith compares
whole name elements, so a sibling no longer matches.
play.libs.Files.unzip (java/zipslip): an entry named "../<outdir>EVIL/x" wrote
outside the target. Separately, the isDirectory branch called mkdir() with no
containment check at all, so a traversing directory entry was never validated;
the check now runs before that branch. This is public API - apps call it on
user-supplied archives.
play.data.FileUpload (java/partial-path-traversal-from-remote): reachable
because FilenameUtils.getName("..") returns "..", so a crafted multipart field
name climbs one level out of the per-request temp folder. Bounded - the temp
folder is "<millis>_<counter>" under uploads/, so an attacker who predicts that
name can only land a file in uploads/ itself, where it also escapes
TempFilePlugin's cleanup. Not arbitrary file write, but the guard was wrong.
Tests: three added to FilesTest covering the prefix-sharing sibling escape, the
previously unguarded traversing directory entry, and that a well-formed archive
still extracts. Verified meaningful by reverting the fix - 2 of 7 go red. Full
ant test passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>