Preventing Csrf in PHP

preventing csrf in php

To prevent CSRF you'll want to validate a one-time token, POST'ed and associated with the current session. Something like the following . . .

On the page where the user requests to delete a record:

confirm.php

<?php
session_start();
$token = isset($_SESSION['delete_customer_token']) ? $_SESSION['delete_customer_token'] : "";
if (!$token) {
// generate token and persist for later verification
// - in practice use openssl_random_pseudo_bytes() or similar instead of uniqid()
$token = md5(uniqid());
$_SESSION['delete_customer_token']= $token;
}
session_write_close();
?>
<html>
<body>
<form method="post" action="confirm_save.php">
<input type="hidden" name="token" value="<?php echo $token; ?>" />
Do you really want to delete?
<input type="submit" value=" Yes " />
<input type="button" value=" No " onclick="history.go(-1);" />
</form>
</body>
</html>

Then when it comes to actually deleting the record:

confirm_save.php

<?php
session_start();
// validate token
$token = isset($_SESSION['delete_customer_token']) ? $_SESSION['delete_customer_token'] : "";
if ($token && $_POST['token'] === $token) {
// delete the record
...
// remove token after successful delete
unset($_SESSION['delete_customer_token']);
} else {
// log potential CSRF attack.
}
session_write_close();
?>

The token should be hard to guess, unique for each delete request, accepted via $_POST only and expire after a few minutes (expiration not shown in this example).

How to prevent Cross-site request forgery (CSRF) effectively in PHP

Is this a good way to prevent CSRF?

Yes. What this does is to force the client to do a GET on the form before it can do a POST to your form handler. This prevents CSRF in modern browsers since browsers will prevent client-side Javascript to do an XHR GET request to a foreign domain, so a 3rd party cannot imitate your form on their site and successfully get a valid token for the submission.

When another page is opened as well that sets the same $_SESSION variable, the previous (still open) page becomes invalid, how to prevent this?

Allow several tokens to be valid at a time, keeping an array of valid tokens in the session. Alternatively, store no tokens at all and use a token signing scheme instead. I've dabbled in and explained that here. Alternative 2: just use a single token for the whole session, without invalidating tokens. (tip o' the hat to @SilverlightFox in the comments)

For forms this method is clear, but how to handle normal links? Is it necessary to append the token to each link as well?

No. You only need to protect POST requests since presumably only POST requests can alter sensitive data (wink wink nudge nudge, you're sticking to REST conventions, right?!) and XHR GET requests are already blocked browser-side.

Token for preventing CSRF attacks not validating

I presume that the submitted auth_token value is something random such as hwm7wherlwkju or whatever. Testing !$auth_token could give special results, depending if it's missing or if it contains "1", "true" or "".
Secondly, use !== instead of != to avoid automatic type casting in the comparaison.

So I would replace your "if" condition with this:

session_start();

// 1) Check if the recieved token is valid.
if (!isset($_POST['auth_token']) ||
!isset($_SESSION['auth_token']) ||
$_POST['auth_token'] !== $_SESSION['auth_token']) {
// Show an error message.
echo "<h1 class=\"error\">Error: invalid form submission</h1>\n" .
"<p>Your request was denied as this request could not be verified.</p>\n";
// Return a 403 error.
http_response_code(403);
die();
}

// 2) Generate a new token for the next request if you are displaying a page with a <form>.
$_SESSION['auth_token'] = bin2hex(random_bytes(20));

About the token value generated, I think you should also check that you are not generating a new value in the session on each request before doing the comparaison for validation. The comparaison should be done first and then a new token value should be generated and stored in the session.

Preventing CSRF with tokens in a AJAX based application

A simpler method than using tokens for an AJAX only might be to check headers that can only be present in an AJAX request from your own domain.

Two options are:

  • Checking the Origin Header
  • Checking the X-Requested-With header

The Origin header can also be used for normal HTML form submissions, and you would verify that this contains the URL of your own site before the form submission does its processing.

If you decide to check the X-Requested-With header, you will need to make sure it is added to each AJAX request client side (JQuery will do this by default). As this header cannot be sent cross domain (without your server's consent to the browser first), checking that this is present and set to your own value (e.g. "XMLHttpRequest") will verify that the request is not a CSRF attack.

CSRF (Cross-site request forgery) attack example and prevention in PHP

This could become an example of CSRF if :

  • that link is fetched (via an <img> tag, for example) : forgery
  • from another site : cross-site



For example, if I could inject this <img> tag in the HTML source-code of stackoverflow (and I can, as stackoverflow allows one to use <img> tags in his posts) :

<img src="http://mysite.com/vote/30" />

You would just have voted for that item ;-)



The solution that is generally used is to place a token, that has a limited life-time, in the URL, and, when the URL is fetched, check that this token is still valid.

The basic idea would be :

  • When generating the page :

    • generate a unique token
    • store it in the user's session
    • and place it in the links of the page -- which would look like this : http://mysite.com/vote/30?token=AZERTYUHQNWGST
  • When the voting page is called :

    • Check if the token is present in the URL
    • Check if it's present in the user's session
    • If not => do not register the vote

The idea there is :

  • Tokens don't have a long life-time, and are hard to guess
  • Which means your attacker :

    • has only a window of a few minutes during which his injection will be valid
    • will have to be good at guessing ^^
    • will have to generate a different page for each user.



Also, note that the shorter the user's session remains active after he has left your site, the less risks there are that it's still valid when he visits the bad website.

But here, you have to choose between security and user-friendly...



Another idea (that's not perfectly secure, but helps against guys would don't know how to force a POST request), would be to only accept POST requests when people are voting :

  • The browser is sending GET requests for injected tags
  • As this URL is modifying some data, anyway, it should not work with GET, but only with POST

But note that this is not perfectly safe : it's (probably ? ) possible to force/forge a POST request, with some bit of Javascript.

Best ways to protect against CSRF attacks in PHP

No. To protect against CSRF, you need to make sure you only trust credentials that are automatically appended to requests (like cookies) when you have reason to believe that the user actually submitted your form. The only way to do that is to have the form carry some kind of secret that the server uses to authorize processing.

If you use a framework or library to compose your form it might help by generating the random number, setting the cookie or session property, and adding the hidden input to your form but those three steps do need to happen.



Related Topics



Leave a reply



Submit