Page MenuHomePhabricator

D6686.id15012.diff
No OneTemporary

D6686.id15012.diff

Index: src/applications/people/storage/PhabricatorUser.php
===================================================================
--- src/applications/people/storage/PhabricatorUser.php
+++ src/applications/people/storage/PhabricatorUser.php
@@ -152,12 +152,14 @@
}
const CSRF_CYCLE_FREQUENCY = 3600;
+ const CSRF_SALT_LENGTH = 8;
const CSRF_TOKEN_LENGTH = 16;
+ const CSRF_BREACH_PREFIX = 'B@';
const EMAIL_CYCLE_FREQUENCY = 86400;
const EMAIL_TOKEN_LENGTH = 24;
- public function getCSRFToken($offset = 0) {
+ private function getRawCSRFToken($offset = 0) {
return $this->generateToken(
time() + (self::CSRF_CYCLE_FREQUENCY * $offset),
self::CSRF_CYCLE_FREQUENCY,
@@ -165,12 +167,42 @@
self::CSRF_TOKEN_LENGTH);
}
- public function validateCSRFToken($token) {
+ /**
+ * @phutil-external-symbol class PhabricatorStartup
+ */
+ public function getCSRFToken() {
+ $salt = PhabricatorStartup::getGlobal('csrf.salt');
+ if (!$salt) {
+ $salt = Filesystem::readRandomCharacters(self::CSRF_SALT_LENGTH);
+ PhabricatorStartup::setGlobal('csrf.salt', $salt);
+ }
+ // Generate a token hash to mitigate BREACH attacks against SSL. See
+ // discussion in T3684.
+ $token = $this->getRawCSRFToken();
+ $hash = PhabricatorHash::digest($token, $salt);
+ return 'B@'.$salt.substr($hash, 0, self::CSRF_TOKEN_LENGTH);
+ }
+
+ public function validateCSRFToken($token) {
if (!$this->getPHID()) {
return true;
}
+ $salt = null;
+
+ $version = 'plain';
+
+ // This is a BREACH-mitigating token. See T3684.
+ $breach_prefix = self::CSRF_BREACH_PREFIX;
+ $breach_prelen = strlen($breach_prefix);
+
+ if (!strncmp($token, $breach_prefix, $breach_prelen)) {
+ $version = 'breach';
+ $salt = substr($token, $breach_prelen, self::CSRF_SALT_LENGTH);
+ $token = substr($token, $breach_prelen + self::CSRF_SALT_LENGTH);
+ }
+
// When the user posts a form, we check that it contains a valid CSRF token.
// Tokens cycle each hour (every CSRF_CYLCE_FREQUENCY seconds) and we accept
// either the current token, the next token (users can submit a "future"
@@ -199,9 +231,23 @@
$csrf_window = 6;
for ($ii = -$csrf_window; $ii <= 1; $ii++) {
- $valid = $this->getCSRFToken($ii);
- if ($token == $valid) {
- return true;
+ $valid = $this->getRawCSRFToken($ii);
+ switch ($version) {
+ // TODO: We can remove this after the BREACH version has been in the
+ // wild for a while.
+ case 'plain':
+ if ($token == $valid) {
+ return true;
+ }
+ break;
+ case 'breach':
+ $digest = PhabricatorHash::digest($valid, $salt);
+ if (substr($digest, 0, self::CSRF_TOKEN_LENGTH) == $token) {
+ return true;
+ }
+ break;
+ default:
+ throw new Exception("Unknown CSRF token format!");
}
}
Index: src/infrastructure/util/PhabricatorHash.php
===================================================================
--- src/infrastructure/util/PhabricatorHash.php
+++ src/infrastructure/util/PhabricatorHash.php
@@ -9,8 +9,11 @@
* @param string Input string.
* @return string 32-byte hexidecimal SHA1+HMAC hash.
*/
- public static function digest($string) {
- $key = PhabricatorEnv::getEnvConfig('security.hmac-key');
+ public static function digest($string, $key = null) {
+ if ($key === null) {
+ $key = PhabricatorEnv::getEnvConfig('security.hmac-key');
+ }
+
if (!$key) {
throw new Exception(
"Set a 'security.hmac-key' in your Phabricator configuration!");
Index: support/PhabricatorStartup.php
===================================================================
--- support/PhabricatorStartup.php
+++ support/PhabricatorStartup.php
@@ -59,6 +59,7 @@
if (!array_key_exists($key, self::$globals)) {
return $default;
}
+
return self::$globals[$key];
}
@@ -370,6 +371,7 @@
private static function validateGlobal($key) {
static $globals = array(
'log.access' => true,
+ 'csrf.salt' => true,
);
if (empty($globals[$key])) {

File Metadata

Mime Type
text/plain
Expires
Sun, Mar 16, 12:11 AM (15 h, 42 m ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7223514
Default Alt Text
D6686.id15012.diff (4 KB)

Event Timeline