Skip to content

fastcgi: reject paths whose escaped form selects another script - #8357

Merged
francislavoie merged 1 commit into
caddyserver:masterfrom
dunglas:fastcgi-escaped-path-script
Nov 4, 2026
Merged

francislavoie merged 1 commit into
caddyserver:masterfrom
dunglas:fastcgi-escaped-path-script

Conversation

@dunglas

@dunglas dunglas commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Path matchers whose pattern contains % clean the escaped path, where %2f and %2e%2e are neither separators nor dot segments, while buildEnv cleans the decoded one. With @public path /public/%*, /public/tool.php%2f..%2f..%2fadmin%2ftool.php matches the public rule but runs admin/tool.php.

Requests whose escaped and decoded paths select different scripts now get a 400. Two cases of the dot-segment test added in 4ec5a5c3 (..%2f and %2e%2e before the script) moved to the new test, since they now get a 400 instead of running the cleaned script.

On Windows, these matchers read a backslash as %2f. Go escapes a backslash even when RawPath is empty, so %5C..%5C goes through the same check. Backslashes are converted to / before comparing, so /dir\index.php is still accepted. Backslash dot segments, which the existing Windows test expected to resolve, now get a 400 like %2f..%2f. The same Windows fix for FrankenPHP: php/frankenphp@main...fix/windows-escaped-backslash

Port of the FrankenPHP fix: php/frankenphp@393e007

Assistance Disclosure

Claude Code (Claude Opus 5.5) ported this fix from the FrankenPHP patch and wrote the tests. I reviewed and tested the result.

@francislavoie francislavoie added the bug ๐Ÿž Something isn't working label Nov 4, 2026
@francislavoie francislavoie added this to the v2.11.9 milestone Nov 4, 2026
@steadytao

steadytao commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

+ conflicts

Path matchers whose pattern contains "%" clean the escaped path, where
"%2f" and "%2e%2e" are neither separators nor dot segments, while
buildEnv cleans the decoded one. With "@public path /public/%*",
"/public/tool.php%2f..%2f..%2fadmin%2ftool.php" matched the public rule
but ran admin/tool.php.

Requests whose escaped and decoded paths select different scripts now
get a 400.

On Windows those matchers read a backslash as "%2f". Go escapes a
backslash even when RawPath is empty, so "%5C..%5C" needs the same check,
and backslash separators are folded before comparing so that
"/dir\index.php" is still accepted. Backslash dot segments, which the
Windows test expected to resolve, now get a 400 like "%2f..%2f".

Backport of the FrankenPHP fix reported by Daybreak Red.
@dunglas
dunglas force-pushed the fastcgi-escaped-path-script branch from 316983a to ab50798 Compare November 4, 2026 20:29
@steadytao

steadytao commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

Thank you for both PRs!

@dunglas

dunglas commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator Author

Thank you for your prompt review, and to FrankenPHP commiters for having reviewed the inital fix :)

@francislavoie
francislavoie merged commit 3d00544 into caddyserver:master Nov 4, 2026
27 checks passed
francislavoie pushed a commit that referenced this pull request Nov 5, 2026
@github-actions github-actions Bot mentioned this pull request Nov 5, 2026
5 tasks
dunglas added a commit to php/frankenphp that referenced this pull request Nov 5, 2026
**Security fix (Windows only).** Follow-up to
393e007.

On Windows, Caddy path matchers whose pattern contains `%` read a
backslash as `%2f`, which they do not resolve as a separator. Go escapes
a backslash even when `RawPath` is empty, so
`escapedPathSelectsAnotherScript` returned early for
`/public/tool.php%5C..%5C..%5Cadmin%5Ctool.php`: a `/public/%*` rule
matched it, but `admin/tool.php` ran.

The check now also runs when the path contains a backslash on Windows.
Backslashes are folded into separators before comparing, which also
fixes a regression: legitimate paths such as `/dir\index.php` were
rejected with a 400.

Found while porting the fix to Caddy:
caddyserver/caddy#8357

## Assistance Disclosure

Claude Code (Claude Opus 5.5) found the issue while porting the fix to
Caddy, and wrote the patch and tests. I reviewed and tested the result.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug ๐Ÿž Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants