In a XSS attack, an attacker exploits the vulnerabilities of a web site in order to attack its visitors. Typically, the attacker injects a tiny script that when executed downloads additional scripts. There are two types of XSS attacks: stored and reflected (or not persistent), depending on whether the malicious scripts are stored or not on the host server.

Stored XSS

In a stored XSS attack the malicious content injected by the attacker is saved on the server by the application. This is the case, for example, of a comment on a blog post. If the comments with malicious contents are not validated and sanitised before storing them into the database, every time the blog comments is read by a user the malicious code will be executed; thus, stored XSS is a one-to-many attack.

Reflected XSS

On the other hand, reflected XSS is a one-to-one attack since the attacker must provide a link with malicious content embedded in the request parameters to the victim user. If the victim clicks on the link a request is sent to the application server whose vulnerabilities were previously exploited by the attacker. The server extracts the malicous content from the request and includes it in the body of its response. When the contents of this response load on the victim’s browser, the malicious code is executed and the XSS attack completed.

Applications that render page content on the client side are not immune to reflected XSS attacks if the unsafe JavaScript code fetches the malicious content from an URL upplied by the attacker.

How to prevent XSS attacks

The XSS attacks occurs when the application code uses user untrusted data without proper validation or sanitisation and renders it on the victim’s browser. The easiest way to avoid this unsafe behaviour is by adding a Content Security Policy (CSP) response header. The CSP header allows the developer to whitelist the domains from which the browser can accept content (JavaScript files, images, CSS, different media, etc.). In such a way, the browser blocks any script injected by an attacker since they are not originated in a whitelisted domain.

A CSP header can be added to a Node.js web application using the helmet package. The following code snippet shows how to embed a simple CSP policy to express.js middleware:

const helmet = require('helmet')

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    imgSrc: ["myothersite.com"]
  }
}))

the defaultSrc set to self allows the browser to download JavaScript code only from the same domain the page was loaded. Conversely, the imgSrc adds an exception that allows downloading images from the myothersite.com domain.

CSP policies must be hardened to also consider malicious code inserted in polyglot JPEGs. A polyglot JPEG image contains javascript code in its header that can be executed by including the image as shown in the code snippet below:

<script charset="ISO-8859-1" src="/uploads/corrupted_image.jpg"></script>

If your website allows the users to upload images on the same domain of your application and the defaultSrc is set to self, an attacker could bypass the XSS protection by uploading a polyglot JPEG with malicious code and injecting a script that points to the corrupted image. In order to avoid this situation the uploaded images must be loaded on a separate domain that must be not whitelisted in the CSP directives. Additionally, the uploaded images can be also sanitised by removing the JPEG header.

Another effective way to protect against XSS attacks consists in encoding untrusted user-supplied data and convert it into a safe format before using it on the page. However, the conversion process can be very challenging because it is context-sensitive and depends on where the untrusted data is located (i.e. HTML tag body, HTML tag attribute, CSS, JavaScript, and URI).

Using the HttpOnly flag with session cookies

Using the HttpOnly flag with session cookies prevents a malicious script from accessing them, which is often the main objective of an XSS attack. The code snippet below shows how to configure express.js middleware to secure cookies:

const session = require("express-session")

app.use(session({
  secret: "my_secret",
  key: "my_session_Id",
  cookie: {
    httpOnly: true,
    secure: true
  }
}))

Some remarks on DOM-based XSS

Until few years ago DOM-based XSS was considered an XSS attack mechanism. However, rather than a standalone attack type, it can be considered as a particular case of either stored or reflected XSS attack, where the malicious code injection happens on the victim’s browser.

Conclusions

The XSS is a very common attack. Although the best way to protect against XSS is by output encoding, this technique is not so straightforward to implement since it must be context-aware. In addition XSS vulnerabilities are very difficult to detect, especially on the server side which makes output encoding really very challenging. Luckily, all the modern browsers support CSP header which is a really simple and efficient mechanism to implement XSS protection.