Silverstripe Framework has open redirect vulnerability on CMSSecurity relogin screen
Description
Silverstripe Framework is the Model-View-Controller framework that powers the Silverstripe content management system. Prior to version 4.12.15, an attacker can display a link to a third party website on a login screen by convincing a legitimate content author to follow a specially crafted link. Users should upgrade to Silverstripe Framework 4.12.15 or above to address the issue.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Silverstripe Framework before 4.12.15 improperly validates URLs with escaped double slashes, allowing phishing via crafted links on login screens.
Root
Cause Silverstripe Framework's Director::is_absolute_url() function incorrectly treats URLs containing escaped double slashes (e.g., https:/\\test.com) as absolute URLs [1][2]. This occurs because the function does not properly handle sequences like \ or \/ that can appear in URL strings, leading to false positives in absolute URL detection [2].
Exploitation
An attacker can exploit this by crafting a malicious link that appears to point to a legitimate internal resource but actually redirects to a third-party website. The attacker must convince a content author with editing privileges to click the link [1]. For example, a link such as https:/\\attacker.com may be interpreted as absolute and rendered on the login screen, potentially leading the author to an external phishing page.
Impact
Successful exploitation results in a phishing attack: the victim may be directed to a malicious site that mimics the login page, potentially capturing credentials or other sensitive information. The vulnerability is limited to social engineering and does not allow direct code execution or privilege escalation.
Mitigation
The vulnerability is patched in Silverstripe Framework version 4.12.15 [1]. Users are advised to upgrade immediately. No workarounds have been published, and the issue does not affect any known active exploitation campaigns at this time.
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
silverstripe/frameworkPackagist | < 4.12.5 | 4.12.5 |
Affected products
2- silverstripe/silverstripe-frameworkv5Range: < 4.12.5
Patches
11a5bb4cbece1[CVE-2023-22729] Escaped double slash is absolute URL
2 files changed · +45 −10
src/Control/Director.php+3 −2 modified@@ -822,10 +822,11 @@ public static function is_absolute_url($url) return ( // Base check for existence of a host on a compliant URL parse_url($url ?? '', PHP_URL_HOST) - // Check for more than one leading slash without a protocol. + // Check for more than one leading slash (forward or backward) without a protocol. // While not a RFC compliant absolute URL, it is completed to a valid URL by some browsers, // and hence a potential security risk. Single leading slashes are not an issue though. - || preg_match('%^\s*/{2,}%', $url ?? '') + // Note: Need 4 backslashes to have a single non-escaped backslash for regex. + || preg_match('%^\s*(\\\\|/){2,}%', $url ?? '') || ( // If a colon is found, check if it's part of a valid scheme definition // (meaning its not preceded by a slash).
tests/php/Control/DirectorTest.php+42 −8 modified@@ -232,6 +232,9 @@ public function provideAbsolutePaths() public function testIsAbsoluteUrl() { $this->assertTrue(Director::is_absolute_url('http://test.com/testpage')); + $this->assertTrue(Director::is_absolute_url('https:/\\test.com')); + $this->assertTrue(Director::is_absolute_url('https:\\/test.com')); + $this->assertTrue(Director::is_absolute_url('https:\\\\test.com')); $this->assertTrue(Director::is_absolute_url('ftp://test.com')); $this->assertFalse(Director::is_absolute_url('test.com/testpage')); $this->assertFalse(Director::is_absolute_url('/relative')); @@ -241,6 +244,11 @@ public function testIsAbsoluteUrl() $this->assertTrue(Director::is_absolute_url("https://test.com/?url=http://foo.com")); $this->assertTrue(Director::is_absolute_url("trickparseurl:http://test.com")); $this->assertTrue(Director::is_absolute_url('//test.com')); + $this->assertTrue(Director::is_absolute_url('\\/\\/test.com')); + $this->assertTrue(Director::is_absolute_url('\/\/test.com')); + $this->assertTrue(Director::is_absolute_url('/\\test.com')); + $this->assertTrue(Director::is_absolute_url('\\\\test.com')); + $this->assertFalse(Director::is_absolute_url('\\test.com')); $this->assertTrue(Director::is_absolute_url('/////test.com')); $this->assertTrue(Director::is_absolute_url(' ///test.com')); $this->assertTrue(Director::is_absolute_url('http:test.com')); @@ -258,8 +266,17 @@ public function testIsRelativeUrl() { $this->assertFalse(Director::is_relative_url('http://test.com')); $this->assertFalse(Director::is_relative_url('https://test.com')); + $this->assertFalse(Director::is_relative_url('https:/\\test.com')); + $this->assertFalse(Director::is_relative_url('https:\\/test.com')); + $this->assertFalse(Director::is_relative_url('https:\\\\test.com')); $this->assertFalse(Director::is_relative_url(' https://test.com/testpage ')); $this->assertTrue(Director::is_relative_url('test.com/testpage')); + $this->assertFalse(Director::is_relative_url('//test.com')); + $this->assertFalse(Director::is_relative_url('\\/\\/test.com')); + $this->assertFalse(Director::is_relative_url('\/\/test.com')); + $this->assertFalse(Director::is_relative_url('/\\test.com')); + $this->assertFalse(Director::is_relative_url('\\\\test.com')); + $this->assertTrue(Director::is_relative_url('\\test.com')); $this->assertFalse(Director::is_relative_url('ftp://test.com')); $this->assertTrue(Director::is_relative_url('/relative')); $this->assertTrue(Director::is_relative_url('relative')); @@ -401,17 +418,34 @@ public function testMakeRelative($baseURL, $requestURL, $relativeURL) ); } - /** - * Mostly tested by {@link testIsRelativeUrl()}, - * just adding the host name matching aspect here. - */ public function testIsSiteUrl() { - $this->assertFalse(Director::is_site_url("http://test.com")); + $this->assertFalse(Director::is_site_url('http://test.com')); + $this->assertTrue(Director::is_site_url('/relative-path')); + $this->assertTrue(Director::is_site_url('relative-path')); $this->assertTrue(Director::is_site_url(Director::absoluteBaseURL())); - $this->assertFalse(Director::is_site_url("http://test.com?url=" . Director::absoluteBaseURL())); - $this->assertFalse(Director::is_site_url("http://test.com?url=" . urlencode(Director::absoluteBaseURL() ?? ''))); - $this->assertFalse(Director::is_site_url("//test.com?url=" . Director::absoluteBaseURL())); + $this->assertFalse(Director::is_site_url('http://test.com?url=' . Director::absoluteBaseURL())); + $this->assertFalse(Director::is_site_url('http://test.com?url=' . urlencode(Director::absoluteBaseURL() ?? ''))); + $this->assertFalse(Director::is_site_url('http:\\\\test.com')); + $this->assertFalse(Director::is_site_url('http:\\\\test.com?url=' . Director::absoluteBaseURL())); + $this->assertFalse(Director::is_site_url('http:\\\\test.com?url=' . urlencode(Director::absoluteBaseURL() ?? ''))); + $this->assertFalse(Director::is_site_url('http:\\/test.com')); + $this->assertFalse(Director::is_site_url('http:\\/test.com?url=' . Director::absoluteBaseURL())); + $this->assertFalse(Director::is_site_url('http:\\/test.com?url=' . urlencode(Director::absoluteBaseURL() ?? ''))); + $this->assertFalse(Director::is_site_url('//test.com')); + $this->assertFalse(Director::is_site_url('//test.com?url=' . Director::absoluteBaseURL())); + $this->assertFalse(Director::is_site_url('\\/\\/test.com')); + $this->assertFalse(Director::is_site_url('\\/\\/test.com?url=' . Director::absoluteBaseURL())); + $this->assertFalse(Director::is_site_url('\/\/test.com')); + $this->assertFalse(Director::is_site_url('\/\/test.com?url=' . Director::absoluteBaseURL())); + $this->assertFalse(Director::is_site_url('\\/test.com')); + $this->assertFalse(Director::is_site_url('\\/test.com?url=' . Director::absoluteBaseURL())); + $this->assertFalse(Director::is_site_url('/\\test.com')); + $this->assertFalse(Director::is_site_url('/\\test.com?url=' . Director::absoluteBaseURL())); + $this->assertFalse(Director::is_site_url('\\\\test.com')); + $this->assertFalse(Director::is_site_url('\\\\test.com?url=' . Director::absoluteBaseURL())); + $this->assertTrue(Director::is_site_url('\\test.com')); + $this->assertTrue(Director::is_site_url('\\test.com?url=' . Director::absoluteBaseURL())); $this->assertFalse(Director::is_site_url('http://google.com\@test.com')); $this->assertFalse(Director::is_site_url('http://google.com/@test.com')); $this->assertFalse(Director::is_site_url('http://google.com:pass\@test.com'));
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-fw84-xgm8-9jmvghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-22729ghsaADVISORY
- github.com/FriendsOfPHP/security-advisories/blob/master/silverstripe/framework/CVE-2023-22729.yamlghsaWEB
- github.com/silverstripe/silverstripe-framework/commit/1a5bb4cbece1721203977910b8ecd8b79c18dc77ghsax_refsource_MISCWEB
- github.com/silverstripe/silverstripe-framework/security/advisories/GHSA-fw84-xgm8-9jmvghsax_refsource_CONFIRMWEB
- www.silverstripe.org/download/security-releases/cve-2023-22729ghsaWEB
News mentions
0No linked articles in our index yet.