CVE-2026-47347
Description
Applications that use GeneralUtility::sanitizeLocalUrl to allow only local URLs are vulnerable to open redirect attacks if the URL is used after it has passed the aforementioned sanitization checks. This enables attackers to redirect users to external content and carry out phishing attacks. This issue affects TYPO3 CMS versions before 10.4.57, 11.0.0-11.5.50, 12.0.0-12.4.45, 13.0.0-13.4.30 and 14.0.0-14.3.2.
Affected products
2Patches
23ffc0835012c[SECURITY] Fix open redirection in GeneralUtility::sanitizeLocalUrl
2 files changed · +60 −8
typo3/sysext/core/Classes/Utility/GeneralUtility.php+22 −2 modified@@ -2127,8 +2127,28 @@ public static function sanitizeLocalUrl(string $url, ServerRequestInterface $req { $sanitizedUrl = ''; if (!empty($url)) { - if (strpbrk($url, "\n\r\x00") !== false) { - static::getLogger()->notice('URL "{url}" contains unexpected whitespace and was denied as local url.', ['url' => $url]); + $validUrlCharacters = [ + // Percent-Encoding: https://datatracker.ietf.org/doc/html/rfc3986#section-2.1 + '%', + + // Reserved Characters: https://datatracker.ietf.org/doc/html/rfc3986#section-2.2 + // gen-delims + ':', '/', '?', '#', '[', ']', '@', + // sub-delims + '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', + + // Unreserved Characters: https://datatracker.ietf.org/doc/html/rfc3986#section-2.3 + '-', '.', '_', '~', + // ALPHA + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + // DIGIT + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + ]; + + $hasInvalidCharacters = str_replace($validUrlCharacters, '', $url) !== ''; + if ($hasInvalidCharacters) { + static::getLogger()->notice('The URL "{url}" contains unexpected characters and was denied as local url.', ['url' => $url]); return ''; }
typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php+38 −6 modified@@ -1365,18 +1365,28 @@ public static function sanitizeLocalUrlValidUrlsDataProvider(): array 'localhost', '/cms/', ], - '/cms/typo3/alt_intro.php¶m=oneparam' => [ - '/cms/typo3/alt_intro.php¶m=oneparam', + '/cms/foo/%3Fbar?baz' => [ + '/cms/foo/%3Fbar?baz', 'localhost', '/cms/', ], - '/cms/typo3/alt_intro.php¶m=oneparam with spaces' => [ - '/cms/typo3/alt_intro.php¶m=oneparam with spaces', + '/cms/typo3/alt_intro.php?param=oneparam' => [ + '/cms/typo3/alt_intro.php?param=oneparam', 'localhost', '/cms/', ], - '/cms/typo3/alt_intro.php¶m=oneparam with spaces&normalparam=2' => [ - '/cms/typo3/alt_intro.php¶m=oneparam with spaces', + '/cms/typo3/alt_intro.php?param=oneparam+with+spaces' => [ + '/cms/typo3/alt_intro.php?param=oneparam+with+spaces', + 'localhost', + '/cms/', + ], + '/cms/typo3/alt_intro.php?param=oneparam%20with%20spaces' => [ + '/cms/typo3/alt_intro.php?param=oneparam%20with%20spaces', + 'localhost', + '/cms/', + ], + '/cms/typo3/alt_intro.php?param=oneparam%20with%20spaces&normalparam=2' => [ + '/cms/typo3/alt_intro.php?param=oneparam%20with%20spaces', 'localhost', '/cms/', ], @@ -1440,7 +1450,28 @@ public static function sanitizeLocalUrlInvalidDataProvider(): array 'empty string' => [''], 'http domain' => ['http://www.google.de/'], 'https domain' => ['https://www.google.de/'], + 'https domain with' => ['https://www.google.de/'], + 'https domain with escape at start' => ['https:\\//www.google.de'], + 'https domain with escape in between' => ['https:/\\/www.google.de'], + 'https domain with escape after' => ['https://\\www.google.de'], + 'https domain with escape instead of slash' => ['https:/\\www.google.de'], + 'https domain with double backslash' => ['https:\\\\www.google.de'], + 'https domain with double backslash and one slash' => ['https:\\/www.google.de'], + 'https domain with quad slash' => ['https:////www.google.de'], + 'https domain with newline' => ["htt\nps://www.google.de"], 'domain without schema' => ['//www.google.de/'], + 'domain without schema escape at start' => ['\\//www.google.de', true], + 'domain without schema escape in between' => ['/\\/www.google.de', true], + 'domain without schema escape after' => ['//\\www.google.de', true], + 'domain without schema escape instead of slash' => ['/\\www.google.de', true], + 'domain without schema with double backslash' => ['\\\\www.google.de', true], + 'domain without schema with double backslash and one slash' => ['\\/www.google.de', true], + 'domain without schema with quad slash' => ['////www.google.de'], + 'domain without schema with newline' => ["/\n/www.google.de", true], + 'domain without schema with EOT' => ["\x04//google.de", true], + 'domain without schema with bell' => ["\x07//google.de", true], + 'domain without schema with backspace' => ["\x08//google.de", true], + 'domain without schema with form feed' => ["\x0c//google.de", true], 'XSS attempt' => ['" onmouseover="alert(123)"'], 'invalid URL, UNC path' => ['\\\\foo\\bar\\'], 'invalid URL, HTML break out attempt' => ['" >blabuubb'], @@ -1450,6 +1481,7 @@ public static function sanitizeLocalUrlInvalidDataProvider(): array 'relative URL with location header injection attempt (not known to work) via vertical white space' => ["\v" . '//evil.site/'], 'HTTP header smuggling attempt' => ["/\r\nX-Injected: evil", true], 'null-byte break out attempt' => ["http\x00://www.google.de"], + 'path invalid because it contains unencoded spaces' => ['/cms/typo3/alt_intro.php¶m=oneparam with spaces', true], ]; }
22c2dd5398eb[SECURITY] Fix open redirection in GeneralUtility::sanitizeLocalUrl
2 files changed · +60 −8
typo3/sysext/core/Classes/Utility/GeneralUtility.php+22 −2 modified@@ -2595,8 +2595,28 @@ public static function sanitizeLocalUrl(string $url): string { $sanitizedUrl = ''; if (!empty($url)) { - if (strpbrk($url, "\n\r\x00") !== false) { - static::getLogger()->notice('URL "{url}" contains unexpected whitespace and was denied as local url.', ['url' => $url]); + $validUrlCharacters = [ + // Percent-Encoding: https://datatracker.ietf.org/doc/html/rfc3986#section-2.1 + '%', + + // Reserved Characters: https://datatracker.ietf.org/doc/html/rfc3986#section-2.2 + // gen-delims + ':', '/', '?', '#', '[', ']', '@', + // sub-delims + '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', + + // Unreserved Characters: https://datatracker.ietf.org/doc/html/rfc3986#section-2.3 + '-', '.', '_', '~', + // ALPHA + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + // DIGIT + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + ]; + + $hasInvalidCharacters = str_replace($validUrlCharacters, '', $url) !== ''; + if ($hasInvalidCharacters) { + static::getLogger()->notice('The URL "{url}" contains unexpected characters and was denied as local url.', ['url' => $url]); return ''; }
typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php+38 −6 modified@@ -1384,18 +1384,28 @@ public static function sanitizeLocalUrlValidUrlsDataProvider(): array 'localhost', '/cms/', ], - '/cms/typo3/alt_intro.php¶m=oneparam' => [ - '/cms/typo3/alt_intro.php¶m=oneparam', + '/cms/foo/%3Fbar?baz' => [ + '/cms/foo/%3Fbar?baz', 'localhost', '/cms/', ], - '/cms/typo3/alt_intro.php¶m=oneparam with spaces' => [ - '/cms/typo3/alt_intro.php¶m=oneparam with spaces', + '/cms/typo3/alt_intro.php?param=oneparam' => [ + '/cms/typo3/alt_intro.php?param=oneparam', 'localhost', '/cms/', ], - '/cms/typo3/alt_intro.php¶m=oneparam with spaces&normalparam=2' => [ - '/cms/typo3/alt_intro.php¶m=oneparam with spaces', + '/cms/typo3/alt_intro.php?param=oneparam+with+spaces' => [ + '/cms/typo3/alt_intro.php?param=oneparam+with+spaces', + 'localhost', + '/cms/', + ], + '/cms/typo3/alt_intro.php?param=oneparam%20with%20spaces' => [ + '/cms/typo3/alt_intro.php?param=oneparam%20with%20spaces', + 'localhost', + '/cms/', + ], + '/cms/typo3/alt_intro.php?param=oneparam%20with%20spaces&normalparam=2' => [ + '/cms/typo3/alt_intro.php?param=oneparam%20with%20spaces', 'localhost', '/cms/', ], @@ -1453,7 +1463,28 @@ public static function sanitizeLocalUrlInvalidDataProvider(): array 'empty string' => [''], 'http domain' => ['http://www.google.de/'], 'https domain' => ['https://www.google.de/'], + 'https domain with' => ['https://www.google.de/'], + 'https domain with escape at start' => ['https:\\//www.google.de'], + 'https domain with escape in between' => ['https:/\\/www.google.de'], + 'https domain with escape after' => ['https://\\www.google.de'], + 'https domain with escape instead of slash' => ['https:/\\www.google.de'], + 'https domain with double backslash' => ['https:\\\\www.google.de'], + 'https domain with double backslash and one slash' => ['https:\\/www.google.de'], + 'https domain with quad slash' => ['https:////www.google.de'], + 'https domain with newline' => ["htt\nps://www.google.de"], 'domain without schema' => ['//www.google.de/'], + 'domain without schema escape at start' => ['\\//www.google.de', true], + 'domain without schema escape in between' => ['/\\/www.google.de', true], + 'domain without schema escape after' => ['//\\www.google.de', true], + 'domain without schema escape instead of slash' => ['/\\www.google.de', true], + 'domain without schema with double backslash' => ['\\\\www.google.de', true], + 'domain without schema with double backslash and one slash' => ['\\/www.google.de', true], + 'domain without schema with quad slash' => ['////www.google.de'], + 'domain without schema with newline' => ["/\n/www.google.de", true], + 'domain without schema with EOT' => ["\x04//google.de", true], + 'domain without schema with bell' => ["\x07//google.de", true], + 'domain without schema with backspace' => ["\x08//google.de", true], + 'domain without schema with form feed' => ["\x0c//google.de", true], 'XSS attempt' => ['" onmouseover="alert(123)"'], 'invalid URL, UNC path' => ['\\\\foo\\bar\\'], 'invalid URL, HTML break out attempt' => ['" >blabuubb'], @@ -1463,6 +1494,7 @@ public static function sanitizeLocalUrlInvalidDataProvider(): array 'relative URL with location header injection attempt (not known to work) via vertical white space' => ["\v" . '//evil.site/'], 'HTTP header smuggling attempt' => ["/\r\nX-Injected: evil", true], 'null-byte break out attempt' => ["http\x00://www.google.de"], + 'path invalid because it contains unencoded spaces' => ['/cms/typo3/alt_intro.php¶m=oneparam with spaces', true], ]; }
Vulnerability mechanics
Root cause
"The sanitization function GeneralUtility::sanitizeLocalUrl did not correctly validate all allowed characters in URLs."
Attack vector
An attacker can craft a URL that appears to be a local URL but contains characters that allow it to be interpreted as an external URL. This crafted URL can then be used to redirect users to malicious external content, facilitating phishing attacks. The vulnerability is triggered when the application uses the output of GeneralUtility::sanitizeLocalUrl without further validation [ref_id=1].
Affected code
The vulnerability resides in the `GeneralUtility::sanitizeLocalUrl` function within the TYPO3 CMS core. The function was intended to filter URLs to only allow local resources but failed to account for certain character sequences that could bypass its checks.
What the fix does
The patch modifies the `GeneralUtility::sanitizeLocalUrl` function to more strictly validate the characters allowed within a URL. Previously, it only checked for newline, carriage return, and null bytes. The updated function now checks against a defined set of valid URL characters, including percent-encoded characters, reserved characters, and unreserved characters, preventing the injection of malicious sequences that could lead to open redirects [ref_id=1, patch_id=5349029].
Preconditions
- inputThe attacker must be able to provide a crafted URL to the application.
Generated on Jun 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
1- TYPO3 CMS: Thirteen Backend Vulnerabilities Disclosed on June 9, 2026Vypr Intelligence · Jun 9, 2026