mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 06:42:42 +01:00
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
This commit is contained in:
parent
ab7a091212
commit
7298589c86
3 changed files with 58 additions and 7 deletions
|
@ -152,12 +152,14 @@ final class PhabricatorUser
|
||||||
}
|
}
|
||||||
|
|
||||||
const CSRF_CYCLE_FREQUENCY = 3600;
|
const CSRF_CYCLE_FREQUENCY = 3600;
|
||||||
|
const CSRF_SALT_LENGTH = 8;
|
||||||
const CSRF_TOKEN_LENGTH = 16;
|
const CSRF_TOKEN_LENGTH = 16;
|
||||||
|
const CSRF_BREACH_PREFIX = 'B@';
|
||||||
|
|
||||||
const EMAIL_CYCLE_FREQUENCY = 86400;
|
const EMAIL_CYCLE_FREQUENCY = 86400;
|
||||||
const EMAIL_TOKEN_LENGTH = 24;
|
const EMAIL_TOKEN_LENGTH = 24;
|
||||||
|
|
||||||
public function getCSRFToken($offset = 0) {
|
private function getRawCSRFToken($offset = 0) {
|
||||||
return $this->generateToken(
|
return $this->generateToken(
|
||||||
time() + (self::CSRF_CYCLE_FREQUENCY * $offset),
|
time() + (self::CSRF_CYCLE_FREQUENCY * $offset),
|
||||||
self::CSRF_CYCLE_FREQUENCY,
|
self::CSRF_CYCLE_FREQUENCY,
|
||||||
|
@ -165,12 +167,42 @@ final class PhabricatorUser
|
||||||
self::CSRF_TOKEN_LENGTH);
|
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()) {
|
if (!$this->getPHID()) {
|
||||||
return true;
|
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.
|
// 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
|
// Tokens cycle each hour (every CSRF_CYLCE_FREQUENCY seconds) and we accept
|
||||||
// either the current token, the next token (users can submit a "future"
|
// either the current token, the next token (users can submit a "future"
|
||||||
|
@ -199,10 +231,24 @@ final class PhabricatorUser
|
||||||
$csrf_window = 6;
|
$csrf_window = 6;
|
||||||
|
|
||||||
for ($ii = -$csrf_window; $ii <= 1; $ii++) {
|
for ($ii = -$csrf_window; $ii <= 1; $ii++) {
|
||||||
$valid = $this->getCSRFToken($ii);
|
$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) {
|
if ($token == $valid) {
|
||||||
return true;
|
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!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -9,8 +9,11 @@ final class PhabricatorHash {
|
||||||
* @param string Input string.
|
* @param string Input string.
|
||||||
* @return string 32-byte hexidecimal SHA1+HMAC hash.
|
* @return string 32-byte hexidecimal SHA1+HMAC hash.
|
||||||
*/
|
*/
|
||||||
public static function digest($string) {
|
public static function digest($string, $key = null) {
|
||||||
|
if ($key === null) {
|
||||||
$key = PhabricatorEnv::getEnvConfig('security.hmac-key');
|
$key = PhabricatorEnv::getEnvConfig('security.hmac-key');
|
||||||
|
}
|
||||||
|
|
||||||
if (!$key) {
|
if (!$key) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
"Set a 'security.hmac-key' in your Phabricator configuration!");
|
"Set a 'security.hmac-key' in your Phabricator configuration!");
|
||||||
|
|
|
@ -59,6 +59,7 @@ final class PhabricatorStartup {
|
||||||
if (!array_key_exists($key, self::$globals)) {
|
if (!array_key_exists($key, self::$globals)) {
|
||||||
return $default;
|
return $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::$globals[$key];
|
return self::$globals[$key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,6 +371,7 @@ final class PhabricatorStartup {
|
||||||
private static function validateGlobal($key) {
|
private static function validateGlobal($key) {
|
||||||
static $globals = array(
|
static $globals = array(
|
||||||
'log.access' => true,
|
'log.access' => true,
|
||||||
|
'csrf.salt' => true,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (empty($globals[$key])) {
|
if (empty($globals[$key])) {
|
||||||
|
|
Loading…
Reference in a new issue