Changeset View
Changeset View
Standalone View
Standalone View
src/infrastructure/env/PhabricatorEnv.php
| Show First 20 Lines • Show All 557 Lines • ▼ Show 20 Lines | public static function popTestEnvironment($key) { | ||||
| } | } | ||||
| } | } | ||||
| /* -( URI Validation )----------------------------------------------------- */ | /* -( URI Validation )----------------------------------------------------- */ | ||||
| /** | /** | ||||
| * Detect if a URI satisfies either @{method:isValidLocalWebResource} or | * Detect if a URI satisfies either @{method:isValidLocalURIForLink} or | ||||
| * @{method:isValidRemoteWebResource}, i.e. is a page on this server or the | * @{method:isValidRemoteURIForLink}, i.e. is a page on this server or the | ||||
| * URI of some other resource which has a valid protocol. This rejects | * URI of some other resource which has a valid protocol. This rejects | ||||
| * garbage URIs and URIs with protocols which do not appear in the | * garbage URIs and URIs with protocols which do not appear in the | ||||
| * ##uri.allowed-protocols## configuration, notably 'javascript:' URIs. | * `uri.allowed-protocols` configuration, notably 'javascript:' URIs. | ||||
| * | * | ||||
| * NOTE: This method is generally intended to reject URIs which it may be | * NOTE: This method is generally intended to reject URIs which it may be | ||||
| * unsafe to put in an "href" link attribute. | * unsafe to put in an "href" link attribute. | ||||
| * | * | ||||
| * @param string URI to test. | * @param string URI to test. | ||||
| * @return bool True if the URI identifies a web resource. | * @return bool True if the URI identifies a web resource. | ||||
| * @task uri | * @task uri | ||||
| */ | */ | ||||
| public static function isValidWebResource($uri) { | public static function isValidURIForLink($uri) { | ||||
| return self::isValidLocalWebResource($uri) || | return self::isValidLocalURIForLink($uri) || | ||||
| self::isValidRemoteWebResource($uri); | self::isValidRemoteURIForLink($uri); | ||||
| } | } | ||||
| /** | /** | ||||
| * Detect if a URI identifies some page on this server. | * Detect if a URI identifies some page on this server. | ||||
| * | * | ||||
| * NOTE: This method is generally intended to reject URIs which it may be | * NOTE: This method is generally intended to reject URIs which it may be | ||||
| * unsafe to issue a "Location:" redirect to. | * unsafe to issue a "Location:" redirect to. | ||||
| * | * | ||||
| * @param string URI to test. | * @param string URI to test. | ||||
| * @return bool True if the URI identifies a local page. | * @return bool True if the URI identifies a local page. | ||||
| * @task uri | * @task uri | ||||
| */ | */ | ||||
| public static function isValidLocalWebResource($uri) { | public static function isValidLocalURIForLink($uri) { | ||||
| $uri = (string)$uri; | $uri = (string)$uri; | ||||
| if (!strlen($uri)) { | if (!strlen($uri)) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| if (preg_match('/\s/', $uri)) { | if (preg_match('/\s/', $uri)) { | ||||
| // PHP hasn't been vulnerable to header injection attacks for a bunch of | // PHP hasn't been vulnerable to header injection attacks for a bunch of | ||||
| Show All 19 Lines | public static function isValidLocalURIForLink($uri) { | ||||
| // Valid URIs must begin with '/', followed by the end of the string or some | // Valid URIs must begin with '/', followed by the end of the string or some | ||||
| // other non-'/' character. This rejects protocol-relative URIs like | // other non-'/' character. This rejects protocol-relative URIs like | ||||
| // "//evil.com/evil_stuff/". | // "//evil.com/evil_stuff/". | ||||
| return (bool)preg_match('@^/([^/]|$)@', $uri); | return (bool)preg_match('@^/([^/]|$)@', $uri); | ||||
| } | } | ||||
| /** | /** | ||||
| * Detect if a URI identifies some valid remote resource. | * Detect if a URI identifies some valid linkable remote resource. | ||||
| * | * | ||||
| * @param string URI to test. | * @param string URI to test. | ||||
| * @return bool True if a URI idenfies a remote resource with an allowed | * @return bool True if a URI idenfies a remote resource with an allowed | ||||
| * protocol. | * protocol. | ||||
| * @task uri | * @task uri | ||||
| */ | */ | ||||
| public static function isValidRemoteWebResource($uri) { | public static function isValidRemoteURIForLink($uri) { | ||||
| $uri = (string)$uri; | try { | ||||
| self::requireValidRemoteURIForLink($uri); | |||||
| $proto = id(new PhutilURI($uri))->getProtocol(); | return true; | ||||
| if (!$proto) { | } catch (Exception $ex) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| } | |||||
| $allowed = self::getEnvConfig('uri.allowed-protocols'); | |||||
| if (empty($allowed[$proto])) { | /** | ||||
| return false; | * Detect if a URI identifies a valid linkable remote resource, throwing a | ||||
| * detailed message if it does not. | |||||
| * | |||||
| * A valid linkable remote resource can be safely linked or redirected to. | |||||
| * This is primarily a protocol whitelist check. | |||||
| * | |||||
| * @param string URI to test. | |||||
| * @return void | |||||
| * @task uri | |||||
| */ | |||||
| public static function requireValidRemoteURIForLink($uri) { | |||||
| $uri = new PhutilURI($uri); | |||||
| $proto = $uri->getProtocol(); | |||||
| if (!strlen($proto)) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'URI "%s" is not a valid linkable resource. A valid linkable '. | |||||
| 'resource URI must specify a protocol.', | |||||
| $uri)); | |||||
| } | } | ||||
| $protocols = self::getEnvConfig('uri.allowed-protocols'); | |||||
| if (!isset($protocols[$proto])) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'URI "%s" is not a valid linkable resource. A valid linkable '. | |||||
| 'resource URI must use one of these protocols: %s.', | |||||
| $uri, | |||||
| implode(', ', array_keys($protocols)))); | |||||
| } | |||||
| $domain = $uri->getDomain(); | |||||
| if (!strlen($domain)) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'URI "%s" is not a valid linkable resource. A valid linkable '. | |||||
| 'resource URI must specify a domain.', | |||||
| $uri)); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Detect if a URI identifies a valid fetchable remote resource. | |||||
| * | |||||
| * @param string URI to test. | |||||
| * @param list<string> Allowed protocols. | |||||
| * @return bool True if the URI is a valid fetchable remote resource. | |||||
| * @task uri | |||||
| */ | |||||
| public static function isValidRemoteURIForFetch($uri, array $protocols) { | |||||
| try { | |||||
| self::requireValidRemoteURIForFetch($uri, $protocols); | |||||
| return true; | return true; | ||||
| } catch (Exception $ex) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Detect if a URI identifies a valid fetchable remote resource, throwing | |||||
| * a detailed message if it does not. | |||||
| * | |||||
| * A valid fetchable remote resource can be safely fetched using a request | |||||
| * originating on this server. This is a primarily an address check against | |||||
| * the outbound address blacklist. | |||||
| * | |||||
| * @param string URI to test. | |||||
| * @param list<string> Allowed protocols. | |||||
| * @return void | |||||
| * @task uri | |||||
| */ | |||||
| public static function requireValidRemoteURIForFetch( | |||||
| $uri, | |||||
| array $protocols) { | |||||
| $uri = new PhutilURI($uri); | |||||
| $proto = $uri->getProtocol(); | |||||
| if (!strlen($proto)) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'URI "%s" is not a valid fetchable resource. A valid fetchable '. | |||||
| 'resource URI must specify a protocol.', | |||||
| $uri)); | |||||
| } | |||||
| $protocols = array_fuse($protocols); | |||||
| if (!isset($protocols[$proto])) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'URI "%s" is not a valid fetchable resource. A valid fetchable '. | |||||
| 'resource URI must use one of these protocols: %s.', | |||||
| $uri, | |||||
| implode(', ', array_keys($protocols)))); | |||||
| } | |||||
| $domain = $uri->getDomain(); | |||||
| if (!strlen($domain)) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'URI "%s" is not a valid fetchable resource. A valid fetchable '. | |||||
| 'resource URI must specify a domain.', | |||||
| $uri)); | |||||
| } | |||||
| $addresses = gethostbynamel($domain); | |||||
| if (!$addresses) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'URI "%s" is not a valid fetchable resource. The domain "%s" could '. | |||||
| 'not be resolved.', | |||||
| $uri, | |||||
| $domain)); | |||||
| } | |||||
| foreach ($addresses as $address) { | |||||
| if (self::isBlacklistedOutboundAddress($address)) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'URI "%s" is not a valid fetchable resource. The domain "%s" '. | |||||
| 'resolves to the address "%s", which is blacklisted for '. | |||||
| 'outbound requests.', | |||||
| $uri, | |||||
| $domain, | |||||
| $address)); | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Determine if an IP address is in the outbound address blacklist. | |||||
| * | |||||
| * @param string IP address. | |||||
| * @return bool True if the address is blacklisted. | |||||
| */ | |||||
| public static function isBlacklistedOutboundAddress($address) { | |||||
| $blacklist = self::getEnvConfig('security.outbound-blacklist'); | |||||
| return PhutilCIDRList::newList($blacklist)->containsAddress($address); | |||||
| } | } | ||||
| public static function isClusterRemoteAddress() { | public static function isClusterRemoteAddress() { | ||||
| $address = idx($_SERVER, 'REMOTE_ADDR'); | $address = idx($_SERVER, 'REMOTE_ADDR'); | ||||
| if (!$address) { | if (!$address) { | ||||
| throw new Exception( | throw new Exception( | ||||
| pht( | pht( | ||||
| 'Unable to test remote address against cluster whitelist: '. | 'Unable to test remote address against cluster whitelist: '. | ||||
| ▲ Show 20 Lines • Show All 72 Lines • Show Last 20 Lines | |||||