When on a penetration testing engagement you’ll often come across anti-CSRF tokens. In my experience, many testers will see the token and assume that CSRF has been mitigated. However, the value of anti-CSRF tokens isn’t the token, it’s what’s done with the token. There are two parts to this mitigation: the token is generated and sent to the server, and the server validates the token properly.
It would be reasonable to assume that developers implement both the token and the server-side check because that would be common sense, right? However, I can’t stress enough that common sense isn’t so common these days, and assumptions are just that, assumptions. Until you have data to support those assumptions, they’re wrong. As a penetration tester, it helps to have the mindset that you’re trying to prove assumptions wrong. Remember, as a penetration tester, you’re (in a controlled manner) imitating an attacker. An attacker will assume everything is vulnerable until they have reason to believe it’s not, and even then a motivated attacker will find a vulnerability out of a secure implementation. And you can’t ignore the context of many development projects. There’s often a lot of pressure to write a lot of code very quickly and to meet insane deliverables. The manager or director might stuck in the 90s and argues nobody will ever try to attack the business since it’s not a bank, or that CSRF attacks don’t happen in the wild (I’ve heard this before). Never lose sight of the social, political and human aspects of development, and use it to identify and create exploits.
Check 1: Remove the anti-CSRF token parameter
When doing a POST request, remove the anti-CSRF token parameter. View the response header and see if the request is accepted or rejected. If the request is accepted, the server doesn’t validate the anti-CSRF token and there’s no protection. Creating an exploit would be trivial from this point on. If the request is rejected, move to Check 2.
Check 2: Remove the token from the parameter
Now we know that if the parameter is remove, the server rejects the request. However, the server may just be checking for the presence of the parameter. By blanking the token value, we can check to see if this is true or not. If the request with the blank token value is accepted, then we know the server is checking for something, but not the right thing, and creating an exploit would be trivial. If the request is rejected, move to Check 3.
Check 3: Use random, different length token values
At this point, we know that if the parameter is removed or a null token value is used, the request will be rejected. Now we want to know if the server is validating based on some token value. First, see if the request is accepted by using a token value such as “11111”, or any value that is shorter than the server’s token length. If it’s accepted, then the server is simply checking to see the parameter is there and there’s some non-null value. Again, creating an exploit would be trivial from here. If it’s rejected, we then know that, at the very least, the server is checking to see that the token parameter is there, it’s a not null, and it’s not accepting a shorter, random value. We can then try a random value of the same length as the server’s token. If it’s accepted, then we know the server-side token validation checks for token length only, and creating an exploit would be trivial. If it’s rejected, move to Check 4.
Check 4: Use another user’s token
Create 2 user accounts and start a unique session for each. Take the token from account 1 and try a POST from account 2’s session. If the request is accepted, we know that the server is validating the token based on it being a valid token, and not a valid token for account 2’s session. In this case, creating an exploit would be trivial, because generating a valid token would be incredibly simple and quick. I see this happening a lot more than I’d like, but it shows that this security implementation is usually half-done, because the managers or developers either don’t understand the mechanism or assume they will never be targeted (because, of course, it won’t ever happen to you, though that’s the same thing every person who has been targeted said until they were). If the request is rejected, then the server is validating the anti-CSRF token correctly, and an exploit would require much more time and consideration than a textbook CSRF, or it would have to come in another form.
Also check for uniqueness
Check to see if tokens are unique. Just because a token is long and complex doesn’t mean it’s unique, it just means it’s long and complex. I’ve come across a few implementations where the token is validated by the server, but the token is always the same. The developer must have thought a long and complex, but re-used, token would somehow mitigate CSRF, which it doesn’t. Again, don’t assume tokens aren’t being re-used and check it.
Just follow the recommendations on the OWASP CSRF Cheat Sheet. It’s good stuff.