I want to use a token to prevent CSRF attacks on my website (written with PHP). I've used it in forms and it works well. But logout link is not a form; It is only a hyperlink.
Is it secure if I put the token in the query string like this:
<a href="logout.php?token=9ae328eea8a72172a2426131a6a41adb">Logout</a>
If it has any problem, what is your suggestions and solutions ?
I think one of main disadvantages of using CSRF-token in GET requests is possibility of incompetent user to easily disclose his token by copying a link with the token and paste it in some public content like a comment/post/etc... Also GET query parameters including CSRF-tokens usually logged by HTTP servers/proxies and it introduces another risk.
So I suggest you to implement CSRF-secure links using something like this:
<form name="logout" action="logout.php" method="post">
<input type="hidden" name="token" value="9ae328eea8a72172a2426131a6a41adb"/>
</form>
...
<a href="/nojs.html" onclick="document.logout.submit(); return false">Logout</a>
Yes, if the CSRF token is 'unguessable' and validated: the approach is the same in both cases.
From Wikipedia's Cross-site Request Forgery - Prevention:
Web sites have various CSRF countermeasures available .. Requiring a secret, user-specific token in all form submissions and side-effect URLs prevents CSRF; the attacker's site cannot put the right token in its submissions.
It doesn't matter if the token is from a form value or a query string parameter1. An approach that prevents CSRF by including a token in forms is adaptable to (and valid for) hyperlinks2.
1 A MitM / proxy which can intercept a URL can just as easily intercept an HTML form. This is outside the scope of a standard CSRF attack or mitigiation of such. In such cases the CSRF token value is 'knowable' and system is not secure.
2 This assumes the token is a per-user (and time-sensitive) value. A simple HMAC hash of the Session ID should be sufficient in most cases.
Others made some good points. I add this answer to augment theirs.
Always filter and validate your query string name/value pairs.
Given: You have a database and you want to create a link to help dynamically get (but not change) content that a user can link to. Example: news articles, case studies, public user profiles.
Requirement: Use a query string and deter CSRF by using a token.
One of the purposes of a CSRF token is to help verify that an incoming HTTP request was generated from a page served from your domain (regardless if sessions are in play. The session token is a separate beast). A CSRF token works best when you use more than one defense vector.
Comparison: Check that a submitted token matches one in session.
Time: You can specify a token is only good for a certain period into the future.
public function setFormToken()
{
$token = $this->cipher->getFormToken(); //Some hashing algorithm.
$this->formToken = $token; //In this example, used to insert the token into HTML.
$_SESSION['token'] = $token; //Save the token for comparison upon form submission / HTTP query string processing.
$_SESSION['tokenExpireTime'] = time() + (60 * FORM_TOKEN_EXPIRE_MINUTES); //This is just an abstract example.
return;
}
Then, in addition to comparing the submitted token to the one in session, verify that the submission period is still valid.
private function hasTokenTimeLeft()
{
if(isset($_SESSION['tokenExpireTime']) && (time() < $_SESSION['tokenExpireTime']))
{
return true;
}
throw new SecurityException("Token time has expired for this POST request.", 911);
}
Sessions: If a session has expired, or is non-existent, the a false HTTP request for your content should fail.
Request Info: With HTTP POST request, some attempt to check that the token, user agent, and IP match the one from the original HTTP GET request (which means storing it in session before responding to the GET request).
Query strings, as previously mentioned, can get cached. There is no problem with that if the data is supposed to be publicly available, anyway. Think about someone bookmarking a product on an e-commerce website.
What you have to ask yourself is "Should I be able to get to this content from anywhere, anytime, through this link with a query string?" If yes, do not use a true, backend, randomly generated, CSRF token. Imagine you are running for elected office by word of mouth and people sending links to their friends and family in email (ok, bad practice). In this case, you never want to set off a "trip wire" and cause a fail condition. If a fresh token always needs to be generated on the backend first, emailing your link around will not work.
If you should not be able to get to content from outside of your security context (i.e., before logging in), then you are free to take whatever measures are necessary to fortify your CSRF token strategy using query strings.
Your log out snippet is a perfect example.
<a href="logout.php?token=9ae328eea8a72172a2426131a6a41adb">Logout</a>
No one from outside your security context should be able to use the logout feature. You want the "trip wire" if someone emails this link around! Obviously, sessions are integrated into this solution (the token has to be stored somewhere while the user uses the page). Session duration and logging out should be managed carefully, and I say you are doing a good job.
Encryption: The only thing you could do better is encrypt the hash/token for the query string, then decrypt it and verify it upon submission. Additionally, you can break the token up into pieces, mix the pieces up, and basically use some security by obscurity string techniques to make your CSRF token more resilient.