GHSA-g966-83w7-6w38: FrankenPHP's unicode case-folding length expansion causes incorrect split_path index (SCRIPT_NAME/PATH_INFO confusion) in FrankenPHP
SummaryFrankenPHP’s CGI path splitting logic improperly handles Unicode characters during case conversion. The logic computes the split index (for finding `.php`) on a lowercased copy of the request path but applies that byte index to the original path.Because `strings.ToLower()` in Go can increase the byte length of certain UTF-8 characters (e.g., `Ⱥ` expands when lowercased), the computed index may not align with the correct position in the original string. This results in an incorrect `SCRIPT_NAME` and `SCRIPT_FILENAME`, potentially causing FrankenPHP to execute a file other than the one intended by the URI.**Details**The vulnerability resides in the `splitPos()` function and its usage within `splitCgiPath()`. The logic attempts to find the script extension (e.g., `.php`) in a case-insensitive manner by lowercasing the path:The issue is that the returned `idx` represents a byte offset within `lowerPath`. However, `splitCgiPath()` uses this index to slice the **original** `path`:This logic relies on the assumption that `len(strings.ToLower(path)) == len(path)`. This assumption is false for certain Unicode characters. For example, the character `Ⱥ` (U+023A) requires 2 bytes in UTF-8 (`0xC8 0xBA`), but its lowercase equivalent `ⱥ` (U+2C65) requires 3 bytes (`0xE2 0xB1 0xA5`).If the path contains such characters before the `.php` extension, the index calculated on `lowerPath` will be larger than the corresponding visual point in the original `path`. When applied to the original path, the split occurs at the wrong byte offset. This can cause the server to treat a larger portion of the path as the script name, effectively allowing an attacker to manipulate `SCRIPT_FILENAME`.**PoC**The following Go program demonstrates the discrepancy between the byte index in the lowercased string versus the original string.Save the following as `poc.go`:Run the PoC:**Output:**In this example, FrankenPHP would identify `/ȺȺȺȺshell.php.txt` as the PHP script to execute, ignoring the fact that the actual file extension in the file system might be `.txt`.Impact*This is a **Security Boundary Bypass** and **Path Confusion** vulnerability.In setups where users can upload files (e.g., avatars, text files) that are stored within the document root or a reachable path, an attacker can upload a file containing malicious PHP code with a safe extension (e.g., `payload.txt`). By crafting a request with specific Unicode characters, the attacker can force FrankenPHP to calculate the `SCRIPT_FILENAME` as ending in `payload.txt`, while the request appears to contain `.php` to the internal router logic.This results in the execution of non-PHP files as PHP scripts, leading to **Remote Code Execution (RCE)**.**Patched Versions**This issue is fixed in FrankenPHP version **1.11.2**.**Workarounds**Ensure that user-uploaded files are stored outside of the public document root.Implement strict WAF rules to reject requests containing specific multi-byte Unicode characters in the URL path if an upgrade is not immediately possible.
CVSS v4.0: 8.9