Skip to content

Commit db421e9

Browse files
committed
security: neutralize backslash-smuggled traversal in sanitizedPathJoin
path.Clean is POSIX-only and treats `\` as an ordinary byte, so a reqPath like "..\\..\\windows\\win.ini" survives it untouched as a single opaque segment. filepath.Join then runs with the host's native separator rules, and on Windows that DOES treat `\` as a separator, resolving the now-live ".." against root and escaping it.
1 parent c5031df commit db421e9

1 file changed

Lines changed: 13 additions & 5 deletions

File tree

cgi.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -339,13 +339,21 @@ func sanitizedPathJoin(root, reqPath string) string {
339339
root = "."
340340
}
341341

342-
// reqPath is an HTTP request path: always "/"-separated, regardless of
343-
// host OS. It must be cleaned with the "path" package (POSIX-only),
344-
// not "path/filepath": on Windows, filepath.Clean does not treat a
342+
// reqPath is an HTTP request path: nominally "/"-separated, regardless
343+
// of host OS, but an attacker can smuggle literal "\" bytes in it too
344+
// (e.g. via %5C). Normalize those to "/" before cleaning: filepath.Join
345+
// below runs with the host's native separator semantics, and on
346+
// Windows it treats "\" as a separator, so any ".." hidden behind a
347+
// backslash must already be collapsed here or it survives path.Clean
348+
// (POSIX-only, "\" is just an ordinary byte to it) and escapes root
349+
// once filepath.Join resolves it.
350+
//
351+
// It must be cleaned with the "path" package (POSIX-only), not
352+
// "path/filepath": on Windows, filepath.Clean does not treat a
345353
// driveless "/"-rooted path as absolute, so a leading ".." isn't
346354
// collapsed at the root the way it is on POSIX - it survives into the
347-
// joined path instead, escaping root.
348-
cleanedReqPath := filepath.FromSlash(path.Clean("/" + reqPath))
355+
// joined path instead, also escaping root.
356+
cleanedReqPath := filepath.FromSlash(path.Clean("/" + strings.ReplaceAll(reqPath, `\`, "/")))
349357

350358
joined := filepath.Join(root, cleanedReqPath)
351359

0 commit comments

Comments
 (0)