XSS in Limited Input Formats

Testing for XSS vulnerabilities requires knowing the data format of input. Usually the format is simply “string” without any restrictions but sometimes the manipulation of XSS entry point is limited.

In most of times it might lead to the assumption of a security filter, one designed/employed specifically to avoid the attack which is not true.

So we are going to see some of those formats and how to deal with them to achieve the classic XSS PoC.

A test page follows. Stop reading right now if you want to #hack2learn!

https://brutelogic.com.br/tests/input-formats.php

1. Email

Email addresses are widely used in forms and displayed several times in different parts of a web application. To demonstrate how we can simply adapt our XSS vector to it, check this test.

Any input different from a classic email format gets rejected by application returning an “INVALID” response:

There’s no real sanitizing but just VALIDATION performed by FILTER_VALIDATE_EMAIL flag of PHP’s filter_var (or filter_input) function. It seems it’s just a matter of appending a “@x.y” (without quotes) to it in order to pass the validation but it is not.

The solution for passing the validation mechanism and achieving XSS relies on a simple and perfectly valid email address according to RFC822.

Like tweeted here:

Final payload:

"<svg/onload=alert(1)>"@x.y

2. URL (No Query)

Input in URL format sometimes ends up in “href” (hypertext reference) of HTML anchor elements like we see here. In our example if we click on “Click me” the page sends the visitor to Google Search website.

Again there’s no sanitizing but just validation brought by FILTER_VALIDATE_URL flag of PHP’s filter_var or filter_input function. Quickly it’s possible to find out we can use the JavaScript pseudo-protocol instead of the HTTP one by keeping the “protocol://reference” format

Because in JavaScript pseudo-protocol we use JS code right after the “:”, double slashes makes the payload an useless one because they turn the entire line into a comment. As a workaround we use a new line character (%0A) but double encoded since it needs to end up in the browser redirect and a single encoded new line just reflects in page’s source code.

So our final payload is:

javascript://%250Aalert(1)

3. URL (With Query)

A variation of the previous case where we also need to append a query in the URL. You can check it here.

A simple “?1” in it it’s enough to pass the filter but it doesn’t create a valid JS syntax. So we just comment it:

javascript://%250Aalert(1)//?1

We can also use a ternary operator:

javascript://%250A1?alert(1):0

There might be an extra validation related to the presence of a given domain name in the URL provided. That’s also easily bypassed in the following form, our final payload:

javascript://https://domain.com/%250A1?alert(1):0

4. Key

A tricky format to test is one with a fixed length of characters. The best example of such input is a key, like an API one. Because tests have to be made taking in consideration that strict char limit, it’s easy to be missed by both automated tools and manual inspection.

As we can see any input which has not the same length of the example key is returned as invalid. We just need to use some extra dummy chars to complete the 32-char length of a MD5 hash and the popup is triggered.

Final payload:

12345678901<svg onload=alert(1)>

Our latest KNOXSS release is able to detect and provide a PoC for that XSS case with most common lengths. It also catches the email one while the URL case is supported in DOM-based XSS only (no user interaction).

 

#hack2learn