diff --git a/src/parser/PhutilURI.php b/src/parser/PhutilURI.php --- a/src/parser/PhutilURI.php +++ b/src/parser/PhutilURI.php @@ -97,11 +97,15 @@ } // NOTE: `parse_url()` is very liberal about host names; fail the parse if - // the host looks like garbage. + // the host looks like garbage. In particular, we do not allow hosts which + // begin with "." or "-". See T12961 for a specific attack which relied on + // hosts beginning with "-". if ($parts) { $host = idx($parts, 'host', ''); - if (!preg_match('/^([a-zA-Z0-9\\.\\-]*)$/', $host)) { - $parts = false; + if (strlen($host)) { + if (!preg_match('/^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-]*\z/', $host)) { + $parts = false; + } } } diff --git a/src/parser/__tests__/PhutilURITestCase.php b/src/parser/__tests__/PhutilURITestCase.php --- a/src/parser/__tests__/PhutilURITestCase.php +++ b/src/parser/__tests__/PhutilURITestCase.php @@ -130,6 +130,20 @@ public function testStrictURIParsingOfHosts() { $uri = new PhutilURI('http://&/'); $this->assertEqual('', $uri->getDomain()); + + // See T12961 for more discussion of these hosts which begin with "-". + $uri = new PhutilURI('ssh://-oProxyCommand/'); + $this->assertEqual('', $uri->getDomain()); + $uri = new PhutilURI('ssh://-oProxyCommand=curl/'); + $this->assertEqual('', $uri->getDomain()); + $uri = new PhutilURI('ssh://.com/'); + $this->assertEqual('', $uri->getDomain()); + + // Make sure newlines are rejected. + $uri = new PhutilURI("ssh://example.com\n.domain.us/"); + $this->assertEqual('', $uri->getDomain()); + $uri = new PhutilURI("ssh://example.com\n"); + $this->assertEqual('', $uri->getDomain()); } public function testStrictURIParsingOfLeadingWhitespace() {