From RNWiki
Jump to: navigation, search

Process>CSRF

Background

CSRF (Cross Site Request Forgery) vulnerabilities have been known and in some cases exploited since the 1990s. Because it is carried out from the user's IP address, some web site logs might not have evidence of CSRF. Exploits are under-reported, at least publicly, and as of 2007 there are few well-documented examples. About 18 million users of eBay's Internet Auction Co. at Auction.co.kr in Korea lost personal information in February 2008. Customers of a bank in Mexico were attacked in early 2008 with an image tag in email and were sent through their home routers to the wrong website.

The attack works by including a link or script in a page that accesses a site to which the user is known (or is supposed) to have authenticated. For example, one user, Bob, might be browsing a chat forum where another user, Mallory, has posted a message. Suppose that Mallory has crafted an HTML image element that references a script on Bob's bank's website (rather than an image file), e.g.,

<img src="http://bank.example/withdraw?account=bob&amount=1000000&for=mallory">

If Bob's bank keeps his authentication information in a cookie, and if the cookie hasn't expired, then the attempt by Bob's web browser to load the image will submit the withdrawal form with his cookie, thus authorizing a transaction without Bob's approval.

A cross-site request forgery is a Confused Deputy Attack against a user's web browser. The deputy in the bank example is Bob's web browser which is confused into misusing Bob's authority at Mallory's direction.

The following characteristics are common to CSRF:

  • Involve sites that rely on a user's Online identity
  • Exploit the site's trust in that identity
  • Trick the user's browser into sending HTTP requests to a target site
  • Involve HTTP requests that have side effects

At risk are web applications that perform actions based on input from trusted and authenticated users without requiring the user to authorize the specific action. A user that is authenticated by a cookie saved in his web browser could unknowingly send an HTTP request to a site that trusts him and thereby cause an unwanted action.

CSRF attacks using images are often made from Internet forums, where users are allowed to post images but not JavaScript.

The above information was taken from http://en.wikipedia.org/wiki/Cross-site_request_forgery. Please visit there for a more detailed description and examples.

Introduction

RavenNuke(tm) (RN) has implemented our own type of CSRF protection based on a customized version of CSRF-Magic. RN uses PHP's output buffering capabilities to dynamically rewrite forms and links on your site. In the future we hope to include the rewriting of scripts (JavaScript) as well. Two of the major changes that the Team has made from the original CSRF-Magic is that we do not do a check just because a token/nonce has been sent and have added the ability to protect sensitive links by passing a token/nonce in a GET type request.

Implementation

Once you include mainfile.php in your script/module CSRF protection is automatically available to you.

The following files and snippets of code are what is used/required to implement CSRF protection in RavenNuke(tm).

Files:

  • /includes/csrf-magic.php

Snippets:

  • /mainfile.php
/**
 * CSRF Protection for POST/GET forms and potentially "dangerous" links
 */
require_once INCLUDE_PATH . 'includes/csrf-magic.php';

Use

Forms

It is important to note before continuing with this section that forms with a method type of GET are considered unsafe when directed to a page recording data to a database. In these cases a POST type form should always be used if possible.

To harness the power of CSRF protection within RavenNuke(tm) (RN) is very simple. If you are trying to protect a FORM with a method type of POST the token will be automatically added to the form as a hidden input when the php file containing the form is parsed into html via php's output buffering. So all you have to do to protect a POST type form is to add a call to the csrf_check() function to the page that the form is posting to. The below is a typical example of how to implement CSRF protection, but it is important to note that the csrf_check() function can be called wherever you deem it necessary.

Before CSRF Protection:

HTML Source of page containing form:

<form action="admin.php" method="post">
<input type="text" name="pollTitle" size="50" maxlength="100" />
<input type="hidden" name="op" value="createPosted" />'
</form>

Code of the page the form POSTs to (admin.php):

switch($op) {
	case "createPosted":
		poll_createPosted($pollTitle, $optionText, $planguage, $title, $hometext, $topic, $bodytext, $catid, $ihome, $acomm);
		break;
}


After CSRF Protection:

HTML Source of page containing form:

<form action="admin_file.php" method="post">
<input type="hidden" name="__csrf_magic" value="sid:95029f1fbcc4aaeeb6acfc338e6b4c11b03b269c,1249077434" />
<input type="text" name="pollTitle" size="50" maxlength="100" />
<input type="hidden" name="op" value="createPosted" />'
</form>

Code of the page the form POSTs to (admin.php):

switch($op) {
	case "createPosted":
		csrf_check();
		poll_createPosted($pollTitle, $optionText, $planguage, $title, $hometext, $topic, $bodytext, $catid, $ihome, $acomm);
		break;
}

The token will always be added to the beginning of the form right after the <form> tag.


Links

In some circumstances, such as in administration areas, it may be required to protect a certain HTML anchor tag (link) - essentially a GET - from CSRF. For example, say you had a link to delete a widget like so:

http://www.mysite.com/delete_widget.php?op=delete&id=1&ok=1

To protect delete_widget.php from CSRF attack, you need to be able to add the CSRF nonce to the anchor's HREF like this:

http://www.mysite.com/delete_widget.php?op=delete&id=1&ok=1&__csrf_magic=sid:95029f1fbcc4aaeeb6acfc338e6b4c11b03b269c,1249077434

Then, within the appropriate place within the delete_widget.php script (such as at the top of the delete function), simply add the csrf_check() function!

RN makes adding this nonce very easy: add a class of rn_csrf to the anchor definition and the link will be re-written via output buffering when the page is loaded. The code for this would be like so:

<a class="rn_csrf" href="delete_widget.php?op=delete&id=1&ok=1">

And RN does all the rest.


Create Your Own Token

There may be times where you will want to create your own token to use within your code. This could be important if you need to place a token that will not be added during output buffering. For example, even though it may not be good practice (insecure coding), you might be using this:

Header('Location: ' . $admin_file . '.php?op=delete_widget');

Using function csrf_rn_token(), RavenNuke™ provides two formats for the token:

Function Call Result String
csrf_rn_token(); &__csrf_magic=sid:95029f1fbcc4aaeeb6acfc338e6b4c11b03b269c,1249077434
csrf_rn_token('input'); <input type="hidden" name="__csrf_magic" value="sid:95029f1fbcc4aaeeb6acfc338e6b4c11b03b269c,1249077434" />

If you want to use the token name or value directly you can also just use the following:

  • $GLOBALS['csrf']['input-name']
  • $GLOBALS['csrf']['rn_csrf_token']

Configuration

The following configuration options are available and can be found in /includes/csrf-magic.php. These are the default values and should not be edited.

/**
 * montego - Added to be able to switch debug mode on if needed
 */
$GLOBALS['csrf']['debug'] = false;

/**
 * By default, when you include this file csrf-magic will automatically check
 * and exit if the CSRF token is invalid. This will defer executing
 * csrf_check() until you're ready.  You can also pass false as a parameter to
 * that function, in which case the function will not exit but instead return
 * a boolean false if the CSRF check failed. This allows for tighter integration
 * with your system.
 */
$GLOBALS['csrf']['defer'] = false;

/**
 * This is the amount of seconds you wish to allow before any token becomes
 * invalid; the default is two hours, which should be more than enough for
 * most websites.
 */
$GLOBALS['csrf']['expires'] = 7200;

/**
 * Callback function to execute when the CSRF check fails and
 * $fatal == true (see csrf_check). This will usually output an error message
 * about the failure.
 */
$GLOBALS['csrf']['callback'] = 'csrf_callback';

/**
 * Whether or not to include our JavaScript library which also rewrites
 * AJAX requests on this domain. Set this to the web path. This setting only works
 * with supported JavaScript libraries in Internet Explorer; see README.txt for
 * a list of supported libraries.
 */
$GLOBALS['csrf']['rewrite-js'] = false;

/**
 * A secret key used when hashing items. Please generate a random string and
 * place it here. If you change this value, all previously generated tokens
 * will become invalid.
 */
$GLOBALS['csrf']['secret'] = false;

/**
 * Set this to false to disable csrf-magic's output handler, and therefore,
 * its rewriting capabilities. If you're serving non HTML content, you should
 * definitely set this false.
 */
$GLOBALS['csrf']['rewrite'] = true;

/**
 * Whether or not to use IP addresses when binding a user to a token. This is
 * less reliable and less secure than sessions, but is useful when you need
 * to give facilities to anonymous users and do not wish to maintain a database
 * of valid keys.
 */
$GLOBALS['csrf']['allow-ip'] = true;

/**
 * If this information is available, use the cookie by this name to determine
 * whether or not to allow the request. This is a shortcut implementation
 * very similar to 'key', but we randomly set the cookie ourselves.
 */
$GLOBALS['csrf']['cookie'] = '__csrf_cookie';

/**
 * If this information is available, set this to a unique identifier (it
 * can be an integer or a unique username) for the current "user" of this
 * application. The token will then be globally valid for all of that user's
 * operations, but no one else. This requires that 'secret' be set.
 */
$GLOBALS['csrf']['user'] = false;

/**
 * This is an arbitrary secret value associated with the user's session. This
 * will most probably be the contents of a cookie, as an attacker cannot easily
 * determine this information. Warning: If the attacker knows this value, they
 * can easily spoof a token. This is a generic implementation; sessions should
 * work in most cases.
 *
 * Why would you want to use this? Lets suppose you have a squid cache for your
 * website, and the presence of a session cookie bypasses it. Let's also say
 * you allow anonymous users to interact with the website; submitting forms
 * and AJAX. Previously, you didn't have any CSRF protection for anonymous users
 * and so they never got sessions; you don't want to start using sessions either,
 * otherwise you'll bypass the Squid cache. Setup a different cookie for CSRF
 * tokens, and have Squid ignore that cookie for get requests, for anonymous
 * users. (If you haven't guessed, this scheme was(?) used for MediaWiki).
 */
$GLOBALS['csrf']['key'] = false;

/**
 * The name of the magic CSRF token that will be placed in all forms, i.e.
 * the contents of <input type="hidden" name="$name" value="CSRF-TOKEN" />
 */
$GLOBALS['csrf']['input-name'] = '__csrf_magic';

/**
 * Set this to false if your site must work inside of frame/iframe elements,
 * but do so at your own risk: this configuration protects you against CSS
 * overlay attacks that defeat tokens.
 */
$GLOBALS['csrf']['frame-breaker'] = true;

/**
 * Whether or not CSRF Magic should be allowed to start a new session in order
 * to determine the key.
 */
$GLOBALS['csrf']['auto-session'] = true;

/**
 * Whether or not csrf-magic should produce XHTML style tags.
 */
$GLOBALS['csrf']['xhtml'] = true;

/**
 * Montego - Added to hold the generated tokens for use within other code, such as in header() functions.
 * Do not edit this.
 */
$GLOBALS['csrf']['rn_csrf_token'] = '';

// FUNCTIONS:

// Don't edit this!
$GLOBALS['csrf']['version'] = '1.0.2'; // Montego - took from their latest git library on 10-JUL-2009

If you want to edit the configuration settings you need to look at the bottom of /includes/csrf-magic.php for this section of code:

/*
 * Montego - Set the default settings here (this way can see the original, as provided defaults, above in the script)
 */
csrf_conf('defer', true); // DO NOT CHANGE!  Do not automatically perform token checking - place the check where appropriate within the rn code
csrf_conf('secret', $sitekey); // figured to just use the 'salt' from config.php (hopefully people change this per instructions!)
csrf_conf('allow-ip', false); // at the same time, should add "nofollow" and "noindex" type directives for search engines

You can edit the three existing ones or add anyone that is needed. The first parameter for the csrf_conf() function is the configuration setting you want to change and the second parameter is the value of the said configuration setting.