XSS - Security Awareness

Following is a small educational content I made for educating devs at my company.

Video

https://drive.proton.me/urls/ZHJBA1MSZ8#sTJuTPKViUko

Script

This video's topic is cross-site scripting, aka XSS.

XSS is a vulnerability where attacker-controlled input is executed in a user’s browser, often as HTML or JavaScript. In other words, the application treats untrusted input as code rather than as data.

A simple example is a website that asks for a name or surname. Instead of entering a normal value, the attacker enters a piece of JavaScript, like an alert() function. If the site doesn’t handle that input correctly, the JavaScript runs when the page loads.

There are three main types of XSS: reflected, stored, and DOM-based. I’ll briefly go through each one.

Reflected XSS happens when user-controlled input is included in the URL and then reflected back in the response. The malicious code isn’t stored anywhere — it’s just part of the request. An attacker can exploit this by crafting a special URL and sending it to a victim. If the victim clicks the link, the script executes in their browser.

Stored XSS is more serious. This is when the attacker’s input is saved in the application’s database. A common example is a username, profile field, or comment containing malicious JavaScript. Anyone who later views that content will have the attacker’s code run in their browser.

DOM-based XSS happens entirely on the client side. The application’s JavaScript takes some input — for example from the URL or another browser-controlled source — and directly writes it into the page in an unsafe way. In this case, the payload never needs to reach the server at all.

The impact of XSS can be significant. An attacker can read or modify data in the page, perform actions as the victim, or steal sensitive information. For example, if cookies aren’t protected with the HttpOnly flag, an attacker can extract them and send them back to themselves, which can lead to session hijacking.


Now that we know what XSS is, let’s look at how to stop it

The first thing to understand is that XSS isn’t one single bug. It’s a whole category of bugs. That means there isn’t a single fix, but requires layers of defence.

We'll start with the framework

Most modern frameworks already protect you from a lot of XSS by default. React, Angular, Vue — they all encode output automatically.
But XSS usually shows up when we step outside those protections.

Things like:

  • Using dangerouslySetInnerHTML in React
  • Angular’s bypassSecurityTrust… methods all introduce potential XSS sinks, that can turn into real vulnerabilities.

So the key take away is, to read up on the docs of your framework and see what protections it provide and which functions are potentially dangerous.

This brings us to output encoding, all user controllable variables must be properly output encoded before being displayed on the browser.

These are example of HTML encoding. Ampersand carries a special meaning in HTML, however when encoded into $amp;, it loses this meaning.

Html attribute contexts, while similar to html encoding requires different attention. Forgetting to close the quotation marks surrounding your variables can introduce XSS bugs.

like HTML injection, JavaScript injection is also a potential XSS risk. Ideally user input should never reach javascript code, but if it has to, then the only safe way is through a quoted data value as shown here. Beacuse this is javscript context, you should not HTML encode it, instead you should Javscript encode it.

Another injection type is CSS injection. Variables should only be placed in a CSS property value.

URL encoding should also be utilised when dealing with URLs. If this url then goes into an href or src attribute, then it also needs to be HTML encoded.


there are some dangerous contexts that ideally shouldnt interact with user controlled input, because output encoding can't guarantee that the input won't be executed.

Sometimes we want some HTML tags to be allowed for styling purposes. This is where HMTL sanitization comes in. HTML Sanitization will strip dangerous HTML from a variable and return a safe string of HTML.
DOMPurify is a good option for sanitizing HTML in javascript, but it should be done right before output, if instead, the output of DOMPurify is modified by user input afterwards, then the security efforts are void.

The DOM API provides safe sinks for entering in user input that automatically gets HTML encoded. If you are using dangerous sinks such as innerHTML or outerHTML and don't need HTML styling, then you can replace them with textContent, or any of the following APIs.

When an attacker is able to execute code on a victims browser, one of the first things they might try is to exfiltrate the session cookie to allow session hijacking. this can be easily prevented by setting the HttpOnly flag on session cookies. It is for this reason HttpOnly cookies are safer against XSS vulnerabilities than JWT tokens stored in browser web storage.


The last mitigation I want to cover is Content Security Policy, or CSP. Think of it as an extra layer of protection built into the browser. Even if an attacker somehow gets the browser to run malicious code, a well-defined CSP can block it or at least severely limit what it can do.
In simple terms, CSP lets you control what resources a page is allowed to load. There are many directives you can use, but the most important one for XSS is script-src.

  • If you set script-src to 'self', the browser will only run scripts that come from your own site.
  • If you set it to a specific URL, the browser will only run scripts from that URL. This way, you can prevent the page from executing unexpected scripts, even if some malicious input sneaks through.

There are a few things to consider here. Assuming the attacker found a way through our HTML encoding and got the browser to interpret his input as executable,

  1. he'll first try to run inline scripts like so. We can prevent this by disallowing the unsafe-inline attrbute which a lot of projects in Adappt currently have enabled.
  2. If the attacker notices that inline scripts are blocked, they might try loading remote scripts, this can be prevented by restricting the URLs that are allowed to load scripts.
  3. It's easy to forget that whitelisting CDNs can be dangerous as attackers can upload their malicious script in the the same CDN, bypassing your CSP.
  4. Some projects allow unsafe-eval in their script-src directive, this opens potential vulnerabilities like shown here.
  5. Apart from the script-src directive, the form-src directive is also of concern as it may allow an attacker to create phishing forms to request the user's password and send it to his own domain.
  6. the object-src directive should be disabled to prevent injection of flash or other legacy exectuables on the page.

Theres a great website by google that evaluates your CSP that I recommend everyone to use.. Here I have the CSP of EIOS and what google thinks of it. if you're wondering what strict-dynamic or trusted-types are. You can read more about them on these websites.

Quick takeaway

So the short version is:

  • Use your framework properly
  • Treat all data as untrusted
  • Encode based on where the data goes
  • Avoid dangerous sinks
  • Sanitize when HTML is actually needed
  • Use CSP and cookie flags as extra safety nets, but not as replacements

You'll only receive email when they publish something new.

More from Kevin
All posts