Loading...
Skip to main content

CVE-2026-27590 — Vulnerability in github.com/caddyserver/caddy/v2...

HIGH

Caddy: Unicode case-folding length expansion causes incorrect split_path index in FastCGI transport

Published February 24, 2026Updated February 27, 2026Source: osv

Summary

### Summary Caddy's FastCGI path splitting logic computes the split index on a lowercased copy of the request path and then uses that byte index to slice the original path. This is unsafe for Unicode because `strings.ToLower()` can change UTF-8 byte length for some characters. As a result, Caddy can derive an incorrect `SCRIPT_NAME`/`SCRIPT_FILENAME` and `PATH_INFO`, potentially causing a request that contains `.php` to execute a different on-disk file than intended (path confusion). In setups where an attacker can control file contents (e.g., upload features), this can lead to unintended PHP execution of non-.php files (potential RCE depending on deployment). ### Details The issue is in `github.com/caddyserver/caddy/modules/caddyhttp/fastcgi.Trasnport.splitPos()` (and the subsequent slicing in `buildEnv()`): ``` lowerPath := strings.ToLower(path) idx := strings.Index(lowerPath, strings.ToLower(split)) return idx + len(split) ``` The returned index is computed in the byte space of lowerPath, but `buildEnv()` applies it to the original path: - `docURI = path[:splitPos]` - `pathInfo = path[splitPos:]` - `scriptName = strings.TrimSuffix(path, fc.pathInfo)` - `scriptFilename = caddyhttp.SanitizedPathJoin(fc.documentRoot, fc.scriptName)` This assumes `lowerPath` and `path` have identical byte lengths and identical byte offsets, which is not true for some Unicode case mappings. Certain characters expand when lowercased (UTF-8 byte length increases), shifting the computed index. This creates a mismatch where `.php` is found in the lowercased string at an offset that does not correspond to the same position in the original string, causing the split point to land later/earlier than intended. ### PoC Create a small Go program that reproduces Caddy's `splitPos()` behavior (compute the `.php` split point on a lowercased path, then use that byte index on the original path): 1. Save this as `poc.go`: ```go package main import ( "fmt" "strings" ) func splitPos(path string, split string) int { lowerPath := strings.ToLower(path) idx := strings.Index(lowerPath, strings.ToLower(split)) if idx < 0 { return -1 } return idx + len(split) } func main() { // U+023A: Ⱥ (UTF-8: C8 BA). Lowercase is ⱥ (UTF-8: E2 B1 A5), longer in bytes. path := "/ȺȺȺȺshell.php.txt.php" split := ".php" pos := splitPos(path, split) fmt.Printf("orig bytes=%d\n", len(path)) fmt.Printf("lower bytes=%d\n", len(strings.ToLower(path))) fmt.Printf("splitPos=%d\n", pos) fmt.Printf("orig[:pos]=%q\n", path[:pos]) fmt.Printf("orig[pos:]=%q\n", path[pos:]) // Expected split: right after the first ".php" in the original string want := strings.Index(path, split) + len(split) fmt.Printf("expected splitPos=%d\n", want) fmt.Printf("expected orig[:]=%q\n", path[:want]) } ``` 2. Run it: ```console go run poc.go ``` Output on my side: ``` orig bytes=26 lower bytes=30 splitPos=22 orig[:pos]="/ȺȺȺȺshell.php.txt" orig[pos:]=".php" expected splitPos=18 expected orig[:]="/ȺȺȺȺshell.php" ``` Expected split is right after the first `.php` (`/ȺȺȺȺshell.php`). Instead, the computed split lands later and cuts the original path after `shell.php.txt`, leaving `.php` as the remainder. ### Impact Security boundary bypass/path confusion in script resolution. In typical deployments, `.php` extension boundaries are relied on to decide what is executed by PHP. This bug can cause Caddy/FPM to execute a different file than intended by confusing `SCRIPT_NAME`/`SCRIPT_FILENAME`. If an attacker can place attacker-controlled content into a file that can be resolved as `SCRIPT_FILENAME` (common in web apps with uploads or writable directories), this can lead to unintended PHP execution of non-.php files and potentially remote code execution. Severity depends on deployment and presence of attacker-controlled file writes, but the primitive itself is remotely triggerable via crafted URLs. This vulnerability was initially reported to FrankenPHP (https://github.com/php/frankenphp/security/advisories/GHSA-g966-83w7-6w38) by @AbdrrahimDahmani. The affected code has been copied/adapted from Caddy, which, according to research, is also affected. The patch is a port of the FrankenPHP patch.

Advisory Timeline

  • First published February 24, 2026 (230 days ago).
  • Last revised February 27, 2026 — 3 days after initial publication.

Remediation

Upgrade to the fixed version using your package manager.

Go
Update github.com/caddyserver/caddy/v2 to 2.11.1 or later
go get github.com/caddyserver/caddy/v2@v2.11.1

After upgrading, run your dependency scanner again to confirm the vulnerability is resolved.

Affected Packages (1)

Affects 1 ecosystem: go (1).

PackageEcosystemAffectedFixed In
github.com/caddyserver/caddy/v2
go
All versions2.11.1

Vulnerability Classification

Common Weakness Enumeration (CWE) identifiers for this vulnerability type.

  • CWE-20
    Improper Input ValidationMITRE

CVSS Score Breakdown

What the CVSS (Common Vulnerability Scoring System) 9.8 score means for each attack dimension.

Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
—
Confidentiality
—
Integrity
—
Availability
—

CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P

Frequently Asked Questions

What is CVE-2026-27590?
Caddy: Unicode case-folding length expansion causes incorrect split_path index in FastCGI transport This vulnerability has been assigned a severity rating of HIGH (CVSS score: 9.8/10).
How do I check if my project is affected by CVE-2026-27590?
CVE-2026-27590 affects github.com/caddyserver/caddy/v2. Use GeekWala's free vulnerability scanner to check your dependencies against CVE-2026-27590 and 200,000+ other known vulnerabilities.

Severity & Exploitability

CVSS Score
9.8

Exploitation is straightforward and causes maximum impact. Patch immediately.

Also Known As

GHSA-5r3v-vc8m-m96g
GO-2026-4536

Related CVEs

  • CVE-2018-21246
    CRITICAL

    Caddy vulnerable to Authentication Bypass due to mishandling of TLS client authentication

  • CVE-2026-107809
    HIGH

    Nginx-UI AuthRequired token cookie fallback enables CSRF against management APIs

  • CVE-2026-52845
    HIGH

    Caddy: FastCGI header normalization bypass in `forward_auth copy_headers`

  • CVE-2026-107813
    HIGH

    Nginx UI: Incomplete fix of CVE-2026-84315 - the api/cluster router was not - wrapped in RequireSecureSession, so those sensitive mutations run without OTP step-up

  • CVE-2026-27586
    HIGH

    Caddy: mTLS client authentication silently fails open when CA certificate file is missing or malformed

Check if you're affected

Scan your dependencies to see if this vulnerability affects your projects.

Scan Your Dependencies