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

Proof of concept mitigation of BREACH

Summary: Ref T3684 for discussion. This could be cleaned up a bit (it would be nice to draw entropy once per request, for instance, and maybe respect CSRF_TOKEN_LENGTH more closely) but should effectively mitigate BREACH.

Test Plan: Submitted forms; submitted forms after mucking with CSRF and observed CSRF error. Verified that source now has "B@..." tokens.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T3684

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

+58 -7
+51 -5
src/applications/people/storage/PhabricatorUser.php
··· 152 152 } 153 153 154 154 const CSRF_CYCLE_FREQUENCY = 3600; 155 + const CSRF_SALT_LENGTH = 8; 155 156 const CSRF_TOKEN_LENGTH = 16; 157 + const CSRF_BREACH_PREFIX = 'B@'; 156 158 157 159 const EMAIL_CYCLE_FREQUENCY = 86400; 158 160 const EMAIL_TOKEN_LENGTH = 24; 159 161 160 - public function getCSRFToken($offset = 0) { 162 + private function getRawCSRFToken($offset = 0) { 161 163 return $this->generateToken( 162 164 time() + (self::CSRF_CYCLE_FREQUENCY * $offset), 163 165 self::CSRF_CYCLE_FREQUENCY, ··· 165 167 self::CSRF_TOKEN_LENGTH); 166 168 } 167 169 168 - public function validateCSRFToken($token) { 170 + /** 171 + * @phutil-external-symbol class PhabricatorStartup 172 + */ 173 + public function getCSRFToken() { 174 + $salt = PhabricatorStartup::getGlobal('csrf.salt'); 175 + if (!$salt) { 176 + $salt = Filesystem::readRandomCharacters(self::CSRF_SALT_LENGTH); 177 + PhabricatorStartup::setGlobal('csrf.salt', $salt); 178 + } 169 179 180 + // Generate a token hash to mitigate BREACH attacks against SSL. See 181 + // discussion in T3684. 182 + $token = $this->getRawCSRFToken(); 183 + $hash = PhabricatorHash::digest($token, $salt); 184 + return 'B@'.$salt.substr($hash, 0, self::CSRF_TOKEN_LENGTH); 185 + } 186 + 187 + public function validateCSRFToken($token) { 170 188 if (!$this->getPHID()) { 171 189 return true; 172 190 } 173 191 192 + $salt = null; 193 + 194 + $version = 'plain'; 195 + 196 + // This is a BREACH-mitigating token. See T3684. 197 + $breach_prefix = self::CSRF_BREACH_PREFIX; 198 + $breach_prelen = strlen($breach_prefix); 199 + 200 + if (!strncmp($token, $breach_prefix, $breach_prelen)) { 201 + $version = 'breach'; 202 + $salt = substr($token, $breach_prelen, self::CSRF_SALT_LENGTH); 203 + $token = substr($token, $breach_prelen + self::CSRF_SALT_LENGTH); 204 + } 205 + 174 206 // When the user posts a form, we check that it contains a valid CSRF token. 175 207 // Tokens cycle each hour (every CSRF_CYLCE_FREQUENCY seconds) and we accept 176 208 // either the current token, the next token (users can submit a "future" ··· 199 231 $csrf_window = 6; 200 232 201 233 for ($ii = -$csrf_window; $ii <= 1; $ii++) { 202 - $valid = $this->getCSRFToken($ii); 203 - if ($token == $valid) { 204 - return true; 234 + $valid = $this->getRawCSRFToken($ii); 235 + switch ($version) { 236 + // TODO: We can remove this after the BREACH version has been in the 237 + // wild for a while. 238 + case 'plain': 239 + if ($token == $valid) { 240 + return true; 241 + } 242 + break; 243 + case 'breach': 244 + $digest = PhabricatorHash::digest($valid, $salt); 245 + if (substr($digest, 0, self::CSRF_TOKEN_LENGTH) == $token) { 246 + return true; 247 + } 248 + break; 249 + default: 250 + throw new Exception("Unknown CSRF token format!"); 205 251 } 206 252 } 207 253
+5 -2
src/infrastructure/util/PhabricatorHash.php
··· 9 9 * @param string Input string. 10 10 * @return string 32-byte hexidecimal SHA1+HMAC hash. 11 11 */ 12 - public static function digest($string) { 13 - $key = PhabricatorEnv::getEnvConfig('security.hmac-key'); 12 + public static function digest($string, $key = null) { 13 + if ($key === null) { 14 + $key = PhabricatorEnv::getEnvConfig('security.hmac-key'); 15 + } 16 + 14 17 if (!$key) { 15 18 throw new Exception( 16 19 "Set a 'security.hmac-key' in your Phabricator configuration!");
+2
support/PhabricatorStartup.php
··· 59 59 if (!array_key_exists($key, self::$globals)) { 60 60 return $default; 61 61 } 62 + 62 63 return self::$globals[$key]; 63 64 } 64 65 ··· 370 371 private static function validateGlobal($key) { 371 372 static $globals = array( 372 373 'log.access' => true, 374 + 'csrf.salt' => true, 373 375 ); 374 376 375 377 if (empty($globals[$key])) {