Changeset View
Changeset View
Standalone View
Standalone View
src/applications/people/storage/PhabricatorUser.php
| Show First 20 Lines • Show All 359 Lines • ▼ Show 20 Lines | public function getCSRFToken() { | ||||
| // discussion in T3684. | // discussion in T3684. | ||||
| $token = $this->getRawCSRFToken(); | $token = $this->getRawCSRFToken(); | ||||
| $hash = PhabricatorHash::digest($token, $salt); | $hash = PhabricatorHash::digest($token, $salt); | ||||
| return self::CSRF_BREACH_PREFIX.$salt.substr( | return self::CSRF_BREACH_PREFIX.$salt.substr( | ||||
| $hash, 0, self::CSRF_TOKEN_LENGTH); | $hash, 0, self::CSRF_TOKEN_LENGTH); | ||||
| } | } | ||||
| public function validateCSRFToken($token) { | public function validateCSRFToken($token) { | ||||
| $salt = null; | // We expect a BREACH-mitigating token. See T3684. | ||||
| $version = 'plain'; | |||||
| // This is a BREACH-mitigating token. See T3684. | |||||
| $breach_prefix = self::CSRF_BREACH_PREFIX; | $breach_prefix = self::CSRF_BREACH_PREFIX; | ||||
| $breach_prelen = strlen($breach_prefix); | $breach_prelen = strlen($breach_prefix); | ||||
| if (strncmp($token, $breach_prefix, $breach_prelen) !== 0) { | |||||
| return false; | |||||
| } | |||||
| if (!strncmp($token, $breach_prefix, $breach_prelen)) { | |||||
| $version = 'breach'; | |||||
| $salt = substr($token, $breach_prelen, self::CSRF_SALT_LENGTH); | $salt = substr($token, $breach_prelen, self::CSRF_SALT_LENGTH); | ||||
| $token = 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" | ||||
| // token if you have two web frontends that have some clock skew) or any of | // token if you have two web frontends that have some clock skew) or any of | ||||
| // the last 6 tokens. This means that pages are valid for up to 7 hours. | // the last 6 tokens. This means that pages are valid for up to 7 hours. | ||||
| // There is also some Javascript which periodically refreshes the CSRF | // There is also some Javascript which periodically refreshes the CSRF | ||||
| // tokens on each page, so theoretically pages should be valid indefinitely. | // tokens on each page, so theoretically pages should be valid indefinitely. | ||||
| Show All 14 Lines | public function validateCSRFToken($token) { | ||||
| // or act as the user [xss]) the 7 hour default seems like a reasonable | // or act as the user [xss]) the 7 hour default seems like a reasonable | ||||
| // balance. Other major platforms have much longer CSRF token lifetimes, | // balance. Other major platforms have much longer CSRF token lifetimes, | ||||
| // like Rails (session duration) and Django (forever), which suggests this | // like Rails (session duration) and Django (forever), which suggests this | ||||
| // is a reasonable analysis. | // is a reasonable analysis. | ||||
| $csrf_window = 6; | $csrf_window = 6; | ||||
| for ($ii = -$csrf_window; $ii <= 1; $ii++) { | for ($ii = -$csrf_window; $ii <= 1; $ii++) { | ||||
| $valid = $this->getRawCSRFToken($ii); | $valid = $this->getRawCSRFToken($ii); | ||||
| switch ($version) { | |||||
| // TODO: We can remove this after the BREACH version has been in the | |||||
| // wild for a while. | |||||
epriestley: The extra changes here are just following through with this and removing support for the plain… | |||||
| case 'plain': | |||||
| if ($token == $valid) { | |||||
| return true; | |||||
| } | |||||
| break; | |||||
| case 'breach': | |||||
| $digest = PhabricatorHash::digest($valid, $salt); | $digest = PhabricatorHash::digest($valid, $salt); | ||||
| if (substr($digest, 0, self::CSRF_TOKEN_LENGTH) == $token) { | $digest = substr($digest, 0, self::CSRF_TOKEN_LENGTH); | ||||
| if (phutil_hashes_are_identical($digest, $token)) { | |||||
| return true; | return true; | ||||
| } | } | ||||
| break; | |||||
| default: | |||||
| throw new Exception(pht('Unknown CSRF token format!')); | |||||
| } | |||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| private function generateToken($epoch, $frequency, $key, $len) { | private function generateToken($epoch, $frequency, $key, $len) { | ||||
| if ($this->getPHID()) { | if ($this->getPHID()) { | ||||
| $vec = $this->getPHID().$this->getAccountSecret(); | $vec = $this->getPHID().$this->getAccountSecret(); | ||||
| ▲ Show 20 Lines • Show All 885 Lines • Show Last 20 Lines | |||||
The extra changes here are just following through with this and removing support for the plain CSRF tokens. The BREACH tokens have been in the wild for a little over a year, now.