{"id":"CVE-2026-48784","aliases":["GHSA-h5x3-xfc9-m39h"],"url":"https://o3.security/vulnerability/CVE-2026-48784","summary":"Symfony: UrlGenerator Dot-Segment Encoding Skips Every Other Chained `../` or `./` → Generated URL Collapses Off-Route Under RFC 3986 Normalization","details":"### Description\n\n`Symfony\\Component\\Routing\\Generator\\UrlGenerator::doGenerate()` percent-encodes `.` and `..` path segments so that the generated URL still resolves to the originating route after RFC 3986 §5.2.4 dot-segment removal (which strict RFC-3986 consumers — routers, reverse proxies, HTTP clients — perform *before* percent-decoding).\n\nThe encoding was implemented as `strtr($url, ['/../' => '/%2E%2E/', '/./' => '/%2E/'])` plus a trailing-segment fixup. `strtr` advances past the trailing `/` of each match, so the next dot-segment in a chained sequence was left unescaped:\n\n| Input                | Output (before fix)                      | Expected                            |\n| -------------------- | ---------------------------------------- | ----------------------------------- |\n| `/../../../`         | `/%2E%2E/../%2E%2E/`                     | `/%2E%2E/%2E%2E/%2E%2E/`            |\n| `/foo/../../../bar`  | `/foo/%2E%2E/../%2E%2E/bar`              | `/foo/%2E%2E/%2E%2E/%2E%2E/bar`     |\n\nWhen a route exposes a parameter constrained by a permissive requirement (`.+`, `.*`, or similar) that accepts dots and slashes, attacker-controlled chained `..` or `.` segments produce a generated URL that, under strict RFC 3986 normalization, collapses to a different path than the originating route. The Twig `path()` / `url()` helpers and any server-side use of `UrlGenerator` are affected. Same class of route round-trip integrity issue as CVE-2026-45065.\n\nNote: WHATWG-conformant browsers treat `%2E`/`%2E%2E` as dot-segments during URL parsing, so the encoding never protected browser-side traversal. The defense exists for RFC-3986-conformant consumers; restoring it for chained segments closes the gap there.\n\n### Resolution\n\n`UrlGenerator` now matches every `/.` or `/..` dot-segment in a single left-to-right `preg_replace_callback` pass using a lookahead that does not consume the trailing `/`, so adjacent dot-segments are encoded correctly.\n\nThe patches for this issue are available [here](https://github.com/symfony/symfony/commit/4b63c3a3f7af04ecd79c89a594b0b02a01990b1d) for branch 5.4 (and forward-ported to 6.4, 7.4, 8.0 and 8.1).\n\n### Credits\n\nSymfony would like to thank Alex Pott for reporting the issue and Nicolas Grekas for providing the fix.","published":"2026-07-14T19:04:20.058Z","modified":"2026-10-22T02:48:44.962038723Z","cvss":null,"epss":{"score":0.00348,"percentile":0.28337,"asOf":"2026-09-17"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Packagist","name":"symfony/routing","fixedVersion":"5.4.53"},{"ecosystem":"Packagist","name":"symfony/routing","fixedVersion":"6.4.41"},{"ecosystem":"Packagist","name":"symfony/routing","fixedVersion":"7.4.13"},{"ecosystem":"Packagist","name":"symfony/routing","fixedVersion":"8.0.13"},{"ecosystem":"Packagist","name":"symfony/symfony","fixedVersion":"5.4.53"},{"ecosystem":"Packagist","name":"symfony/symfony","fixedVersion":"6.4.41"},{"ecosystem":"Packagist","name":"symfony/symfony","fixedVersion":"7.4.13"},{"ecosystem":"Packagist","name":"symfony/symfony","fixedVersion":"8.0.13"}],"fix":{"url":"https://github.com/symfony/symfony/commit/4b63c3a3f7af04ecd79c89a594b0b02a01990b1d","label":"symfony/symfony@4b63c3a"},"references":[{"type":"WEB","url":"https://github.com/symfony/symfony/releases/tag/v5.4.53"},{"type":"WEB","url":"https://github.com/symfony/symfony/releases/tag/v6.4.41"},{"type":"WEB","url":"https://github.com/symfony/symfony/releases/tag/v7.4.13"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/48xxx/CVE-2026-48784.json"},{"type":"ADVISORY","url":"https://github.com/symfony/symfony/security/advisories/GHSA-h5x3-xfc9-m39h"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-48784"},{"type":"FIX","url":"https://github.com/symfony/symfony/commit/4b63c3a3f7af04ecd79c89a594b0b02a01990b1d"},{"type":"WEB","url":"https://github.com/FriendsOfPHP/security-advisories/blob/master/symfony/routing/CVE-2026-48784.yaml"},{"type":"WEB","url":"https://github.com/FriendsOfPHP/security-advisories/blob/master/symfony/symfony/CVE-2026-48784.yaml"},{"type":"PACKAGE","url":"https://github.com/symfony/symfony"},{"type":"WEB","url":"https://symfony.com/cve-2026-48784"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-22T02:48:44.962038723Z"}}