I'm currently working on an implementation of a CSRF protection. What is the best practice when we detect a CSRF? Is it better to 404 the page, 403 (forbidden), 200 (OK) with an error message, something else?
Bonus : here's my code.
I'm not very proud of the preg_replace
and ob_*
. If you also have suggestions on how to do it better, I'll take that too.
- The
addCSRF
method is called just before sending the output of an HTML page. - The
checkCSRF
method is called when the server receives a request.
<?php
// This method checks if the content contains a form and adds a csrf_token hidden field
public static function addCSRF()
{
$content = ob_get_contents();
if (strlen($content))
{
// Random csrf token
$randomtoken = base64_encode(openssl_random_pseudo_bytes(32));
// Add the hidden input to the content if needed
$content = preg_replace('/(<([^>]*\s)?form(\s[^>]*)?>)/i', '\1<input type="hidden" name="csrf_token" value="'.$randomtoken.'" />', $content, -1, $count);
// If at least one input has been added, add the csrf_token value in the $_SESSION and replace the content
if ($count)
{
Session::set('csrf_token', $randomtoken);
// Echo the new content
ob_end_clean();
ob_start();
echo $content;
}
}
return;
}
// This method checks if a form has been submited and if the csrf token is given and valid
public static function checkCSRF()
{
// No form submitted
if (!isset($_POST))
return;
// CSRF detected
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] != Session::get('csrf_token'))
{
// 404 ? 403 ? 200 + error message ?
}
Session::forget('csrf_token');
}