Table of Contents
- Intro
- Importance of XSS vulnerabilities.
- Some known XSS attacks in the wild
- Types of XSS
- Attacks
- Prevention
- DOM XSS
- Summary
Intro
Cross-site scripting (XSS) is an old but always relevant and dangerous type of attack that plagues almost all web applications, be it older or modern ones. It relies on developers using javascript to enhance the experience of end-users of their application, but when the javascript isn’t properly handled it leads to many possible issues, and one of them is XSS.
Importance of XSS vulnerabilities
The risk of XSS is that the malicious code is usually injected directly into the vulnerable application and not a redirect site that the user might watch out for. So if you often go to example.com and someone sends you a link of one of their articles that goes something like example.com/this-article-is-good?id=%3Cscript%3Ealert%281%29%3C%2Fscript%3E you’ll probably click it because it’s something you’re really used to. What you’re not aware of is that there was some code injected in the site without your or the site’s approval and that code might steal your session, take some screenshots, activate a keylogger, etc…
An even more dangerous type of XSS vulnerability is the persistent one where you don’t even have to click on a link to execute the code, you just browse to some page on a site you trust and an attackers comment containing malicious code that was saved in the database is displayed on the site and suddenly you and everyone who visits that page is triggering something they really don’t want to trigger.
Some known XSS attacks in the wild
One of the most famous examples of XSS is the “Samy“. Samy is one of the fastest spreading malwares in internet history. It abused unsanitized profile posts to inject harmful javascript code that was saved to the database and then activated whenever a user viewed that post, thus spreading the worm to themselves and so on.
Yahoo account hijack via email phishing and XSS, attackers made a page with malicious javascript that would steal cookies of visitors. The attack was executed by sending an email with a link to the popular news page article, but the link linked back to the attacker’s site which contained malicious code.
Types of XSS
The three most common types of XSS are:
– Reflected
– Persistent
– DOM-based XSS
You can read more about these types and how and why they work here.
Attacks
Let’s start with the basics
XSS is a really easy attack to start testing and seeing if you can execute malicious code. To get started, find some possible injection points in your targets and start with some simple basic payloads and see how the page reacts and then try to break it.
Finding possible injection points
The easiest way to find possible injection points is to see if reflection happens somewhere. A good example for this is usually the search bar where once you search for something you get the string you searched back at the top of the page.
In the image above you can clearly see reflection happening and this is a prime spot to start testing for XSS.
Another good place to start injecting is a form in which text will be displayed to a large number of people. A good example of this is comments on a page, a review, post, or basically anything that will be seen by someone other than you.
Inspecting the elements and analyzing the reflection
Once you found a reflection point it’s a good idea to analyze it a bit and see how things are getting reflected, what do they pass through to get reflected back to you and how you can get over some of the common hurdles that developers put in to stop XSS attacks.
A good first step is to inject a bunch of random characters to see if some are blacklisted. this includes characters like < > / ; ! # $ and combinations of them to see if they are all reflected properly. Another good way to see for common blacklisting is doing some basic injections and seeing how they are reflected.
After playing around with the input field itself it’s good to check the frontend code to see if it’s sanitized somewhere. Then, check the javascript files that the input field goes through to see that.
Basic injections
Doing basic injections is a great way to see how the field is reflecting the input and what it’s doing with it behind the scenes.
First start with injecting the most basic alert: . What is reflected back to you, just the alert part, maybe you got a popup (if you did you found the goldmine, go ahead and break the whole site because there are probably a bunch more vectors possible), maybe it just filtered out special characters, maybe nothing got reflected, or in the worst case everything got reflected nicely back to you.
Depending on what got reflected back to you you can start crafting your payload.
Here are some examples on different simple injections and bypasses and how to work through them:
1. Basic injection works in the URL parameter id (broken_site/xss/1?id=)
2. Basic injection doesn’t work but we get some reflection (http://100.26.239.14:3000/vuln/xss/2?id=)
In this example we see that there is reflection but the script tags are filtered out, let’s play a bit with them to see how we can get them displayed.
Let’s see if capitalization breaks out of the blacklisting. Next payload is broken/site/xss/2?id=.
And it worked, the filtering used was just checking lowercase/uppercase characters and not mixed case.
3. Let’s try on another page, again we start with basic injection to scout it out broken_site/xss/3?id=
Again as in the previous example, let’s mix the case and see how that reacts broken/site/xss/3?id=
Pretty much the same as the previous try, nothing changed, looks like it sanitizes the input to only one case and then checks it.
Let’s try wrapping it to see if it does just one check or multiple checks. Payload is broken_site/xss/3?id=
And this worked, the site just checks once if the payload contains the script tags and removes them, once it removes them we get another set of script tags that we wrapped around the removed ones and we get the successful alert prompt.
4. Page 4, again basic injection to see what’s going on broken_site/xss/4?id=
Here we get an interesting reflection, where some tags are still there but there is no script.
Let’s try wrapping it up to see what’s happening with the payload broken_site/xss/4?id=
It looks like the page uses some sort of regex to filters the script out, let’s try something other than script.
Injecting a basic a tag and seeing how the site reacts to that. Payload is broken_site/xss/4?id=Click me!. What we do here is basically create a link that says click me, and when we hover the mouse over it, the alert box should execute.
We have the link on the site, let’s try hovering over it to see if we get the alert box.
And that worked because the whole focus of filtering was the script tag, the developers forgot about a tag and left that for the injections.
5. Another page, again we try the basic injection to see what’s going on with the site broken_site/xss/5?id=
Nothing gets reflected, let’s try wrapping it and seeing how it behaves then. Payload is broken_site/xss/5?id=
We get some reflection, but it doesn’t really help us out.
Let’s try with the a tag and see how it behaves, we’re still not sure what’s being filtered here.
Payload is broken_site/xss/5?id=Click me!
We get the link but when we try to hover over it, it doesn’t actually execute anything. So it isn’t the script tag that is being filtered out but the alert is the culprit here. Let’s try converting it from ascii code to characters using some basic JS. Open up your developer tools by pressing F12 in your favorite browser and go to the Console tab. Type the following String.fromCharCode(97) and press Enter. You should get the character a displayed in your console. Now let’s craft up our alert box with this. Function is String.fromCharCode(97, 108, 101, 114, 116, 40, 49, 41) and let’s put that into eval so it executes.
Payload is broken_site/xss/5?id=Click me!
And once we hover on it.
It worked.
Another thing that would’ve worked in this case if we injected prompt or confirm instead of alert.
This is the standard way of working through to figure out how to get the prompt to appear, iteratively trying things until something sticks and then modifying the thing that sticks the best to get the prompt. Another good thing to do is to inspect the element and see the code around it to see if you can get something out of that. It’s really useful for detecting DOM-XSS issues.
The site we used to test these injections is Broken Crystals, and you can also contribute to it to make it better.
Going crazy with the payloads
XSS Payloads can and do get really crazy real fast, and the AppSec community created some great payloads that you can just copy and paste to see if they work.
Some common bypasses are:
– Encodings
– URL encoding
to %3Cscript%3Ealert%281%29%3C%2Fscript%3E
– Base64 encoding
to PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==
– Hexadecimal encoding without semicolon
to %3C%73%63%72%69%70%74%3E%61%6C%65%72%74%28%31%29%3C%2F%73%63%72%69%70%74%3E
– Decimal HTML character
to
– Decimal HTML character without semicolon
to <script>alert(1)</script>
– Octal encoding
javascript:prompt(1) to javascript:'160162157155160164506151'
– Unicode encoding %EF%BC%9E -> > and %EF%BC%9C -> < to %EF%BC%9Cscript%EF%BC%9Ealert(1)%EF%BC%9C/script%EF%BC%9E
- Using jsfuck
- Embedding encoded characters that don't break the script (tab, newline, carriage return)
- Embedding tab
to
- Embedding newline
to
- Embedding carriage return
to
- Null breaks
to