GitHub Advisoryadvisory · Go

GHSA-m675-2p33-xv9g Caddy: Unsafe Unicode Handling in FastCGI splitPos Allows Execution of Non-PHP Files

HIGH Published 2026-05-18 Modified 2026-07-20 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

Weakness · CWE (3)

  • CWE-176 Improper Handling of Unicode Encoding weakness
  • CWE-178 Improper Handling of Case Sensitivity weakness
  • CWE-20 Improper Input Validation weakness

References