@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.

Require a CSRF code for Twitter and JIRA (OAuth 1) logins

Summary:
OAuth1 doesn't have anything like the `state` parameter, and I overlooked that we need to shove one in there somewhere. Append it to the callback URI. This functions like `state` in OAuth2.

Without this, an attacker can trick a user into logging into Phabricator with an account the attacker controls.

Test Plan:
- Logged in with JIRA.
- Logged in with Twitter.
- Logged in with Facebook (an OAuth2 provider).
- Linked a Twitter account.
- Linked a Facebook account.
- Jiggered codes in URIs and verified that I got the exceptions I expected.

Reviewers: btrahan, arice

Reviewed By: arice

CC: arice, chad, aran

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

+56 -28
+2 -1
src/applications/auth/application/PhabricatorApplicationAuth.php
··· 76 76 '(?P<action>enable|disable)/(?P<id>\d+)/' => 77 77 'PhabricatorAuthDisableController', 78 78 ), 79 - 'login/(?P<pkey>[^/]+)/' => 'PhabricatorAuthLoginController', 79 + 'login/(?P<pkey>[^/]+)/(?:(?P<extra>[^/]+)/)?' => 80 + 'PhabricatorAuthLoginController', 80 81 'register/(?:(?P<akey>[^/]+)/)?' => 'PhabricatorAuthRegisterController', 81 82 'start/' => 'PhabricatorAuthStartController', 82 83 'validate/' => 'PhabricatorAuthValidateController',
+6
src/applications/auth/controller/PhabricatorAuthLoginController.php
··· 4 4 extends PhabricatorAuthController { 5 5 6 6 private $providerKey; 7 + private $extraURIData; 7 8 private $provider; 8 9 9 10 public function shouldRequireLogin() { ··· 12 13 13 14 public function willProcessRequest(array $data) { 14 15 $this->providerKey = $data['pkey']; 16 + $this->extraURIData = idx($data, 'extra'); 17 + } 18 + 19 + public function getExtraURIData() { 20 + return $this->extraURIData; 15 21 } 16 22 17 23 public function processRequest() {
+34
src/applications/auth/provider/PhabricatorAuthProvider.php
··· 441 441 return null; 442 442 } 443 443 444 + protected function getAuthCSRFCode(AphrontRequest $request) { 445 + $phcid = $request->getCookie(PhabricatorCookies::COOKIE_CLIENTID); 446 + if (!strlen($phcid)) { 447 + throw new Exception( 448 + pht( 449 + 'Your browser did not submit a "%s" cookie with client state '. 450 + 'information in the request. Check that cookies are enabled. '. 451 + 'If this problem persists, you may need to clear your cookies.', 452 + PhabricatorCookies::COOKIE_CLIENTID)); 453 + } 454 + 455 + return PhabricatorHash::digest($phcid); 456 + } 457 + 458 + protected function verifyAuthCSRFCode(AphrontRequest $request, $actual) { 459 + $expect = $this->getAuthCSRFCode($request); 460 + 461 + if (!strlen($actual)) { 462 + throw new Exception( 463 + pht( 464 + 'The authentication provider did not return a client state '. 465 + 'parameter in its response, but one was expected. If this '. 466 + 'problem persists, you may need to clear your cookies.')); 467 + } 468 + 469 + if ($actual !== $expect) { 470 + throw new Exception( 471 + pht( 472 + 'The authentication provider did not return the correct client '. 473 + 'state parameter in its response. If this problem persists, you may '. 474 + 'need to clear your cookies.')); 475 + } 476 + } 477 + 444 478 }
+3 -27
src/applications/auth/provider/PhabricatorAuthProviderOAuth.php
··· 35 35 36 36 protected function renderLoginForm(AphrontRequest $request, $mode) { 37 37 $adapter = $this->getAdapter(); 38 - $adapter->setState( 39 - PhabricatorHash::digest( 40 - $request->getCookie(PhabricatorCookies::COOKIE_CLIENTID))); 38 + $adapter->setState($this->getAuthCSRFCode($request)); 41 39 42 40 $scope = $request->getStr('scope'); 43 41 if ($scope) { ··· 71 69 return array($account, $response); 72 70 } 73 71 72 + $this->verifyAuthCSRFCode($request, $request->getStr('state')); 73 + 74 74 $code = $request->getStr('code'); 75 75 if (!strlen($code)) { 76 76 $response = $controller->buildProviderErrorResponse( ··· 80 80 'response.')); 81 81 82 82 return array($account, $response); 83 - } 84 - 85 - if ($adapter->supportsStateParameter()) { 86 - $phcid = $request->getCookie(PhabricatorCookies::COOKIE_CLIENTID); 87 - if (!strlen($phcid)) { 88 - $response = $controller->buildProviderErrorResponse( 89 - $this, 90 - pht( 91 - 'Your browser did not submit a "%s" cookie with OAuth state '. 92 - 'information in the request. Check that cookies are enabled. '. 93 - 'If this problem persists, you may need to clear your cookies.', 94 - PhabricatorCookies::COOKIE_CLIENTID)); 95 - } 96 - 97 - $state = $request->getStr('state'); 98 - $expect = PhabricatorHash::digest($phcid); 99 - if ($state !== $expect) { 100 - $response = $controller->buildProviderErrorResponse( 101 - $this, 102 - pht( 103 - 'The OAuth provider did not return the correct "state" parameter '. 104 - 'in its response. If this problem persists, you may need to clear '. 105 - 'your cookies.')); 106 - } 107 83 } 108 84 109 85 $adapter->setCode($code);
+11
src/applications/auth/provider/PhabricatorAuthProviderOAuth1.php
··· 55 55 $response = null; 56 56 57 57 if ($request->isHTTPPost()) { 58 + // Add a CSRF code to the callback URI, which we'll verify when 59 + // performing the login. 60 + 61 + $client_code = $this->getAuthCSRFCode($request); 62 + 63 + $callback_uri = $adapter->getCallbackURI(); 64 + $callback_uri = $callback_uri.$client_code.'/'; 65 + $adapter->setCallbackURI($callback_uri); 66 + 58 67 $uri = $adapter->getClientRedirectURI(); 59 68 $response = id(new AphrontRedirectResponse())->setURI($uri); 60 69 return array($account, $response); ··· 69 78 70 79 // NOTE: You can get here via GET, this should probably be a bit more 71 80 // user friendly. 81 + 82 + $this->verifyAuthCSRFCode($request, $controller->getExtraURIData()); 72 83 73 84 $token = $request->getStr('oauth_token'); 74 85 $verifier = $request->getStr('oauth_verifier');