{"id":"CVE-2026-57584","aliases":["GHSA-x7rj-f32v-7jjg"],"url":"https://o3.security/vulnerability/CVE-2026-57584","summary":"Phalcon: Catastrophic backtracking (ReDoS) in the default Phalcon Router route lead to remote unauthenticated DoS","details":"## Summary\n\nEvery Phalcon MVC application built with a default router (`new Phalcon\\Mvc\\Router()` or `new Phalcon\\Mvc\\Router(true)`, which is the normal case) registers a built-in route whose compiled PCRE pattern is `#^/([\\w0-9\\_\\-]+)/([\\w0-9\\.\\_]+)(/.*)*$#u`. The trailing `(/.*)*` is a nested quantifier whose group body (`/.*`) overlaps itself (`.` matches `/`, and there is no `s`/DOTALL flag), so when the final `$` is forced to fail the engine explores roughly `2^(N/2)` ways to split a run of `N` slashes, causing classic catastrophic backtracking. `Phalcon\\Mvc\\Router::handle()` runs on **every** request and matches this pattern against the attacker-controlled request URI, so a single short request can burn seconds-to-minutes of CPU per request. The same `(/.*)*` construct is also produced by the `/:params` placeholder (`Phalcon\\Mvc\\Router\\Route::compilePattern()`) and by the CLI router (`Phalcon\\Cli\\Router` / `Phalcon\\Cli\\Router\\Route`).\n\n\n## Details \n\nThe vulnerable pattern is emitted in four places, all carrying the same `*` nested quantifier:\n\n- Default MVC route registration `phalcon/Mvc/Router.zep` (`Router::__construct()`): `\"#^/([\\\\w0-9\\\\_\\\\-]+)/([\\\\w0-9\\\\.\\\\_]+)(/.*)*$#u\"`, with paths `[\"controller\": 1, \"action\": 2, \"params\": 3]`.\n- `/:params` placeholder expansions `phalcon/Mvc/Router/Route.zep` (`Route::compilePattern()`): `str_replace(\"/:params\", \"(/.*)*\", pattern)`.\n- Default CLI route `phalcon/Cli/Router.zep` (`Router::__construct()`): `\"#^(?::delimiter)?([a-zA-Z0-9\\\\_\\\\-]+):delimiter([a-zA-Z0-9\\\\.\\\\_]+)(:delimiter.*)*$#\"`.\n- CLI `/:params` expansion `phalcon/Cli/Router/Route.zep` (`Route::compilePattern()`): `\"(\" . this->delimiter . \".*)*\"`.\n\n`Router::handle()` matches the request URI against this pattern on every request (the combined-regex fast path and the per-route dynamic loop both call `preg_match()` with it). When the subject string ends in a byte that the group cannot consume (for example a newline, since `.` does not match `\\n`), the anchored `$` cannot be satisfied and the engine backtracks over every partition of the leading run of slashes, which is exponential in the number of slashes.\n\n## Remote reachability\n\nIn the default MVC configuration the router uses `URI_SOURCE_GET_URL`, i.e. it reads the request path from `$_GET[\"_url\"]`, which the web server populates from the rewritten request path. **PHP URL-decodes `$_GET`**, so a request path containing `%0a%0a` arrives as the literal two-byte string `\"\\n\\n\"`. The two newlines are the trigger: `.` cannot match `\\n`, and PCRE's `$` forgives exactly one trailing `\\n`, so two of them force the match to fail and unleash the backtracking. No authentication, cookies, or application-specific routes are needed.\n\nExample malicious request path (≈40 bytes): `/a/a////////////////////////////////%0a%0a` (two short segments, a run of `/`, then `%0a%0a`).\n\nApplications configured with `URI_SOURCE_SERVER_REQUEST_URI` are not reachable through this specific newline trick because `REQUEST_URI` is not URL-decoded; they remain exposed to the underlying CPU amplification when the unmatchable tail can be introduced by other means.\n\n## Proof of Concept\n\n```php\n<?php\n\nuse Phalcon\\Di\\FactoryDefault;\nuse Phalcon\\Mvc\\Router;\n\n$di = new FactoryDefault();\n$router = new Router(true);   // defaultRoutes = true (the default)\n$router->setDI($di);\n\necho \"phalcon            : \" . phpversion(\"phalcon\") . \"\\n\";\necho \"pcre.backtrack_limit: \" . ini_get(\"pcre.backtrack_limit\") . \"\\n\";\necho \"pcre.jit           : \" . ini_get(\"pcre.jit\") . \"\\n\";\n\n// Default configuration\nforeach ($router->getRoutes() as $r) {\n    if (strpos($r->getCompiledPattern(), \"(/.*)*\") !== false) {\n        echo \"vulnerable route   : \" . $r->getCompiledPattern() . \"\\n\";\n    }\n}\necho \"\\n\";\n\nfunction bench(Router $router, string $uri, string $label): void\n{\n    $t0 = hrtime(true);\n    try {\n        $router->handle($uri);\n    } catch (\\Throwable $e) {\n        // matching failure and fallback to time\n    }\n    $ms = (hrtime(true) - $t0) / 1e6;\n    printf(\"  %-22s uri_len=%4d   %10.3f ms\\n\", $label, strlen($uri), $ms);\n}\n\nbench($router, \"/products/edit/123\", \"normal URL\");\necho \"\\n\";\n\n// Malicious: two short segments, then a run of slashes, then \"\\n\\n\" (the decoded %0a%0a).\n$ks = getenv(\"REDOS_KS\") ? array_map(\"intval\", explode(\",\", getenv(\"REDOS_KS\")))\n                         : [14, 18, 22, 26, 30, 34];\nforeach ($ks as $k) {\n    $uri = \"/a/a\" . str_repeat(\"/\", $k) . \"\\n\\n\";\n    bench($router, $uri, \"evil slashes=$k\");\n}\n\necho \"\\nEach +4 slashes multiplies time ~16x (clean 2^N). A ~40-byte URL is sufficient\\n\";\necho \"to pin a CPU core; under default backtrack_limit the per-request cost is a fixed\\n\";\necho \"(but ~10000x-amplified vs a normal route) bail, exhausting workers under volume.\\n\";\n\n\n```\n\nin poc above builds a default `Phalcon\\Mvc\\Router`, confirms the live compiled pattern contains `(/.*)*`, and times `$router->handle($uri)` (the real request path) for crafted URIs of the form `\"/a/a\" . str_repeat(\"/\", k) . \"\\n\\n\"`. Measured against a clean, non-sanitizer build of Phalcon 5.14.2 (PHP 8.3.31 NTS):\n\n```\nphalcon            : 5.14.2\nvulnerable route   : #^/([\\w0-9\\_\\-]+)/([\\w0-9\\.\\_]+)(/.*)*$#u\n\nDEFAULT config (pcre.jit=1, backtrack_limit=1,000,000)\n  normal URL             uri_len=  18        1.182 ms\n  evil slashes=22        uri_len=  28        1.016 ms\n  evil slashes=34        uri_len=  40        1.015 ms   (plateau = backtrack-limit bail)\n\nRAISED backtrack_limit=1e9, pcre.jit=0 (true exponential)\n  evil slashes=18        uri_len=  24        5.555 ms\n  evil slashes=22        uri_len=  28       90.317 ms\n  evil slashes=24        uri_len=  30      356.800 ms\n  evil slashes=26        uri_len=  32     1426.734 ms\n  evil slashes=28        uri_len=  34     5727.099 ms\n```\n\nThe curve is cleanly exponential each four extra slashes multiplies the time by ~16× (`2^(N/2)`). A ~34-byte URL already costs ~5.7 s of CPU; ~40 bytes reaches minutes.\n\n## Impact\n\nTwo regimes, both measured on the real build:\n\n- **Default PHP configuration (JIT on, `pcre.backtrack_limit = 1,000,000`):** each match bails at the backtrack limit after a fixed ~1 ms and `preg_match()` reports failure. This is not a per-request hang, but it is (a) a large CPU amplification per tiny request a few hundred concurrent ~40-byte requests saturate the PHP-FPM worker pool (volumetric DoS), and (b) a correctness bug, because the default route silently fails to match and affected requests mis-route / 404. \n\n- **PCRE JIT disabled, or `pcre.backtrack_limit` raised:** a single ~40-byte request pins a CPU core for seconds to minutes a classic single-packet ReDoS that hangs a worker outright. PCRE JIT is disabled on a number of distributions/builds, and applications with complex routes or large request bodies sometimes raise the backtrack limit, so this is a realistic configuration.","published":"2026-07-10T21:04:26.946Z","modified":"2026-08-12T16:41:34.541738Z","cvss":null,"epss":{"score":0.00524,"percentile":0.43225,"asOf":"2026-09-17"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Packagist","name":"phalcon/cphalcon","fixedVersion":"5.15.0"}],"fix":{"url":"https://github.com/phalcon/cphalcon/commit/14ba22d389d5ca620bb9d5207205f836ef1224f2","label":"phalcon/cphalcon@14ba22d"},"references":[{"type":"WEB","url":"https://github.com/phalcon/cphalcon/releases/tag/v5.15.0"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/57xxx/CVE-2026-57584.json"},{"type":"ADVISORY","url":"https://github.com/phalcon/cphalcon/security/advisories/GHSA-x7rj-f32v-7jjg"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-57584"},{"type":"FIX","url":"https://github.com/phalcon/cphalcon/commit/14ba22d389d5ca620bb9d5207205f836ef1224f2"},{"type":"FIX","url":"https://github.com/phalcon/cphalcon/commit/fa798e919cb2c487062bb9899ad6fc2b673b3a67"},{"type":"PACKAGE","url":"https://github.com/phalcon/cphalcon"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T16:41:34.541738Z"}}