The lab
here is how portswigger describes it
This lab contains a path traversal vulnerability in the display of product images.
The application blocks input containing path traversal sequences. It then performs a URL-decode of the input before using it.
To solve the lab, retrieve the contents of the /etc/passwd file.
the last lab let us smuggle ../ past a filter by nesting it. this one gives us a different seam to work with. the app checks your input for traversal sequences and then does an extra url decode afterwards, and that ordering is the mistake we exploit.
why an extra decode hands us the bug ?#
servers url decode input as a normal part of handling a request, turning something like %2f back into /. that is routine. the bug here is that the app decodes a second time, after it has already run its traversal check.
think about the order of operations. the app takes your filename, looks at it for ../, and if it is clean it then url decodes it one more time before reading the file. so if we encode our traversal sequence, the security check sees only the harmless encoded text and waves it through, and then the superfluous decode quietly turns that text back into a real ../ right before it gets used. the check and the dangerous use are looking at two different versions of our input, and we live in that gap.
Step 1 - Double encode the traversal sequence#
we hit the same image endpoint, intercept it, and send it to repeater.
the trick is to encode the payload so that it survives the filter and only becomes dangerous after the app's own decode. we double url encode the slashes:
..%252f..%252f..%252fetc/passwd
walking through what each layer does makes it clear. %252f is the url encoding of the text %2f, because the percent sign itself is encoded as %25. so when the server does its normal first decode on the way in, %252f becomes %2f, which is still just encoded text and still contains no literal ../ for the filter to catch. the traversal check looks at ..%2f..%2f..%2fetc/passwd, sees no real slash sequences, and lets it pass. then the app performs its extra superfluous decode, and that second pass turns every %2f into an actual /, rebuilding ../../../etc/passwd at the exact moment the file is about to be read.

so by the time the path is used it is a fully working traversal sequence, even though the filter never saw one.
the response returns the full contents of /etc/passwd.

with this, the lab is solved!
