Preventing Cross-Site Request Forgeries in PHP February 16, 2012
Posted by Tournas Dimitrios in PHP.trackback
Cross-site request forgery (CSRF , pronounced sea-surf ) is a common and serious exploit where a user executes unwanted actions on a web application in which he/she is currently authenticated (loged-in) . With a little help of social engineering (like sending a link via email/chat) , an attacker may trick the users of a web application to execute actions of the attacker’s choosing with the privileges of the already logged-in user account . If the user where logged-in as administrator , the attacker can compromise the entire web application . There is one golden rule a web-developer shouldn’t forget , NEVER TRUST SUBMITTED DATA , always filter – sanitize and check submitted user-data . This article will present a simple example that demonstrates the basic concepts of CSRF attacks and how to prevent them .
Other serious attacks a developer should be aware off are : XSS and SQL-injection which deserves an separate article .
The most popular ways to execute CSRF attacks is by using a HTML image tag , or JavaScript image object . Typically an attacker will embed these into an email or website so when the user loads the page or email , they perform a web request to any URL of the attackers liking . Below is a list of the common ways that an attacker may try sending a request .
- IMG SRC : <img src=”http://domain.com?command”>
- SCRIPT SRC : // <![CDATA[
src=”http://domain.com?command”>
// ]]> - IFRAME SRC : <iframe src=”http://host/?command”>
- JavaScript Image Methods :
<script>
var value = new Image();
value.src = “http://somedomain.com?command”;
</script>
Vulnerability isn’t limited to browsers , an attacker could embed scripting into a word document , RSS , Flash File , Movie , or Atom web feed , or other document format allowing scripting . Applications utilizing XML documents use XML parsers to quickly parse through data . Certain tags within an XML document may tell the XML parser to request additional documents from a URI . Browsers will be the dominant way to execute these attacks but aren’t the only way .
What can I do to protect myself as an user ? Simply , don’t relay on “the good-will” of the developer of sites you visit , he/she might or might-not have implemented security measures to protect you . Especially for critical administration tasks or login-accounts use an unique browser and switch browser (from Chrome to FF) for all other Internet surfing tasks (as cookies are related to which browser was used to access a website ) .
What can a web-developer do to protect a web-applications ? The most popular suggestion to preventing CSRF involves appending non predictable tokens to each request . It is important to state that this token MUST be associated with the user session , otherwise an attacker may be able to fetch a valid token on their own and utilize it in an attack . In addition to being tied to the user session it is important to limit the time period to which a token is valid .
To understand how a CSRF attack works , it’s best to see it in action . The first script “login.php” has an html form field that captures user’s credentials (username/ password) . Submitting the form will send them to “process.php” script , it will validate the submitted credentials , and if valid (correct username / password) user’s name will be recorded to the $_SESSION[“user”] variable . Finaly the user will automaticaly redirected to “login.php” which will display a welcome message with user’s name (retrieved from $_SESSION[“user”] array ) . When user clicks on “log-out” hyperlink , process.php will destroy the $_SESSION[“user”] array and the form field will be displayed again .
<?php //the code for : login.php: ?> <html> <body> <?php session_start(); if (isset($_SESSION["user"])) { echo "<p>Welcome back, " . $_SESSION["user"] . "!<br/>"; echo '<a href="process.php?action=logout">Logout</a></p>'; } else { ?> <p> The username is: JohnDoe <br> The password is: xyz!@# </p> <form action="process.php?action=login" method="post"> <input type="text" name="user" size="20"/> <input type="password" name="pass" size="20"/> <input type="submit" value="Login"/> </form> <?php } ?> </body> </html> <?php //The code for : process.php session_start(); switch($_GET["action"]) { case "login": if ($_SERVER["REQUEST_METHOD"] == "POST") { $user = (isset($_POST["user"]) && ctype_alnum($_POST["user"]) ? $_POST["user"] : null ; $pass = (isset($_POST["pass"])) ? $_POST["pass"] : null ; $salt = '$a$Gfyew@!hjerifdggh#$uyTT$'; if (isset($user, $pass) && (crypt($user . $pass, $salt) == crypt("JohnDoexyz!@#", $salt))) { $_SESSION["user"] = $_POST["user"]; } } break; case "logout": $_SESSION = array(); session_destroy(); break; } header("Location: login.php"); ?>
Now let’s pretend the following scenario : a user is logged-into his website that implements the aforementioned authentication mechanism and opens another tab on his/her browser . The new browser tab is linked to an attackers site that is hosted on a different domain/server . The attackers site has an “img” tag with an source-code like :
<html> <body> <!-- This site pretent to be the attackers site --> <img src="http://victim.com/process.php?action=logout" style="display: none;"/> <!-- Here goes all content --> <h1> Hello visitor , welcome to this site </h1> </body> </html>
The browser sends a request to the user’s process.php script , expecting it to be an image file . The processing script has no way of differentiating between a valid request initiated by user’s original site (he/she has clicked on the logout link) and a cleverly crafted request the browser was tricked into sending . Even hosted on an entirely different server the attacker can pass $GET-values to user’s process.php script and resetting it’s session values , it still works because the attacker’s page is making a request on user’s behalf using session variables for the domain he/she already is logged-in in the background . Cookies are related to domain level and always send in the background when a request is made by an browser .
Preventing CSRF attacks :
In order to ensure that an browser-request originates from server’s domain pages rather a third party domain (attacker) , you need to identify each form with an unique random string (token) which is placed into a hidden form field and into a cookie . On each browser-request , the hidden-field- token is compared with it’s associated token stored in the cookie . If these two tokens are similar the request is verified as valid . The token has to be unique to each request , so the attacker cannot guess it . Since the attacker cannot guess the contents of the token , he is therefore unable to forge a request that the user would pass on to the server . The modified code for preventing CSRF attack is as follows :
<?php //the code for : login.php: ?> <html> <body> <?php session_start(); // Generate a random identifier $_SESSION["token"] = md5(uniqid(mt_rand(), true)); if (isset($_SESSION["user"])) { echo "<p>Welcome back, " . $_SESSION["user"] . "!<br/>"; echo '<a href="process.php?action=logout&<span class=">csrf=' . $_SESSION["token"] . '">Logout</a></pre> <pre>'; } else { ?> <p> The username is: JohnDoe The password is: xyz!@# </p> <form action="process.php?action=login" method="post"> <input type="hidden" name="csrf" value="<!--?php echo $_SESSION["token"]; ?-->" /> <input type="text" name="user" size="20"/> <input type="password" name="pass" size="20"/> <input type="submit" value="Login"/> </form> <?php } ?> </body> </html> <?php //The code for : process.php session_start(); switch($_GET["action"]) { case "login": if ($_SERVER["REQUEST_METHOD"] == "POST") { $user = (isset($_POST["user"]) && ctype_alnum($_POST["user"]) ? $_POST["user"] : null ; $pass = (isset($_POST["pass"])) ? $_POST["pass"] : null ; $salt = '$a$Gfyew@!hjerifdggh#$uyTT$'; if (isset($user, $pass) && (crypt($user . $pass, $salt) == crypt("JohnDoexyz!@#", $salt))) { $_SESSION["user"] = $_POST["user"]; } } break; case "logout": if (isset($_GET["csrf"]) && $_GET["csrf"] == $_SESSION["token"]) { $_SESSION = array(); session_destroy(); } break; } header("Location: login.php"); ?>
The synchronization token could be either a hidden form element , or as part of an URL (as an GET variable ) . As an additional layer of protection , the token can have an expiration time associated with it , so even if the attacker somehow manages to obtain the token , he/she will have only a limited window to mount the attack .
There is an open-source CSRF – Class available from GitHub (CsrfToken.php) , it implements the same concepts in an OOP style . Implementing the Class is simple as the following code :
<?php //the code for : login.php: start_session(); require 'CsrfToken.php'; $c = new \Csrf\CsrfToken(); // it's in Csrf namespace $hiddenField = $c.generateHiddenField(); ?> <form action="process.php" method="post"> <?php echo $hiddenField; ?> <input type="text" name="test" /> <input type="submit" value="send" /> </form> <?php session_write_close() ?> <?php /* //the code for : process.php */ start_session(); require 'CsrfToken.php'; $c = new \Csrf\CsrfToken(); if ($c->checkToken($timeout=20)) { echo 'Success! You typed in: ' . $_POST['test']; } else { echo 'Bad token!'; } session_write_close(); ?>
References and Additional Reading :
- A must read article from the Open Web Application Project (OWAP) .
- Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet .
- PHP CSRF Guard .
- Synchronizer Token Pattern
Super post! Keep up the good work!
Enlightning post! You have a really cool site here!
We’re a group of volunteers and opening a brand new scheme in our community. Your web site offered us with valuable information to work on. You’ve done a formidable job and our whole neighborhood might be grateful to you.
[…] https://tournasdimitrios1.wordpress.com/2012/02/16/preventing-cross-site-request-forgeries-in-php/ […]
There is definately a lot to find out about this subject. I love
all the points you’ve made.
Good article! We will be linking to this particularly great post on our website.
Keep up the good writing.
[…] Csrf protection (cross site request forgery) is achieved with a custom class . […]
What a information of un-ambiguity andd preserveness of valuable know-how concerning unpredicted feelings.