In the previous three chapters of this series, we discussed ways for developers to put their hacker hats on and program defensively to prevent security bugs from cropping up in their software. We described the nature of SQL injection, OS command injection and buffer overflow attacks. We did not, however, touch upon the No. 1 issue that plagues web application developers: cross-site scripting (XSS).
Indeed, XSS ranks quite high in the CWE/SANS Top 25 Most Dangerous Software Errors list:
|1||Improper Neutralization of Special Elements used in an SQL Command (‘SQL Injection’)|
|2||Improper Neutralization of Special Elements used in an OS Command (‘OS Command Injection’)|
|3||Buffer Copy without Checking Size of Input (‘Classic Buffer Overflow’)|
|4||Improper Neutralization of Input During Web Page Generation (‘Cross-site Scripting’)|
XSS is easy to test for. In fact, it is probably one of the most common vulnerability types found in software. However, XSS can be really hard to fix. When maintaining a large web application that was written 10 years ago using servlet technology, there may be thousands of places where XSS lays dormant. Newer technologies using rich, client-side user interfaces (UIs) are not spared, either.
To top it all off, most developers are using web technologies nowadays, even in mobile apps, so XSS is a big headache for everyone.
Notorious Examples of Cross-Site Scripting
XSS made history with the Samy worm, the fastest spreading virus of all time. The worm was a relatively harmless and very original type of virus that self-replicated by altering the profile pages of MySpace users and sending friend requests to its creator, now-famous hacker Samy Kamkar, who ended up in hot water with authorities after the incident. Twitter was also targeted by a similar XSS worm that embedded malicious links on the website StalkDaily.
Besides infections on social networking sites, XSS has been used for financial gain, most notably in attacks against e-commerce giant eBay. Cybercriminals injected malicious scripts into several listings for cheap iPhones. The scripts sent users to a spoofed login page that harvested their credentials.
What Is the Programming Flaw?
Figure 1 shows several locations within the HTML markup of a server-generated page:
Figure 3 shows XSS being rendered by client-side code:
The attack vectors come in many flavors. In the reflected XSS scenario, the attack is conducted via links that contain the malicious code. Victims click on the link, which is from a host they trust, and then interact with the altered website. Common attacks include fake login pages that send credentials somewhere else.
Figure 4 shows how reflected XSS works:
The stored flavor of XSS is even more dangerous because victims come across it unwittingly simply by using a vulnerable web application. The aforementioned XSS worms are examples of stored XSS.
There is no silver bullet to prevent XSS. In fact, fixing XSS sometimes feels like playing whack-a-mole. The video below exemplifies the challenges associated with preventing this pesky bug:
Let’s dive into a few best practices that web developers should always keep in mind.
Output encoding works very well for pages generated on the sever side and is quite effective in neutralizing most XSS payloads. The most common method is HTML encoding, while URL encoding can help neutralize the injection of markup in links and redirects.
Figure 5 shows how HTML encoding neutralizes XSS:
For example: x=”-alert (1)-” becomes x=’\’-alert (1)-\”
A flavor of HTML encoding that also encodes single quotes with ‘ can also be used. This provides a more consistent approach to preventing the issue.
Safe DOM Elements
XSS in modern, rich client UIs is often made possible by unsafe handling of the document object model (DOM).
Use Eval and Dynamic Function Calls With Care
Enforcing the Charset
There are XSS attacks that use a different encoding, such as UTF7, for example. If the charset of the page is not enforced, the browser will default to auto detect and those payloads will execute. For example: Content-type: text/html; charset=UTF8.
Whitelisting can reduce the attack surface, although in some cases single quotes and tags must be allowed. If you want to allow someone named O’Brien to update his or her user profile, for example, you need to allow single quotes.
If possible, most input should be whitelisted to alphanumeric to prevent XSS and many other attacks, and special characters should only be allowed on an exception basis. This will reduce the attack surface and minimize the potential for bugs.
Blacklisting is a very bad idea because it may prevent some tools or testers from finding the issue. Others may be able to beat the rule by trying a previously unknown method. The OWASP XSS Evasion Cheat Sheet lists the staggering number of XSS attack variations.
Besides preventing the issue, there are ways to minimize it by using secure headers that most modern browsers support:
- X-XSS-Protection: 1; mode=block enforces the browser XSS filter for some browsers; and
- Content-Security-Policy: script-src ‘self’ prevents the loading of external scripts, which makes XSS exploitation difficult.
There are many more useful settings of the content security policy, including options to log violations that can indicate that an attacker is leveraging a possibly unknown XSS on the site.
XSS Is Here to Stay
XSS is a pesky, ubiquitous and very dangerous type of programming flaw. Developers should always keep XSS in mind when building today’s flashy web applications. The web has become the choice for implementing attractive, platform-independent applications, and it is here to stay. Unfortunately, so is the threat of XSS.