diff --git a/src/wp-includes/rewrite.php b/src/wp-includes/rewrite.php index 0a6b84b75e534..976d2a014b9db 100644 --- a/src/wp-includes/rewrite.php +++ b/src/wp-includes/rewrite.php @@ -502,7 +502,8 @@ function url_to_postid( $url ) { $url_host = parse_url( $url, PHP_URL_HOST ); if ( is_string( $url_host ) ) { - $url_host = str_replace( 'www.', '', $url_host ); + // Only a leading 'www.' is optional. Removing it anywhere else would match a different host. + $url_host = preg_replace( '|^www\.|', '', $url_host ); } else { $url_host = ''; } @@ -510,7 +511,7 @@ function url_to_postid( $url ) { $home_url_host = parse_url( home_url(), PHP_URL_HOST ); if ( is_string( $home_url_host ) ) { - $home_url_host = str_replace( 'www.', '', $home_url_host ); + $home_url_host = preg_replace( '|^www\.|', '', $home_url_host ); } else { $home_url_host = ''; } diff --git a/tests/phpunit/tests/rewrite.php b/tests/phpunit/tests/rewrite.php index 24c7e4e1459fc..a45bb8e2ec913 100644 --- a/tests/phpunit/tests/rewrite.php +++ b/tests/phpunit/tests/rewrite.php @@ -167,7 +167,7 @@ public function test_url_to_postid_of_http_site_when_current_site_uses_https() { * @param string $url The complete home URL including scheme and path. * @param string $path Path relative to the home URL. Blank string if no path is specified. * @param string|null $orig_scheme Scheme to give the home URL context. - * @param int|null $_blog_id Site ID, or null for the current site. + * @param int|null $blog_id Site ID, or null for the current site. * @return string The complete home URL including scheme and path. */ public function filter_http_home_url( $url, $path, $orig_scheme, $_blog_id ) { @@ -262,6 +262,48 @@ public function test_url_to_postid_url_has_only_path() { $this->assertSame( 0, url_to_postid( '/example/' ) ); } + /** + * Only a leading 'www.' is optional when comparing the URL's host to the site's. + * + * A 'www.' elsewhere in the host belongs to a different domain, which an attacker + * can register: stripping it everywhere makes 'exwww.ample.com' match 'example.com'. + * + * @ticket 65016 + * + * @covers ::url_to_postid + * + * @dataProvider data_url_to_postid_host_matching + * + * @param string $host Host of the URL to resolve. + * @param bool $is_local Whether the host should be treated as this site. + */ + public function test_url_to_postid_matches_www_prefix_only( $host, $is_local ) { + update_option( 'home', 'https://example.com' ); + update_option( 'siteurl', 'https://example.com' ); + + $post_id = self::factory()->post->create(); + + $expected = $is_local ? $post_id : 0; + + $this->assertSame( $expected, url_to_postid( "https://$host/?p=$post_id" ) ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_url_to_postid_host_matching() { + return array( + 'the site host' => array( 'example.com', true ), + 'the site host with www' => array( 'www.example.com', true ), + 'www inside the domain' => array( 'exwww.ample.com', false ), + 'www inside the TLD' => array( 'example.cwww.om', false ), + 'an unrelated host' => array( 'evil.com', false ), + 'the site host as subdomain' => array( 'example.com.evil.com', false ), + ); + } + /** * @covers ::url_to_postid */