Skip to main content
๐Ÿ“œ WAYPOINT LESSON

Canonicalize, then validate

โญโญโญ advancedโณ 16 min read๐Ÿ“ Lesson 169 of 180

Path traversal via canonical containment, and SSRF via finite allowlists.

Validate the canonical form, never the input string. Path traversal: ..%2F..%2Fetc%2Fpasswd decoded and normalized is /etc/passwd โ€” a filter that rejects .. on the RAW string sees neither the dots nor the slashes.

Path root = baseDir.toAbsolutePath().normalize();
Path target = root.resolve(userPath).normalize();
if (!target.startsWith(root)) throw new SecurityException("traversal");

resolve + normalize produce the canonical path; the containment check then answers the only question that matters: is the result still inside the root? Note what this rejects that string filters miss: docs/../secrets, absolute escapes, encoding tricks (already decoded by the time you see the String), symlinks resolved by the filesystem itself.

The same shape applies to SSRF: never blacklist bad hosts (infinite), allowlist the hosts you mean (finite). URL.openStream() on a user-supplied URL can reach http://169.254.169.254/ (cloud metadata) or http://localhost:8080/ (your own admin) โ€” the fix is resolving the host against an allowlist BEFORE any I/O.