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

Disallow email addresses which will overflow MySQL storage

Summary:
Via HackerOne. An attacker can bypass `auth.email-domains` by registering with an email like:

aaaaa...aaaaa@evil.com@company.com

We'll validate the full string, then insert it into the database where it will be truncated, removing the `@company.com` part. Then we'll send an email to `@evil.com`.

Instead, reject email addresses which won't fit in the table.

`STRICT_ALL_TABLES` stops this attack, I'm going to add a setup warning encouraging it.

Test Plan:
- Set `auth.email-domains` to `@company.com`.
- Registered with `aaa...aaa@evil.com@company.com`. Previously this worked, now it is rejected.
- Did a valid registration.
- Tried to add `aaa...aaaa@evil.com@company.com` as an email address. Previously this worked, now it is rejected.
- Did a valid email add.
- Added and executed unit tests.

Reviewers: btrahan, arice

Reviewed By: arice

CC: aran, chad

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

+110 -2
+2
src/__phutil_library_map__.php
··· 2175 2175 'PhabricatorUserCustomFieldStringIndex' => 'applications/people/storage/PhabricatorUserCustomFieldStringIndex.php', 2176 2176 'PhabricatorUserDAO' => 'applications/people/storage/PhabricatorUserDAO.php', 2177 2177 'PhabricatorUserEditor' => 'applications/people/editor/PhabricatorUserEditor.php', 2178 + 'PhabricatorUserEditorTestCase' => 'applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php', 2178 2179 'PhabricatorUserEmail' => 'applications/people/storage/PhabricatorUserEmail.php', 2179 2180 'PhabricatorUserLog' => 'applications/people/storage/PhabricatorUserLog.php', 2180 2181 'PhabricatorUserPreferences' => 'applications/settings/storage/PhabricatorUserPreferences.php', ··· 4997 4998 'PhabricatorUserCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage', 4998 4999 'PhabricatorUserDAO' => 'PhabricatorLiskDAO', 4999 5000 'PhabricatorUserEditor' => 'PhabricatorEditor', 5001 + 'PhabricatorUserEditorTestCase' => 'PhabricatorTestCase', 5000 5002 'PhabricatorUserEmail' => 'PhabricatorUserDAO', 5001 5003 'PhabricatorUserLog' => 'PhabricatorUserDAO', 5002 5004 'PhabricatorUserPreferences' => 'PhabricatorUserDAO',
+4 -1
src/applications/auth/controller/PhabricatorAuthRegisterController.php
··· 200 200 if (!strlen($value_email)) { 201 201 $e_email = pht('Required'); 202 202 $errors[] = pht('Email is required.'); 203 + } else if (!PhabricatorUserEmail::isValidAddress($value_email)) { 204 + $e_email = pht('Invalid'); 205 + $errors[] = PhabricatorUserEmail::describeValidAddresses(); 203 206 } else if (!PhabricatorUserEmail::isAllowedAddress($value_email)) { 204 - $e_email = pht('Invalid'); 207 + $e_email = pht('Disallowed'); 205 208 $errors[] = PhabricatorUserEmail::describeAllowedAddresses(); 206 209 } else { 207 210 $e_email = null;
+4
src/applications/people/editor/PhabricatorUserEditor.php
··· 570 570 // user friendly errors for us, but we omit the courtesy checks on some 571 571 // pathways like administrative scripts for simplicity. 572 572 573 + if (!PhabricatorUserEmail::isValidAddress($email->getAddress())) { 574 + throw new Exception(PhabricatorUserEmail::describeValidAddresses()); 575 + } 576 + 573 577 if (!PhabricatorUserEmail::isAllowedAddress($email->getAddress())) { 574 578 throw new Exception(PhabricatorUserEmail::describeAllowedAddresses()); 575 579 }
+68
src/applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php
··· 1 + <?php 2 + 3 + final class PhabricatorUserEditorTestCase extends PhabricatorTestCase { 4 + 5 + protected function getPhabricatorTestCaseConfiguration() { 6 + return array( 7 + self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true, 8 + ); 9 + } 10 + 11 + public function testRegistrationEmailOK() { 12 + $env = PhabricatorEnv::beginScopedEnv(); 13 + $env->overrideEnvConfig('auth.email-domains', array('example.com')); 14 + 15 + $this->registerUser( 16 + 'PhabricatorUserEditorTestCaseOK', 17 + 'PhabricatorUserEditorTestCase@example.com'); 18 + } 19 + 20 + public function testRegistrationEmailInvalid() { 21 + $env = PhabricatorEnv::beginScopedEnv(); 22 + $env->overrideEnvConfig('auth.email-domains', array('example.com')); 23 + 24 + $prefix = str_repeat('a', PhabricatorUserEmail::MAX_ADDRESS_LENGTH); 25 + $email = $prefix.'@evil.com@example.com'; 26 + 27 + try { 28 + $this->registerUser( 29 + 'PhabricatorUserEditorTestCaseInvalid', 30 + $email); 31 + } catch (Exception $ex) { 32 + $caught = $ex; 33 + } 34 + 35 + $this->assertEqual(true, ($caught instanceof Exception)); 36 + } 37 + 38 + public function testRegistrationEmailDomain() { 39 + $env = PhabricatorEnv::beginScopedEnv(); 40 + $env->overrideEnvConfig('auth.email-domains', array('example.com')); 41 + 42 + $caught = null; 43 + try { 44 + $this->registerUser( 45 + 'PhabricatorUserEditorTestCaseDomain', 46 + 'PhabricatorUserEditorTestCase@whitehouse.gov'); 47 + } catch (Exception $ex) { 48 + $caught = $ex; 49 + } 50 + 51 + $this->assertEqual(true, ($caught instanceof Exception)); 52 + } 53 + 54 + private function registerUser($username, $email) { 55 + $user = id(new PhabricatorUser()) 56 + ->setUsername($username) 57 + ->setRealname($username); 58 + 59 + $email = id(new PhabricatorUserEmail()) 60 + ->setAddress($email) 61 + ->setIsVerified(0); 62 + 63 + id(new PhabricatorUserEditor()) 64 + ->setActor($user) 65 + ->createNewUser($user, $email); 66 + } 67 + 68 + }
+28
src/applications/people/storage/PhabricatorUserEmail.php
··· 12 12 protected $isPrimary; 13 13 protected $verificationCode; 14 14 15 + const MAX_ADDRESS_LENGTH = 128; 16 + 15 17 public function getVerificationURI() { 16 18 return '/emailverify/'.$this->getVerificationCode().'/'; 17 19 } ··· 30 32 /** 31 33 * @task restrictions 32 34 */ 35 + public static function isValidAddress($address) { 36 + if (strlen($address) > self::MAX_ADDRESS_LENGTH) { 37 + return false; 38 + } 39 + 40 + return true; 41 + } 42 + 43 + 44 + /** 45 + * @task restrictions 46 + */ 47 + public static function describeValidAddresses() { 48 + return pht( 49 + 'The maximum length of an email address is %d character(s).', 50 + new PhutilNumber(self::MAX_ADDRESS_LENGTH)); 51 + } 52 + 53 + 54 + /** 55 + * @task restrictions 56 + */ 33 57 public static function isAllowedAddress($address) { 58 + if (!self::isValidAddress($address)) { 59 + return false; 60 + } 61 + 34 62 $allowed_domains = PhabricatorEnv::getEnvConfig('auth.email-domains'); 35 63 if (!$allowed_domains) { 36 64 return true;
+4 -1
src/applications/settings/panel/PhabricatorSettingsPanelEmailAddresses.php
··· 173 173 if (!strlen($email)) { 174 174 $e_email = pht('Required'); 175 175 $errors[] = pht('Email is required.'); 176 + } else if (!PhabricatorUserEmail::isValidAddress($email)) { 177 + $e_email = pht('Invalid'); 178 + $errors[] = PhabricatorUserEmail::describeValidAddresses(); 176 179 } else if (!PhabricatorUserEmail::isAllowedAddress($email)) { 177 - $e_email = pht('Invalid'); 180 + $e_email = pht('Disallowed'); 178 181 $errors[] = PhabricatorUserEmail::describeAllowedAddresses(); 179 182 } 180 183