Cross Site Scripting (XSS)

Cross Site Scripting is the second most prevalent issue in the OWASP (Open Source Foundation for Application Security) top 10, 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, making it crucial that security teams identify and address the vulnerabilities first.

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 inserts a malicious script into a section for user input (which the server expects to be data, not commands). If not appropriately handled, the malicious code can break out of the 'data plane' and execute as normal code (the 'control plane').

Most XSS attacks can be grouped into two categories: stored and reflected. A third type, which is less common, is referred to as DOM-based XSS. All XSS attacks occur 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>

And the legitimate site doesn't process the data before returning it to the user, the malicious code will be executed by the user's browser.

Stored XSS (Impact: Severe): Stored XSS occurs when the injection is permanently stored on the target's servers, such as a comment or message in a forum or comment section, in a database, etc. This allows the exploit to impact every visitor to the site/application.

For example, if a site allows users to comment and for their comments to be displayed, 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 attack takes advantage of an increasing amount of HTML generated by the client.

For example, an attacker could social engineer a victim into clicking 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, it can't be prevented by server-side protections as the malicious code isn't being sent to the server at all. Instead, protecting against it requires ensuring that JavaScript doesn't interpret URI fragments in an insecure manner (such as using a framework with in-built protections, avoiding insecure methods like innerHTML, outerHTML, and document.write, and applying a content security policy).

Mitigation Measures:

Mitigating effectively against XSS attacks requires a combination of the below measures, which, when used 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 untrusted data can be input. The safest assumption is that all untrusted data is malicious.

Validate/Filter Input: Ideally, all input should be validated against a whitelist of acceptable values.

Output Encoding: Any user-input data should be encoded before being output anywhere 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. Ideally, output is both filtered/validated when input, and encoded when output in order to be sure that no malicious code was able to slip through.

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 HttpOnly Flag: Since XSS attacks often use JavaScript to steal session cookies (and normal web apps rarely need to use JavaScript to access session cookies), setting the HttpOnly flag protects session cookies from attackers, while not limiting normal behavior. Most browsers support setting this flag.

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.

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

Content security policy: A content security policy (CSP) can help 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. 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 all third party content (from sources like a CDN), was delivered without being manipulated. To do this, a provided cryptographic hash is checked against the loaded file. If a hacker modifies the third party's content, the site simply will not load the malicious content.

A problem this causes 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 leveraged versioned JavaScript resources, such as chatbot_0.1.23.js, instead of chatbot.js. This is a best practice in general, as well as ensuring 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.

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, Report URI, run by Troy Hunt and Scott Helme can be used to get alerts on CSP violations in order to more proactively monitor sites.

Sources/Further Reading:

Show Comments
As seen in: