|
|
@@ -194,6 +194,26 @@ class CsrfProtectionMiddleware implements MiddlewareInterface
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * Verify CSRF token.
|
|
|
+ *
|
|
|
+ * @param string $token The CSRF token.
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+ protected function _verifyToken(string $token): bool
|
|
|
+ {
|
|
|
+ if (strlen($token) <= self::TOKEN_VALUE_LENGTH) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ $key = substr($token, 0, static::TOKEN_VALUE_LENGTH);
|
|
|
+ $hmac = substr($token, static::TOKEN_VALUE_LENGTH);
|
|
|
+
|
|
|
+ $expectedHmac = hash_hmac('sha1', $key, Security::getSalt());
|
|
|
+
|
|
|
+ return hash_equals($hmac, $expectedHmac);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* Add a CSRF token to the response cookies.
|
|
|
*
|
|
|
* @param string $token The token to add.
|
|
|
@@ -206,16 +226,7 @@ class CsrfProtectionMiddleware implements MiddlewareInterface
|
|
|
ServerRequestInterface $request,
|
|
|
ResponseInterface $response
|
|
|
): ResponseInterface {
|
|
|
- $cookie = Cookie::create(
|
|
|
- $this->_config['cookieName'],
|
|
|
- $token,
|
|
|
- [
|
|
|
- 'expires' => $this->_config['expiry'] ?: null,
|
|
|
- 'path' => $request->getAttribute('webroot'),
|
|
|
- 'secure' => $this->_config['secure'],
|
|
|
- 'httponly' => $this->_config['httpOnly'],
|
|
|
- ]
|
|
|
- );
|
|
|
+ $cookie = $this->_createCookie($token, $request);
|
|
|
if ($response instanceof Response) {
|
|
|
return $response->withCookie($cookie);
|
|
|
}
|
|
|
@@ -238,16 +249,25 @@ class CsrfProtectionMiddleware implements MiddlewareInterface
|
|
|
throw new InvalidCsrfTokenException(__d('cake', 'Missing CSRF token cookie'));
|
|
|
}
|
|
|
|
|
|
+ if (!$this->_verifyToken($cookie)) {
|
|
|
+ $exception = new InvalidCsrfTokenException(__d('cake', 'Missing CSRF token cookie'));
|
|
|
+
|
|
|
+ $expiredCookie = $this->_createCookie('', $request)->withExpired();
|
|
|
+ $exception->responseHeader('Set-Cookie', $expiredCookie->toHeaderValue());
|
|
|
+
|
|
|
+ throw $exception;
|
|
|
+ }
|
|
|
+
|
|
|
$body = $request->getParsedBody();
|
|
|
if (is_array($body) || $body instanceof ArrayAccess) {
|
|
|
- $post = Hash::get($body, $this->_config['field']);
|
|
|
- if ($this->_compareToken($post, $cookie)) {
|
|
|
+ $post = (string)Hash::get($body, $this->_config['field']);
|
|
|
+ if (hash_equals($post, $cookie)) {
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
$header = $request->getHeaderLine('X-CSRF-Token');
|
|
|
- if ($this->_compareToken($header, $cookie)) {
|
|
|
+ if (hash_equals($header, $cookie)) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -255,43 +275,25 @@ class CsrfProtectionMiddleware implements MiddlewareInterface
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Ensure that the request token matches the cookie value and that
|
|
|
- * both were generated by us.
|
|
|
+ * Create response cookie
|
|
|
*
|
|
|
- * @param mixed $post The request token.
|
|
|
- * @param mixed $cookie The cookie token.
|
|
|
- * @return bool
|
|
|
+ * @param string $value Cookie value
|
|
|
+ * @param \Psr\Http\Message\ServerRequestInterface $request The request object.
|
|
|
+ * @return \Cake\Http\Cookie\Cookie
|
|
|
*/
|
|
|
- protected function _compareToken($post, $cookie): bool
|
|
|
+ protected function _createCookie(string $value, ServerRequestInterface $request): Cookie
|
|
|
{
|
|
|
- if (!is_string($post)) {
|
|
|
- $post = '';
|
|
|
- }
|
|
|
- if (!is_string($cookie)) {
|
|
|
- $cookie = '';
|
|
|
- }
|
|
|
- $postKey = (string)substr($post, 0, static::TOKEN_VALUE_LENGTH);
|
|
|
- $postHmac = (string)substr($post, static::TOKEN_VALUE_LENGTH);
|
|
|
- $cookieKey = (string)substr($cookie, 0, static::TOKEN_VALUE_LENGTH);
|
|
|
- $cookieHmac = (string)substr($cookie, static::TOKEN_VALUE_LENGTH);
|
|
|
-
|
|
|
- // Put all checks in a list
|
|
|
- // so they all burn time reducing timing attack window.
|
|
|
- $checks = [
|
|
|
- hash_equals($postKey, $cookieKey),
|
|
|
- hash_equals($postHmac, $cookieHmac),
|
|
|
- hash_equals(
|
|
|
- $postHmac,
|
|
|
- hash_hmac('sha1', $postKey, Security::getSalt())
|
|
|
- ),
|
|
|
- ];
|
|
|
-
|
|
|
- foreach ($checks as $check) {
|
|
|
- if ($check !== true) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- }
|
|
|
+ $cookie = Cookie::create(
|
|
|
+ $this->_config['cookieName'],
|
|
|
+ $value,
|
|
|
+ [
|
|
|
+ 'expires' => $this->_config['expiry'] ?: null,
|
|
|
+ 'path' => $request->getAttribute('webroot'),
|
|
|
+ 'secure' => $this->_config['secure'],
|
|
|
+ 'httponly' => $this->_config['httpOnly'],
|
|
|
+ ]
|
|
|
+ );
|
|
|
|
|
|
- return true;
|
|
|
+ return $cookie;
|
|
|
}
|
|
|
}
|