Browse Source

Add a easy to use method for regenerating Session CSRF Tokens

Currently there isn't a good way to regenerate a CSRF token during
login/logout. Rotating CSRF tokens can help mitigate the possibility
of replay attacks if an application cannot rotate session id/cookies
during login.
Mark Story 2 years ago
parent
commit
771ac54c72

+ 21 - 0
src/Http/Middleware/SessionCsrfProtectionMiddleware.php

@@ -18,6 +18,7 @@ namespace Cake\Http\Middleware;
 
 use ArrayAccess;
 use Cake\Http\Exception\InvalidCsrfTokenException;
+use Cake\Http\ServerRequest;
 use Cake\Http\Session;
 use Cake\Utility\Hash;
 use Cake\Utility\Security;
@@ -268,4 +269,24 @@ class SessionCsrfProtectionMiddleware implements MiddlewareInterface
             'CSRF token from either the request body or request headers did not match or is missing.'
         ));
     }
+
+    /**
+     * Replace the token in the provided request.
+     *
+     * Replace the token in the session and request attribute. Replacing
+     * tokens is a good idea during privilege escalation or privilege reduction.
+     *
+     * @param \Cake\Http\ServerRequest $request The request to update
+     * @param string $key The session key/attribute to set.
+     * @return \Cake\Http\ServerRequest An updated request.
+     */
+    public static function replaceToken(ServerRequest $request, string $key = 'csrfToken'): ServerRequest
+    {
+        $middleware = new SessionCsrfProtectionMiddleware(['key' => $key]);
+
+        $token = $middleware->createToken();
+        $request->getSession()->write($key, $token);
+
+        return $request->withAttribute('csrfToken', $middleware->saltToken($token));
+    }
 }

+ 21 - 0
tests/TestCase/Http/Middleware/SessionCsrfProtectionMiddlewareTest.php

@@ -413,4 +413,25 @@ class SessionCsrfProtectionMiddlewareTest extends TestCase
         }
         $this->assertCount(10, array_unique($results));
     }
+
+    /**
+     * Ensure that tokens can be regenerated
+     */
+    public function testRegenerateToken(): void
+    {
+        $request = new ServerRequest([
+            'url' => '/articles/',
+        ]);
+        $updated = SessionCsrfProtectionMiddleware::replaceToken($request);
+        $session = $updated->getSession()->read('csrfToken');
+        $this->assertNotEmpty($session);
+
+        $attribute = $updated->getAttribute('csrfToken');
+        $this->assertNotEmpty($attribute);
+        $this->assertNotEquals($session, $attribute, 'Should not be equal because of salting');
+
+        $updated = SessionCsrfProtectionMiddleware::replaceToken($request, 'custom-key');
+        $this->assertNotEmpty($updated->getSession()->read('custom-key'));
+        $this->assertNotEmpty($updated->getAttribute('custom-key'));
+    }
 }