diff --git a/src/markup/__tests__/PhutilMarkupTestCase.php b/src/markup/__tests__/PhutilMarkupTestCase.php --- a/src/markup/__tests__/PhutilMarkupTestCase.php +++ b/src/markup/__tests__/PhutilMarkupTestCase.php @@ -72,11 +72,20 @@ // These should get the implicit insertion. 'http://www.example.org/' => true, - '///evil.com/' => true, ' http://www.example.org/' => true, 'ftp://filez.com' => true, 'mailto:santa@northpole.com' => true, 'tel:18005555555' => true, + + // These are protocol-relative hrefs. Browers will treat a URI with + // a leading slash followed by any positive number of slashes and + // backslashes as a protocol-relative link. + '//evil.com/' => true, + '/\\evil.com/' => true, + '///evil.com/' => true, + '//\\evil.com/' => true, + '/\\/evil.com/' => true, + '/\\\\/evil.com' => true, ); foreach ($map as $input => $expect) { diff --git a/src/markup/render.php b/src/markup/render.php --- a/src/markup/render.php +++ b/src/markup/render.php @@ -35,9 +35,14 @@ $is_anchor_href = ($href[0] == '#'); // Is this a link to a resource on the same domain? The second part of - // this excludes "///evil.com/" protocol-relative hrefs. - $is_domain_href = ($href[0] == '/') && - (!isset($href[1]) || $href[1] != '/'); + // this excludes "//evil.com/" protocol-relative hrefs. The third part + // of this excludes "/\evil.com/" protocol-relative fantasy hrefs which + // are completely made up but which browsers all respect. Broadly, + // browsers will dutifuly treat "/" followed by ANY sequence of "/" and + // "\" as though it were "//". + $is_domain_href = + ($href[0] == '/') && + (!isset($href[1]) || ($href[1] != '/' && $href[1] != '\\')); // If the `rel` attribute is not specified, fill in `rel="noreferrer"`. // Effectively, this serves to make the default behavior for offsite