jump to navigation

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 .

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>
&nbsp;
<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 :

Comments»

1. mission - February 17, 2012

Super post! Keep up the good work!

2. edin - February 17, 2012

Enlightning post! You have a really cool site here!

3. Flash The Net - February 23, 2012

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.

4. How can you secure your web site or application from CSRF – Cross Site Request Forgery attacks | Eko UK - August 10, 2012
5. olabergman - May 6, 2013

There is definately a lot to find out about this subject. I love
all the points you’ve made.

6. www.youtube.com - May 25, 2013

Good article! We will be linking to this particularly great post on our website.
Keep up the good writing.

7. A Sample MVC App Build on Top of Symfony2 and Laravel4 Components (Part-1 Introduction) | Tournas Dimitrios - June 20, 2014

[…] Csrf protection (cross site request forgery) is achieved with a custom class . […]

8. Shaun - July 24, 2014

What a information of un-ambiguity andd preserveness of valuable know-how concerning unpredicted feelings.


Leave a comment