GitHub Advisoryadvisory · Go
GHSA-m675-2p33-xv9g Caddy: Unsafe Unicode Handling in FastCGI splitPos Allows Execution of Non-PHP Files
HIGH
Official page ↗
- Aliases
- CVE-2026-45135
- Ecosystem
- Go
- Packages
- github.com/caddyserver/caddy/v2
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
- Weaknesses
- CWE-176, CWE-178, CWE-20
- Reviewed
- 2026-05-18
Affected packages
• Go github.com/caddyserver/caddy/v2: >= 2.7.0, < 2.11.3 → fixed in 2.11.3
Description
Summary
The FastCGI transport's `splitPos()` in `modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go` misuses `golang.org/x/text/search` with `search.IgnoreCase` when the request path contains a non-ASCII byte. Two distinct flaws in that fallback let an attacker mislead Caddy's FastCGI splitting into treating a non-`.php` (or other configured `split_path` extension) file as a script. In any deployment where the attacker can place content into a file served via FastCGI (uploads, file storage, etc.), this can be escalated to remote code execution by crafting a URL whose path triggers either flaw.
This function was adapted from FrankenPHP's code (see the source comment) and inherits the same bugs. Both were originally reported against FrankenPHP by @KC1zs4 as GHSA-3g8v-8r37-cgjm (which absorbed the duplicate GHSA-v4h7-cj44-8fc8). Credit for finding the underlying flaws belongs to @KC1zs4.
Details
```go
var splitSearchNonASCII = search.New(language.Und, search.IgnoreCase)
func (t Transport) splitPos(path string) int {
if len(t.SplitPath) == 0 {
return 0
}
pathLen := len(path)
for _, split := range t.SplitPath {
splitLen := len(split)
for i := range pathLen {
if path[i] >= utf8.RuneSelf {
if _, end := splitSearchNonASCII.IndexString(path, split); end > -1 {
return end
}
break
}
if i+splitLen > pathLen {
continue
}
match := true
for j := range splitLen {
c := path[i+j]
if c >= utf8.RuneSelf {
if _, end := splitSearchNonASCII.IndexString(path, split); end > -1 {
return end
}
break // = utf8.RuneSelf` and `splitSearchNonASCII.IndexString(...)` returns `-1`, the loop `break`s without setting `match = false`. The outer code then evaluates `if match { return i + splitLen }` with `match` still `true`, returning a position as if the configured extension had been matched. The script-name suffix actually present at that offset is whatever bytes the attacker chose, so a file named `name..txt` gets routed as PHP.
Flaw 2 — Unicode equivalence: `search.IgnoreCase` folds non-ASCII lookalikes onto ASCII
`search.New(language.Und, search.IgnoreCase)` performs Unicode equivalence matching (compatibility decomposition + case folding), which goes far beyond the ASCII-only case folding the surrounding code is built for. Many code points fold onto ASCII `.`, `p`, `h`, `p`, so a path containing `﹒php`, `.php`, `.php`, `.ⓟⓗⓟ`, `.𝗽𝗵𝗽`, `.𝓅𝒽𝓅`, `.𝖕𝖍𝖕`, etc. is reported as `.php`.
Both flaws share the same root cause: invoking `search.IgnoreCase` to match an ASCII-only, validated-lower-case `SplitPath` entry against an arbitrary path. `Provision()` already guarantees every entry is ASCII and lower-cased, so any byte `>= utf8.RuneSelf` in the path can never be part of a legitimate match — but the fallback ignored that guarantee.
PoC
Run against a Caddy build serving FastCGI to PHP-FPM (or any FastCGI app where script lookup is gated by `split_path`). Caddyfile:
```text
:8080 {
root * /app/public
php_fastcgi unix//run/php/php-fpm.sock
}
```
Place attacker-controlled files in `/app/public`:
• `/app/public/poc-match-unset.\xc2\xa1.` — `= utf8.RuneSelf` in the path as a non-match. `SplitPath` entries are validated ASCII-only and lower-cased upstream, so this preserves correct behavior for every legitimate path while making the Unicode bypasses unrepresentable. The replacement is a tight byte loop with no library calls in the hot path. See `fix/fastcgi-splitpos-unicode-bypass` (commit `4ddad83c`) for the implementation and regression tests.
Credit
Both flaws were originally found and reported by @KC1zs4 against FrankenPHP, where the offending `splitPos()` function was first introduced before being adapted into this module. The Caddy maintainers thank @KC1zs4 for the high-quality reports.
Related
Alias · CVE (1)
- CVE-2026-45135 Caddy is an extensible server platform that uses TLS by default. vulnerability
References
- https://github.com/caddyserver/caddy/security/advisories/GHSA-m675-2p33-xv9g web
- https://github.com/php/frankenphp/security/advisories/GHSA-3g8v-8r37-cgjm web
- https://github.com/php/frankenphp/security/advisories/GHSA-g966-83w7-6w38 web
- https://nvd.nist.gov/vuln/detail/CVE-2026-45135 advisory
- https://github.com/caddyserver/caddy package