Cross Site Scripting (XSS)

Cross Site Scripting is the second most prevalent issue in the Open Source Foundation for Application Security (OWASP) top 10 – it's found in roughly 2/3 of all applications.

While automated tools can find some of these problems, there are also automated tools designed to detect and exploit these vulnerabilities.

What is Cross-Site Scripting?

XSS attacks occur when data enters a web application through an untrusted source (like a web request), and is sent to a user without being validated.

XSS can cause scripts to be executed in the user's browser, resulting in hijacked sessions, website defacement, and redirection of users to malicious sites.

Essentially an attacker is inputting malicious code into a section for user input which the server expects to be data (but is instead code designed to be executed).

And if you don't handle it appropriately, the malicious code can break out of the 'data plane' and execute as normal code (the 'control plane').

We can group most XSS attacks into two categories: stored and reflected. A third type, which is less common, is referred to as DOM-based XSS.

Any XSS attack occurs when data enters via an untrusted source and is sent to a user (in dynamic content) without being checked for malicious content. The malicious content can be JavaScript, Flash, HTML, or any other code a browser is capable of executing.

Reflected XSS (Impact: Moderate)

This is the most basic type of XSS, wherein an application receives data and then includes that data in the response to the user in an insecure way.

For example, if an attacker convinces a user to click this phishing link:

http://legitwebsite.com/message=<script>malicious code</script>

If the legitimate site doesn't process the data and just returns it to the user, the user's browser will execute the malicious code.

Stored XSS (Impact: Severe)

Stored XSS occurs when the injection is permanently stored on the target's servers, such as a message in a forum or comment section, in a database, and so on. Essentially this means that the exploit impacts every visitor to the site/application.

For example, if a site allows users to comment and then displays their comments, a user could enter the following:

<p><script>malicious code</script></p>

If the site doesn't check user input appropriately, it could result in the script executing for any other user who sees this message.

DOM-based XSS/Client Side XSS (Impact: Moderate)

The big difference between reflected and stored XSS and DOM-based is where the attack is injected.

Reflected and stored XSS are server side issues, while DOM-based is a client (browser) side issue. DOM-based XSS occurs in the DOM (document object model) instead of as part of the HTML.

Rather than inserting malicious code into the page, this attack will allow the legitimate page to load, then leverage user input to add HTML to the page, executing the malicious script. This takes advantage of the increasing amounts of HTML generated by the client.

For example, an attacker could social engineer a victim into clicking on a malicious link (such as http://www.legitimatewebsite.com/contact#<script>malicious code</script>). The website will receive the legitimate request for the page, but not the malicious fragment (because browsers don't send anything after the # character to a site's server). The victim will see the legitimate website, but the victim's browser will also execute the malicious script.

Due to the way this attack works, server-side protections won't prevent it as the malicious code isn't being sent to the server at all.

Instead, protecting against this attack requires ensuring that JavaScript doesn't interpret URI fragments (uniform resource identifier - A URI identifies a resource at a specified location such as a URL) in an insecure manner.

Mitigation Measures for XSS Attacks

Mitigating effectively against XSS attacks requires a combination of the below measures, which, when you use them together, can provide a robust defense against XSS.

Avoid inserting user-supplied/untrusted data anywhere other than specified locations

This is the first and most important rule. Encoding, escaping, validating, and filtering input is extremely difficult and very complicated.

It's much easier to limit the number of places where someone can input untrusted data. The safest assumption is that all untrusted data is malicious.

Validate/Filter Input

Ideally, all input should be validated against a list of acceptable values.

Encode Output

Any user-input data should be encoded to prevent it from being read as active. This may require CSS, JavaScript, URL, and/or HTML encoding.

It's particularly important to ensure output is encoded at any point where it is exposed, as the same data could be stored and displayed in multiple places.

Choose Frameworks Carefully

Use frameworks which provide automatic escaping functionality (like Go Templates) or those which have native defenses against XSS (like .NET's request validation).

Set the HttpOnly Flag

XSS attacks often use JavaScript to steal session cookies (and normal web apps rarely need to use JavaScript to access session cookies). So setting the HttpOnly flag protects session cookies from attackers, while not limiting normal behavior. Most browsers support setting this flag.

Use Response Headers

Similar to HttpOnly flags, any HTTP responses which shouldn't contain HTML or JavaScript can leverage 'Content-Type' and 'X-Content-Type-Options' headers to ensure that browsers only interpret responses in the way they were intended to do.

Educate Developers about Security

Education specifically targeted at developers, like 'Security Champions' programs which pair application security team members with developers, are important. They can help educate developers about security vulnerabilities (like XSS) and how to prevent them.

Have a Content Security Policy

A content security policy (CSP) can help you detect and mitigate XSS and other data injection attacks.

They set allowlists for sources of trusted content and can apply only to sensitive pages (like payment pages) or, ideally, to the entire site. They can even provide notifications if content is loaded from a page which it should not.

They're fairly easy to deploy – just add the Content-Security-Policy HTTP header to the webpage and any directives that are appropriate.

The CSP Fiddler Extension can help you generate a baseline CSP. The tool will build a policy around reports submitted by the browser, creating a changeable baseline policy.

Additionally, you can use Report URI, run by Troy Hunt and Scott Helme, to get alerts on CSP violations in order to more proactively monitor your sites.

Typically a policy consists of a series of directives which will describe the policy for a type of resource or area. One of these directives, sub-resource integrity checks, is used to ensure that a browser verifies that third party content (from sources like a CDN) is delivered without being manipulated.

Essentially, a provided cryptographic hash must match the loaded file. If a hacker modifies the third party's content, the site simply will not load the malicious content.

The problem is that if the content provider updates the file or makes legitimate changes, the content will also not load (because the hash will have changed).

The main way to work around this issue is to leverage versioned JavaScript resources, such as chatbot_0.1.23.js, instead of chatbot.js. This is a best practice in general, and it also ensures the continuity of services if the JS file changes.

While browsers do not universally support this feature, for those browsers which lack the feature, using it won't break the site (it simply won't leverage the technology).

For more detail on the various directives, check out these guides.

Sources/Further Reading:

Show Comments
As seen in: