CVE-2023-22729 (published 2023-04-26T15:15Z)

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.

CVSS v3 base score: 6.1, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N)

Weakness information:
cwe_id: CWE-601
cwe_name: URL Redirection to Untrusted Site ('Open Redirect')
description: A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.
extended_description: An http parameter may contain a URL value and could cause the web application to redirect the request to the specified URL. By modifying the URL value to a malicious site, an attacker may successfully launch a phishing scam and steal user credentials. Because the server name in the modified link is identical to the original site, phishing attempts have a more trustworthy appearance. Whether this issue poses a vulnerability will be subject to the intended behavior of the application. For example, a search engine might intentionally provide redirects to arbitrary URLs.
         

Fix information:
repo_url: https://github.com/silverstripe/silverstripe-framework
commit_details: repo_url: https://github.com/silverstripe/silverstripe-framework
author: Guy Sartorelli
author_date: 2023-02-14 11:41:51+13:00
msg: [CVE-2023-22729] Escaped double slash is absolute URL
repository: repo_url: https://github.com/silverstripe/silverstripe-framework
repo_name: silverstripe/silverstripe-framework
description: Silverstripe Framework, the MVC framework that powers Silverstripe CMS
repo_language: PHP
owner: silverstripe
file_changes: filename: Director.php
change_type: ModificationType.MODIFY
diff: @@ -822,10 +822,11 @@ class Director implements TemplateGlobalProvider
         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).

programming_language: PHP
filename: DirectorTest.php
change_type: ModificationType.MODIFY
diff: @@ -232,6 +232,9 @@ class DirectorTest extends SapphireTest
     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 @@ class DirectorTest extends SapphireTest
         $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 @@ class DirectorTest extends SapphireTest
     {
         $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 @@ class DirectorTest extends SapphireTest
         );
     }
 
-    /**
-     * 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'));

programming_language: PHP