@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix an OAuthServer issue where an attacker could make a link function over HTTP when it should be HTTPS-only

Summary:
Two behavioral changes:

- If the redirect URI for an application is "https", require HTTPS always.
- According to my reading of http://tools.ietf.org/html/draft-ietf-oauth-v2-23#section-3.1.2 we need to check both names //and values// for parameters. Add value checking. I think this makes more sense in general? No one uses this, soooo...

iiam

Test Plan: This has good coverage already; added some tests for the new cases.

Reviewers: vrana

Reviewed By: vrana

CC: cbg, aran, btrahan

Differential Revision: https://secure.phabricator.com/D5022

+80 -19
+50 -19
src/applications/oauthserver/PhabricatorOAuthServer.php
··· 206 206 * for details on what makes a given redirect URI "valid". 207 207 */ 208 208 public function validateRedirectURI(PhutilURI $uri) { 209 - if (PhabricatorEnv::isValidRemoteWebResource($uri)) { 210 - if ($uri->getFragment()) { 211 - return false; 212 - } 213 - if ($uri->getDomain()) { 214 - return true; 215 - } 209 + if (!PhabricatorEnv::isValidRemoteWebResource($uri)) { 210 + return false; 216 211 } 217 - return false; 212 + 213 + if ($uri->getFragment()) { 214 + return false; 215 + } 216 + 217 + if (!$uri->getDomain()) { 218 + return false; 219 + } 220 + 221 + return true; 218 222 } 219 223 220 224 /** ··· 222 226 * its own right. Further, it must have the same domain and (at least) the 223 227 * same query parameters as the primary URI. 224 228 */ 225 - public function validateSecondaryRedirectURI(PhutilURI $secondary_uri, 226 - PhutilURI $primary_uri) { 227 - $valid = $this->validateRedirectURI($secondary_uri); 228 - if ($valid) { 229 - $valid_domain = ($secondary_uri->getDomain() == 230 - $primary_uri->getDomain()); 231 - $good_params = $primary_uri->getQueryParams(); 232 - $test_params = $secondary_uri->getQueryParams(); 233 - $missing_params = array_diff_key($good_params, $test_params); 234 - $valid = $valid_domain && empty($missing_params); 229 + public function validateSecondaryRedirectURI( 230 + PhutilURI $secondary_uri, 231 + PhutilURI $primary_uri) { 232 + 233 + // The secondary URI must be valid. 234 + if (!$this->validateRedirectURI($secondary_uri)) { 235 + return false; 236 + } 237 + 238 + // Both URIs must point at the same domain. 239 + if ($secondary_uri->getDomain() != $primary_uri->getDomain()) { 240 + return false; 241 + } 242 + 243 + // Any query parameters present in the first URI must be exactly present 244 + // in the second URI. 245 + $need_params = $primary_uri->getQueryParams(); 246 + $have_params = $secondary_uri->getQueryParams(); 247 + 248 + foreach ($need_params as $key => $value) { 249 + if (!array_key_exists($key, $have_params)) { 250 + return false; 251 + } 252 + if ((string)$have_params[$key] != (string)$value) { 253 + return false; 254 + } 235 255 } 236 - return $valid; 256 + 257 + // If the first URI is HTTPS, the second URI must also be HTTPS. This 258 + // defuses an attack where a third party with control over the network 259 + // tricks you into using HTTP to authenticate over a link which is supposed 260 + // to be HTTPS only and sniffs all your token cookies. 261 + if (strtolower($primary_uri->getProtocol()) == 'https') { 262 + if (strtolower($secondary_uri->getProtocol()) != 'https') { 263 + return false; 264 + } 265 + } 266 + 267 + return true; 237 268 } 238 269 239 270 }
+30
src/applications/oauthserver/__tests__/PhabricatorOAuthServerTestCase.php
··· 61 61 "relative to '{$primary_uri}'"); 62 62 } 63 63 64 + $primary_uri = new PhutilURI('https://secure.example.com/'); 65 + $tests = array( 66 + 'https://secure.example.com/' => true, 67 + 'http://secure.example.com/' => false, 68 + ); 69 + foreach ($tests as $input => $expected) { 70 + $uri = new PhutilURI($input); 71 + $this->assertEqual( 72 + $expected, 73 + $server->validateSecondaryRedirectURI($uri, $primary_uri), 74 + "Validation (https): {$input}"); 75 + } 76 + 77 + $primary_uri = new PhutilURI('http://example.com/?z=2&y=3'); 78 + $tests = array( 79 + 'http://example.com?z=2&y=3' => true, 80 + 'http://example.com?y=3&z=2' => true, 81 + 'http://example.com?y=3&z=2&x=1' => true, 82 + 'http://example.com?y=2&z=3' => false, 83 + 'http://example.com?y&x' => false, 84 + 'http://example.com?z=2&x=3' => false, 85 + ); 86 + foreach ($tests as $input => $expected) { 87 + $uri = new PhutilURI($input); 88 + $this->assertEqual( 89 + $expected, 90 + $server->validateSecondaryRedirectURI($uri, $primary_uri), 91 + "Validation (params): {$input}"); 92 + } 93 + 64 94 } 65 95 66 96 }