1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-20 04:20:55 +01:00

Improve protection against SSRF attacks

Summary:
Ref T6755. This improves our resistance to SSRF attacks:

  - Follow redirects manually and verify each component of the redirect chain.
  - Handle authentication provider profile picture fetches more strictly.

Test Plan:
  - Tried to download macros from various URIs which issued redirects, etc.
  - Downloaded an actual macro.
  - Went through external account workflow.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T6755

Differential Revision: https://secure.phabricator.com/D12151
This commit is contained in:
epriestley 2015-03-24 18:49:01 -07:00
parent 22b2b8eb89
commit 4f8147dbb8
2 changed files with 93 additions and 18 deletions

View file

@ -247,13 +247,19 @@ abstract class PhabricatorAuthProvider {
$image_uri, $image_uri,
array( array(
'name' => $name, 'name' => $name,
'canCDN' => true, 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
)); ));
if ($image_file->isViewableImage()) {
$image_file
->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy())
->setCanCDN(true)
->save();
$account->setProfileImagePHID($image_file->getPHID());
} else {
$image_file->delete();
}
unset($unguarded); unset($unguarded);
if ($image_file) {
$account->setProfileImagePHID($image_file->getPHID());
}
} catch (Exception $ex) { } catch (Exception $ex) {
// Log this but proceed, it's not especially important that we // Log this but proceed, it's not especially important that we
// be able to pull profile images. // be able to pull profile images.

View file

@ -451,24 +451,93 @@ final class PhabricatorFile extends PhabricatorFileDAO
} }
/**
* Download a remote resource over HTTP and save the response body as a file.
*
* This method respects `security.outbound-blacklist`, and protects against
* HTTP redirection (by manually following "Location" headers and verifying
* each destination). It does not protect against DNS rebinding. See
* discussion in T6755.
*/
public static function newFromFileDownload($uri, array $params = array()) { public static function newFromFileDownload($uri, array $params = array()) {
PhabricatorEnv::requireValidRemoteURIForFetch(
$uri,
array(
'http',
'https',
));
$timeout = 5; $timeout = 5;
list($file_data) = id(new HTTPSFuture($uri))
->setTimeout($timeout)
->resolvex();
$params = $params + array( $redirects = array();
'name' => basename($uri), $current = $uri;
); while (true) {
try {
if (count($redirects) > 10) {
throw new Exception(
pht('Too many redirects trying to fetch remote URI.'));
}
return self::newFromFileData($file_data, $params); PhabricatorEnv::requireValidRemoteURIForFetch(
$current,
array(
'http',
'https',
));
list($status, $body, $headers) = id(new HTTPSFuture($current))
->setFollowLocation(false)
->setTimeout($timeout)
->resolve();
if ($status->isRedirect()) {
// This is an HTTP 3XX status, so look for a "Location" header.
$location = null;
foreach ($headers as $header) {
list($name, $value) = $header;
if (phutil_utf8_strtolower($name) == 'location') {
$location = $value;
break;
}
}
// HTTP 3XX status with no "Location" header, just treat this like
// a normal HTTP error.
if ($location === null) {
throw $status;
}
if (isset($redirects[$location])) {
throw new Exception(
pht(
'Encountered loop while following redirects.'));
}
$redirects[$location] = $location;
$current = $location;
// We'll fall off the bottom and go try this URI now.
} else if ($status->isError()) {
// This is something other than an HTTP 2XX or HTTP 3XX status, so
// just bail out.
throw $status;
} else {
// This is HTTP 2XX, so use the the response body to save the
// file data.
$params = $params + array(
'name' => basename($uri),
);
return self::newFromFileData($body, $params);
}
} catch (Exception $ex) {
if ($redirects) {
throw new PhutilProxyException(
pht(
'Failed to fetch remote URI "%s" after following %s redirect(s) '.
'(%s): %s',
$uri,
new PhutilNumber(count($redirects)),
implode(' > ', array_keys($redirects)),
$ex->getMessage()),
$ex);
} else {
throw $ex;
}
}
}
} }
public static function normalizeFileName($file_name) { public static function normalizeFileName($file_name) {