Paper: Preventing Cross-Site Request Forgery (CSRF)Written by Nexus, PlayHack.netFriday, 2 November 2007 --------------------------[ PLAYHACK.net ]-------------------------------
-[ INFOS ]-------------------------------------------------------------------
Title: "Preventing CSRF"
Author: Nexus
Website: http://nexus.playhack.net
Date: 2007-10-25 (ISO 8601)
---------------------------------------------------------------------------------
-[ SUMMARY ]--------------------------------------------------------------
0x01: Hello World
0x02: Introduction
0x03: About Authentications
\_ 0x03a: Cookies Hashing
\_ 0x03b: HTTP Referer
\_ 0x03c: CAPTCHA Image
0x04: One-Time Tokens
0x05: Conclusions
---------------------------------------------------------------------------------
---[ 0x01: Hello World ]
Welcome to a fresh new inaugural paper for the new season of the Playhack.net Project! :) I'm really happy to see you back again and make our c00l project be back!
Hope you'll enjoy this new short paper and i invite you to visit the whole new project at:
http://www.playhack.net
Intake: actually nothing.. just a pair of cigarettes! :\
Shoutouts: all my shoutouts goes to my playhack m8s null, omni, god and emdel, ofc to str0ke! NEX IS BACK!
-----------------------------------------------------------------------------[/]
---[ 0x02: Introduction ]
I already dealt with the Cross Site Request Forgery topic, but not too deep regarding the possible solutions that web developers should adopt.
In these days i've been deeply involved in this topic during the coding of a distributed web application which should warrant a good level of security for user and most of all for the system's administrators (who are not that smart though their tasks :P).
Considering this situation i had to consider each aspect and each possible attack attempts that the applications could eventually suffer.
The one that gave me most problems to apply has been the Session Riding (or CSRF, call it as you prefere) because there is no 100% certain way to prevent that due to the concept that the attack fully stands on the users' credentials.
If you don't really know what Session Riding is you should read the previous paper reachable at the following link:
http://www.playhack.net/view.php?id=30
-----------------------------------------------------------------------------[/]
---[ 0X03: Possible Solutions ]
Ok, from here i must assume that you quite deeply know how a Session Riding attack should work out :P
Let's just have a little and fresh resume..
Consider that a trusted user is logged into a website which permits him to accomplish some important or confidential actions, an attacker wants to get over a possible login attack (which often will be voided) and disfrut the opened session of the user in order to realize some crafted actions.
The attacker in order to hijack the user's session will craft an appropriate web page which will hide a javascript function that re-create an original form but with fixed values, then he'll make the victim visit that page which on load will submit the form to the remote action page which will accomplish the request secretely (without the victim's aknowledge) confying on the user's trusted credentials.
This is a as quick as simple explaining of how a Session Riding attack would work out, the important question now is "How do i prevent my users to be victims of that?".
Now you would probably consider the following solutions:
- Check some cookies credentials
- Check the HTTP Referer
- Use a CAPTCHA
With some tryouts you'll realize that these are not the most proper solutions to adopt, let's see why one by one.
-----------------------------------------------------------------------------[/]
------[ 0x03a: Cookies Hashing ]
This first one could be a very simple and quick solutions to this problem because the attacker should not be aware of the victim's cookies contents, and cannot craft then a proper workaround.
An implementation of this solutions would work out in a way like following.
In some login file we create the Cookie related to the current session:
<!-- login.php -->
<?php
// Cookie value
$value = "Something from Somewhere";
// Create a cookie which expires in one hour
setcookie("cookie", $value, time()+3600);
?>
<!-- EOF -->
We use an hashing of the Cookie to make the form verified
<!-- form.php -->
<?php
// Hash the cookie
$hash = md5($_COOKIE['cookie']);
?>
<form method="POST" action="resolve.php">
<input type="text" name="first_name">
<input type="text" name="last_name">
<input type="hidden" name="check" value="<?=$hash;?>">
<input type="submit" name="submit" value="Submit">
</form>
<!-- EOF -->
And the action script would be something like following:
<!-- resolve.php -->
<?php
// Check if the "check" var exists
if(isset($_POST['check'])) {
$hash = md5($_COOKIE['cookie']);
// Check if the values coincide
if($_POST['check'] == $hash) {
do_something();
} else {
echo "Malicious Request!";
}
} else {
echo "Malicious Request!";
}
?>
<!-- EOF -->
Actually this would be a fine solution to CSRF if we wouldn't consider the fact that the user's cookie are a way easy to retrieve disfruting some XSS flaw in the website (that we previousy saw they are not a rarity :D). It would be more secure if we create and destroy a random cookie for each form request that the user makes, but it wouldn't be very comfortable.
-----------------------------------------------------------------------------[/]
------[ 0x03b: HTTP Referer ]
The easiest way to check if the incoming requests are trusted and allowed, is to disfrut the HTTP Referer and check if they come from the same website or from a remote malicious page: this would be a great solution, but it falls due to the possibility to spoof the referers and make them appear to be corrects.
Let's see why it's not a proper solution..
The following code shows an implementing example of HTTP Referer:
<!-- check.php -->
if(eregi("www.playhack.net", $_SERVER['HTTP_REFERER'])) {
do_something();
} else {
echo "Malicious Request!";
}
<!-- EOF -->
This check can be easily bypassed forging a fake HTTP Referer from the attacker's script using something like:
header("Referer: www.playhack.net");
Or any other way to forge the Headers to be sent from the malicious script.
Since the HTTP Referer is sent by the browser and not controlled by the server you should never consider this variable as a trusted source!
-----------------------------------------------------------------------------[/]
------[ 0x03c: CAPTCHA Image ]
Another idea that came out in order to solve this issue is to use a random CAPTCHA image in every form which require the user to type in a textbox the generated string and with that verify the integrity of the submitted datas and the credentials of the user.
This solutions has been discarded some time ago due to the possibility to retrieve the captcha image using the so called MHTML bug, which affected several versions of Microsoft Internet Explorer.
You can find all the specific informations about this vulnerability from the Secuniawebsite:
http://secunia.com/advisories/19738/
Here's an extract from the Secunia's explanation of the bug:
"The vulnerability is caused due to an error in the handling of redirections for URLs with the 'mhtml:' URI handler. This can be exploited to access documents served from another web site."
In the same page you can find also a web test made from Secunia Staff.
Actually this bug is known to be solved with several patches released by Microsoft for Windows XP and Windows Vista and with the release 6.0 of their own browser Internet Explorer.
Even if actually it appears to be secure, the times brought to others possible and theoretically more reliable solutions.
-----------------------------------------------------------------------------[/]
---[ 0x04: One-Time Tokens ]
Now let's explain the final solution i decided to adopt for my job: after having dealt with all of these unreliable techniques i tried to write something different and probably more effective.
In order to make the webforms safe from Session Riding (CSRF) i decided to make the checking free from any kind of item that could be spoofed, retrieved or faked.
So i needed to create some one-time tokens that cannot be guessed or retrived in any way and that after having accomplished their task would have been destroyed.
Let's start from the token value generation:
<!-- start function -->
<?php
function gen_token() {
// Generate the md5 hash of a randomized uniq id
$hash = md5(uniqid(rand(), true));
// Select a random number between 1 and 24 (32-8)
$n = rand(1, 24);
// Generate the token retrieving a part of the hash starting from
// the random N number with 8 of lenght
$token = substr($hash, $n, 8);
return $token;
}
?>
<!-- EOF -->
The uniqid() PHP function allows the web developer to get a unique ID from the current time in microseconds, which is quite good in order to retrieve a value that won't be repeated again.
We retrieve the MD5 hashing of that ID, and then we select 8 characters from that hashing starting from a random number <=24 (strlen($hash)-8).
The returned $token variable will retrieve an 8-long randomized token.
Let's now generate a Session Token which will be used later for the last check:
<!-- start function -->
<?php
function gen_stoken() {
// Call the function to generate the token
$token = gen_token();
// Destroy any eventually Session Token variable
destroy_stoken();
// Create the Session Token variable
session_register(STOKEN_NAME);
$_SESSION[STOKEN_NAME] = $token;
}
?>
<!-- EOF -->
With this function we call the gen_token() function and use the returned token to copy his value into a new $_SESSION variable.
Now let's see the function that will start the whole mechanism and that generates the hidden input for our form:
<!-- start function -->
<?php
function gen_input() {
// Call the function to generate the Session Token variable
gen_stoken();
// Generate the form input code
echo "<input type=\"hidden\" name=\"" . FTOKEN_NAME . "\"
value=\"" . $_SESSION[STOKEN_NAME] . "\">\n";
}
?>
<!-- EOF -->
As we can see this function just call the gen_stoken() function and create the HTML code for the hidden input that will be included in the web form.
Let's now take a look to the function that make the check of the Session Token with the submitted hidden input:
<!-- start function -->
<?php
function token_check() {
// Check if the Session Token exists
if(is_stoken()) {
// Check if the request has been sent
if(isset($_REQUEST[FTOKEN_NAME])) {
// If the Form Token is different from Session Token
// it's a malicious request
if($_REQUEST[FTOKEN_NAME] != $_SESSION[STOKEN_NAME]) {
gen_error(1);
destroy_stoken();
exit();
} else {
destroy_stoken();
}
// If it isn't then it's a malicious request
} else {
gen_error(2);
destroy_stoken();
exit();
}
// If it isn't then it's a malicious request
} else {
gen_error(3);
destroy_stoken();
exit();
}
}
?>
<!-- EOF -->
This function check the existence of the $_SESSION[STOKEN_NAME] and of $_REQUEST[FTOKEN_NAME] (i used the $_REQUEST method in order to accept both GET and POST methods from the form) and check if their values are the same: if they are the submitted form is authorized.
The important point of this function is that at every concluding step the tokens get destroyed and will be recreated only at next web form page call.
The use of these functions is really simple we just need to just add some additional PHP instructions.
This is the webform:
<!-- form.php -->
<?php
session_start();
include("functions.php");
?>
<form method="POST" action="resolve.php">
<input type="text" name="first_name">
<input type="text" name="last_name">
<!-- Call the function to generate the hidden input -->
<? gen_input(); ?>
<input type="submit" name="submit" value="Submit">
</FORM>
<!-- EOF -->
And this is the resolving script:
<!-- resolve.php -->
<?php
session_start();
include("functions.php");
// Call the function to make the check
token_check();
// Your code
...
?>
<!-- EOF -->
As you can see is really simple to implement such a check and it should protect your users from being hijacked by attackers and avoid to get your data compromised.
-----------------------------------------------------------------------------[/]
---[ 0x05: Conclusions ]
Let's conclude this short paper saying that there is no 100% way to be secure that your web application is completely safe, but you can start avoiding the most common attacking techniques.
Another point that i'd like to make you focus on is that a web developer SHOULD NOT forget of general applications faults (like XSS Browser Bugs ecc..), it would be a great mistake not considering them as a potential threat for your users: you should always keep in mind everything that could compromise your application's integrity, security and interoperability.
Cya!
nexus
-----------------------------------------------------------------------------[/]
\======================================[EOF]=====================================/
Original Link to Tutorial:
http://www.playhack.net/view.php?id=31
Related Paper:
http://www.xssed.com/article/4/Paper_Preventing_CSRF_Attacks/ - By Petko D. Petkov (pdp) | GNUCitizen.org
Share this content:
|