Skip to content

files

Which file does this path name?

Joining, normalising, and the order those two happen in.

A path is a string about a string, and every traversal defect is a disagreement between two of them: the path a check looked at, and the path a read actually used. When those are the same string there is nothing to exploit; the entire class is the gap between them.

This build serves files from a per-customer directory. Ask it for one and watch the resolver in the transcript — it reports what it was given, what that resolved to, and whether the answer was still inside the root.

  • GET /files/README.txt

    An ordinary read. Look at the resolved path beside the requested one.

  • GET /files/../../etc/passwd

    The same resolver, given something that climbs. Both builds resolve it identically — only one of them then reads it.

So the fix is an order rather than a filter: resolve, then check. The version everybody writes first is the other way round — reject anything containing .., then resolve — and it looks more careful, because it is doing the checking earlier.

It loses to anything that becomes a .. later. A percent-encoded one, a doubly encoded one, a backslash on a system that treats those as separators. The check passed because at the moment it ran, there really was no .. in the string.

  • GET /files/%252e%252e%252f%252e%252e%252fetc%252fpasswd

    No `..` anywhere in what the check sees. Follow the requested path through to the resolved one.

This build

docs.example.com

Nothing sent yet. Every request goes to the application in this tab and nowhere else.