CVE-2023-22729 (PHP, published 2023-04-26T15:15Z)
Description: value: 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.
Severity: nan (CVSS v3 base score: 6.1)
Weakness: CWE-601 \u2014 URL Redirection to Untrusted Site ('Open Redirect')
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.
Repository: https://github.com/silverstripe/silverstripe-framework
Files changed:
src/Control/Director.php
tests/php/Control/DirectorTest.php
Commit message: [CVE-2023-22729] Escaped double slash is absolute URL


Vulnerable code:
            // Check for more than one leading slash without a protocol.
            || preg_match('%^\s*/{2,}%', $url ?? '')
    /**
     * Mostly tested by {@link testIsRelativeUrl()},
     * just adding the host name matching aspect here.
     */
        $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?url=" . Director::absoluteBaseURL()));

Fixed code:
            // Check for more than one leading slash (forward or backward) without a protocol.
            // Note: Need 4 backslashes to have a single non-escaped backslash for regex.
            || preg_match('%^\s*(\\\\|/){2,}%', $url ?? '')
        $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('\\/\\/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->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('//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_site_url('http://test.com'));
        $this->assertTrue(Director::is_site_url('/relative-path'));
        $this->assertTrue(Director::is_site_url('relative-path'));
        $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()));