I am looking for a way to add a CSRF token to an application I'm making. The caveat is, the application does not currently use cookies or sessions.
I would love to find a way to introduce a CSRF token without having to:
- Introduce state in my application.
- Use session or cookie (
$_SESSION
/ $_COOKIE
) storage
Is this at all a possibility, or am I stuck creating new state in my application.
You can, but it's not going to be very secure.
Here's an example BUT DO NOT USE THIS, IT IS PROBABLY A BAD IDEA:
// Do this somewhere define('CSRF_SALT', '4dedMj4CWgBMNhRsoTpJlokwB5wTz7UsmF8Mq4uzFIbv'); $token = base64_encode( hash_hmac( 'sha256', date('Ymd') . $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'], CSRF_SALT, true ) ); if (\hash_equals($_POST['security_token'], $token)) { // Form passes validation }
The downside is that these tokens are inherently reusable, so if one leaks an attacker can simply reuse (or recalculate) it. You can also try adding the form action="" value in the hash calculation.
function getToken($action) { return base64_encode( hash_hmac( 'sha256', hash_hmac( 'sha256', date('Ymd') . $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'], hash('sha256', $action, true), true ), CSRF_SALT, true ) ); } echo "<form action='register.php' method='post'>\n"; echo "<input type='hidden' name='security_token' value='".getToken('register.php')."' />\n"; // ...
What's your anathema for sessions anyway?
You can use a lightweight db and store a token, along with the ip adres, user agent, expiry time and requested url or you can try using a database and session storage to store a key with which tokens are created. E.g.
$post = array(); If (isset($_POST) && !empty($_POST)) { If (isset($_POST['csrftoken']) && isset($_SESSION['csrfkey'])) { // check if csrftoken exists in db and fetch the row $hash = hash('sha256', $row['String'] . $_SESSION['csrfKey']); // delete the db record If ($hash === $_POST['csrfToken']) { $post = (array) $_POST; // check if expiry time has not passed // check requested url // check user agent and ip adres } } $_REQUEST = null; $_POST = null; } If (!isset($_SESSION['csrfKey'])) { $_SESSION['csrfKey'] = uniqid('key_', true); } $string = uniqid('token_', true); $token = hash('sha256', $string . $_SESSION['csrfKey']); // save token and string along with expirytime in db echo "<input type='hidden' name='csrftoken' value='" . $token . "' />";
CSRF protection works by comparing a token embedded in a page with a token associated with the browser that the page was sent to.
It is absolutely impossible to do this without introducing state.