CENT285 - Cross Site Request Forgery (CSRF) Attacks

There are several different kinds of request forgery attacks. Here is a summary of these attacks:

Preventing CSRF attacks

The basic idea behind preventing CSRF attacks is to use random nonce (cryptographic number used only once) tokens that are created when the user logs in and stored in session data. Any subsequent page checks the session data to see a match and prevents any request from going forward if the nonce token does not match.

Before going into the details of using nonce tokens, let's discuss some measures that do not protect against CSRF attacks (despite what some online sources state).

Measures that don't work

Implementing randomized nonce tokens

When a user successfully logs in, generate a nonce like this:


$randomtoken = base64_encode(openssl_random_pseudo_bytes(32));

Store this in your session data:


$_SESSION["myToken"] = $randomtoken;

For every page that has an input form, add the following hidden field inside the form:


$display .= " <input type=\"hidden\" name=\"myToken\" " . 
"value=\"" . $_SESSION["myToken"] . "\">\n";

Then, on every page that handles the submitted data from a form, you can perform a check like this:


$token = $_POST["myToken"]; 
if ($_SESSION["myToken"] !== $token) { 
  session_unset(); 
  session_destroy(); 
  header("Location: index.php"); 
  exit();
}

This assumes that "index.php" is the login page.

Open Source libraries that prevent CSRF

Other CSRF protection methods

  • Checking the Referer Header. This involves looking at the contents $_SERVER['HTTP_REFERER'] when the user logs in. Although this would work to prevent CSRF, it cannot be used if the https protocol is used since the https protocol does not pass this information. So, this is not considered a complete CSRF prevention method.
  • Checking the Origin Header. At this point, there isn't enough consistency among browsers and the type of requests handled by the browsers to make use of this. If this were done consistently, it would be passed even when using the https protocol. However, since it is not consistent across browsers, this is not complete protection against CSRF attacks either.
  • CAPTCHA - Since the CAPTCHA string is generated randomly, this is a good way to implement what is known as a Challenge-Response approach to preventing CSRF attacks. It is regarded by some to be less favorable from a user experience than the random nonce tokens, but it is an effective approach.
  • Zed Attack Proxy (ZAP)

    The Zed Attack Proxy (ZAP) is a penetration testing tool that can be used in finding vulnerabilities in web applications. It is an OWASP (the Open Web Application Security Project) project that is used by a lot of penetration testers. The homepage for ZAP is:

    https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project

    At this site, you can find a link to download ZAP and links to some YouTube videos that have overviews of ZAP.

    Installing ZAP

    At Zap's homepage, click on the Download ZAP button. Select the ZAP 2.3.1 Standard Linux/cross platform link. This will download a gzipped file that you can uncompress to your Desktop. This will create a folder called ZAP_X.XX where (X.XX represents whatever the current version number is). Within this folder run the script zap.sh.

    After accepting an agreement, the ZAP program starts up.

    Setting up a demo application

    To start using ZAP, let's set up a demo application.

    Set up database

    
    $ psql -U cent285man -W cent285db
    Password for user cent285man: 
    psql (9.3.11)
    Type "help" for help.
    
    cent285db=# create database zapdemo_db owner bob;
    CREATE DATABASE
    cent285db=# \c zapdemo_db
    Password for user cent285man: 
    You are now connected to database "zapdemo_db" as user "cent285man".
    zapdemo_db=# create extension pgcrypto;
    CREATE EXTENSION
    zapdemo_db=# \q

    Download the following gzipped file zapdemo.tar.gz.

    Extract this to your Desktop.

    Run SQL scripts

    
    $ cd ~/Desktop/zapdemo/docs/include/zapdemo/
    vern@c285-ubuntu14:~/Desktop/zapdemo/docs/include/zapdemo$ psql -U bob -W zapdemo_db
    Password for user bob: 
    psql (9.3.11)
    Type "help" for help.
    
    zapdemo_db=> \i all.sql

    This will take some time to load as the pg_sleep(3) function is called so that the data records will be three seconds apart.

    Setup up symbolic link

    
    cd /var/www/docs/cent285
    sudo ln -s ~/Desktop/zapdemo/docs/cent285/zapdemo zapdemo

    Now, we can use ZAP. Set the URL to localhost/zapdemo/index.php and click on the Attack button. After the process stops, view the Alerts. This will show some quick things that could be done to improve the security of your application.

    You can also try running it on your Project 1's directory. This should show more alerts and some higher risk (Medium) alerts because that application did not protect against very much.

    For zapdemo application, you might see alerts such as the following:

    Cookie set without HttpOnly flag

    This can be fixed by modifying the apache configuration file. You can see which file this is by entering the following command:

    
    ls -l /etc/apache2/sites-enabled

    This should show that a symbolic link has been set to point to a file in the /etc/apache2/sites-available directory. On my setup, the file pointed to is:

    /etc/apache2/sites-available/vern.hcc.hawaii.edu

    If you have enough permissions to edit this file, all you need to do is add

    
    <IfModule php5_module>
        php_value session.cookie_httponly true
    </IfModule>

    to that file. Then, you just reload Apache:

    
    sudo service apache2 reload

    If you don't have permissions to edit that file, you can put a command into every script before session_start() is called. Recall that you be calling session_start() from within the Session.php include script. So, for every PHP script in your web application, you would add the following line:

    
    ini_set( 'session.cookie_httponly', 1 );

    before the Session.php file is included (that is, before session_start() has been called).

    Password Autocomplete in browser

    This shows up if you have a password input and you don't have autocomplete turned off. To fix this, you can modify index.php (the only script with a password input) by adding:

    
    <!DOCTYPE html>

    before the start of the HTML and setting the autocomplete attribute to "off" for the password input. Here is the modified version of index.php:

    
    <?php 
      require_once('../../include/proj2/proj2Helpers.php'); 
      require_once('../../include/proj2/Session.php'); 
      if (count($_POST) > 0) { 
        $username = $_POST["user"]; 
        $password = $_POST["pass"]; 
      } 
      if (empty($username) || empty($password)) { 
        $display = "<form action=\"index.php\" method=\"post\">\n"; 
        $display .= " Username:\n"; 
        $display .= " <input type=\"text\" name=\"user\"><br />\n"; 
        $display .= " Password:\n"; 
        $display .= " <input type=\"password\" " . 
          "name=\"pass\" autocomplete=\"off\"><br />\n"; 
        $display .= " <br />\n"; 
        $display .= " <input type=\"submit\" value=\"Ok\">"; 
        $display .= " </form>\n"; 
      } 
      else { 
        $pdo = connect(); 
        $s_username = addslashes($username); 
        $s_password = addslashes($password); 
        $auth_query = "select get_id('$s_username','$s_password')"; 
        $result = $pdo->query($auth_query); 
        $row = $result->fetch(PDO::FETCH_ASSOC); 
        $db_id = $row["get_id"]; 
        $sql = "select * from users where username=:usr "; 
        $statement = $pdo->prepare($sql); 
        $statement->execute(array(':usr' => $username)); 
        $row = $statement->fetch(); 
        if ($row["id"] === $db_id) { 
          session_unset(); 
          $_SESSION["id"] = $db_id; 
          header("Location: showSuggestions.php"); 
          exit(); 
        } 
        else { 
          header("Location: index.php"); 
          exit(); 
        } 
      } 
    ?> 
    <!DOCTYPE html> 
    <html> 
      <body> 
        <?php echo $display; ?> 
      </body> 
    </html>

    The new code is on lines 13-14, where the autocomplete="off" has been added for the password input, and on line 43, where HTML5 is specified.

    X-Content-Type-Options header missing

    This applies mainly to Internet Explorer 8 and Google Chrome. Setting this option prevents MIME-type sniffing. You can make the following modification so that index.php is protected.

    
    <?php 
      require_once('../../include/proj2/proj2Helpers.php'); 
      require_once('../../include/proj2/Session.php'); 
      header("X-Content-Type-Options: nosniff"); 
    // rest of code unchanged

    The ZAP attack will still show the alert for http://localhost/proj2, but it no longer shows up for http://localhost/proj2/index.php.

    X-Frame-Options header not set

    This is to prevent Clickjack attacks. See this page for an example of Clickjacking:

    http://javascript.info/tutorial/clickjacking

    The way to address this is to add another header():

    
    <?php 
      require_once('../../include/proj2/proj2Helpers.php'); 
      require_once('../../include/proj2/Session.php'); 
      header("X-Content-Type-Options: nosniff"); 
      header("X-Frame-Options: sameorigin"); 
    // rest of code unchanged

    You can also use:

    
    header("X-Frame-Options: deny");

    if you don't need to support frames in your pages.

    Using ZAP to study CSRF attacks

    Change Firefox's preferences to use a proxy. Go to Edit => Preferences => Advanced => Network => Configure how Firefox connects to the Internet.

    Click on Settings. Then, make the following changes:

    Don't forget to reset to Use system proxy settings, when finished doing the testing.

    Demonstrating a CSRF attack

    The following site demonstrates how a CSRF attack can be simulated with ZAP:

    http://resources.infosecinstitute.com/csrf-proof-of-concept-with-owasp-zap/

    To do something similar, do the following:

    Browse to http://localhost/zapdemo/index.php and login as janedoe (janey). Click on Add suggestion and add a new suggestion. In the ZAP application, go to Sites and expand the http://localhost, then expand zapdemo. Right-click on the POST request for handleAddSuggestion.php.

    Right-click on this request and select Generate anti CSRF test form. This will open up in a new tab. Go back to the original tab and logout. Then, login as johndoe (john). Go to the generated test form tab and change the suggestion to what you want and submit. This will show up as John Doe's suggestion.