Rhainfosec XSS Challenge 1 - Writeup
Update - The challenge is still up on hack.me - https://hack.me/101575/bypass-blacklist-based-waf-challenge.html
On 7th January 2014, we announced an XSS challenge for the whole infosec community, the challenge was based upon blacklist based protection and the task was to bypass the blacklist based protection and to execute the javascript. Based upon unique IP addresses we had 1740 participants and more then 80k unique vectors were recorded into our log file which is a tremendous turn out, the size of the log file was around 4.4 mb, which is pretty huge. Out of 1740 participants only 17 were able to solve it, which is less then 1% (0.97 to be exact).
Challenge Setup
To make your lives a bit harder, we induced certain amount of difficulties. Here is an overview of what we had done:- We blacklisted alert, prompt, confirm, document.write functions which are most commonly used to execute javascript.
- We blacklisted open & closed parenthesis, which is what most of the XSS vectors require.
- We blacklisted most commonly used event handlers such as onclick, onfocus etc
- We blacklisted the closing bracket ">".
- We blacklisted most of the attributes used to execute javascript such as src, formaction, action etc.
- We blacklisted "+" sign, which otherwise would had been used to concatenate javascript strings, however we did leave a room for it. (More on it later).
- The winners for the challenge would be decided on the basis of the shortest vector by length.
Hints
The two of most important hints we gave was as follows:
- Look at alternative javascript execution possibilities.
- The solution for the challenge was already given inside my "XSS filter evasion cheat sheet", however you would need tweak the payload. (Obviously, there was not point to the challenge, if the solution was already there).
Partial Bypass - Solution
As it was mentioned in one of the hints that you would need to look at alternative javascript execution possibilities. Almost all attributes were being filtered except the "code" and "data" attribute. The "code" attribute can be used along with the "embed" element and the "data" attribute can be object element to execute the javascript.
Vector #1
Several instances of the "Object" element were being filtered, however it wasn't difficult for some one to figure it out. The "data" attribute was being filtered out too, however case-sensitive based escaping was not being done. Therefore the final vector would be:
Vector #2
A more easier partial solution would be to use "embed" element with "code" attribute:
The reason, why i have coined the solutions as "Partial solution" is because of the fact that the javascript does not executes under the context of the main domain, "rafay.prakharprasad.com", for it to be termed as a full solution, the javascript must be executed under the context of the challenge domain.
The following people who came up with partial bypass:
@yappare
http://rafay.prakharprasad.com/?search=
Let's first take a look at our solution and then take a look at amazing solutions from the community, we used window.open() function to set the name property to "javascript:alert(1)", we used string concatenation to join the "l" and "ocation" together, the "+" sign was being filtered out, however the encoded version of "+" was not being filtered out which is equivalent to "%2b".
Browsers: IE and Firefox
Browsers: IE and Firefox
Let's now try the tremendous solutions we received from the community:
1)@skeptic_fx
Ahamed Nafeez was the first to solve the challenge by our expected method, The following solution works in Firefox w/o user interaction.
Length: 58 characters
POC #1
Later, he made it work inside both Internet explorer and Firefox:
Length: 121 characters
POC #2
Length: 366 Characters
POC
Length: 53 characters
POC
Length: 58 characters
POC #1
Later, he made it work inside both Internet explorer and Firefox:
Length: 121 characters
POC #2
http://rafay.prakharprasad.com/?search=
2)@fransrosen
Frans rosen came up with a very sophisticated bypass, The vector could had been shortened a lot though, but he decided not to do it, even though he could had. He used parentNode to walk to up the document, however all of these parentNodes could had been replaced with "top" or "self" and the POC still had worked, however this turns out to be a great technique in a case top and self keywords have been blacklisted.Length: 366 Characters
POC
http://rafay.prakharprasad.com/?search=%3Csvg/onload=g=parentNode.parentNode.parentNode.parentNode.parentNode.parentNode;h=g[/loc/.source%2b/ation/.source]; g[/loc/.source%2b/ation/.source]=/javascrip/.source%2b/t/.source%2bh[/has/.source%2b/h/.source][1]%2b/aler/.source%2b/t/.source%2bh[/has/.source%2b/h/.source][2]%2b/documen/.source%2b/t./.source%2b/domain/.source%2bh[/has/.source%2b/h/.source][3]%0c#:%28%29
3)@insertscript
Alex, next came up with a nice and clean cross-browser bypass:Length: 53 characters
POC
onclick=window.open('http://rafay.prakharprasad.com/?search=
4)@hasegawayosuke
Hasegawa next came up with an amazingly short IE9 specific bypass using "onactivate" event handler, which only works withinternet explorer.
Length: 27 Characters
POC
Click Here
5)@avlidienbrunn
Mathias came up with several different solutions namely a universal solution that would work on all browsers and a chrome specific bypass with shortened length.Length: 97 characters
POC
Along with his solution, he was kind enough to send an explanation on how it worked:
This solution relies upon setting the window.location property to a javascript URI. We can do this by accessing the property using the syntax with a string within brackets instead of the regular way (window["location"] versus window.location). Since a lot of strings were filtered (such as "alert" and "location"), we can use the same window.name trick to go transfer a string to the page. In the example I passed "javascript:alert(1)".
Sadly, only one string can be passed with that trick and as for the other string ("location"), I forged it by building a desired string out of another, character by character. But to do that I needed a string in the first place, so I used regex and addition to cast it into a string. /locatio/+/n/ becomes "/locatio//n/". From there we can just pick character by character and glue it together to make "location".
In the end we set window[string_from_regexes]=string_from_window_name, which becomes window["location"]="javascript:alert(1)", executing the alert.
Later, he came up with a chrome specific bypass of only 32 characters in length:
Length: 32 Characters
POC
data:text/html,
6)@yujikosuga
Yuji arrived in a bit late inside the challenge, but still managed to solve it in a decent amount of time.Length: 57 characters
7)@philroberts & @adam_baldwin
Phil roberts next, came up with a huge and the most longest solution in terms of length.
Length: 527 characters
POC #1
http://rafay.prakharprasad.com/?search=%3Csvg/onload=z=[]%2batob;l=z[13];r=z[14];s=z[8];a=[]%2b/tnemucod/;a=a[8]%2ba[7]%2ba[6]%2ba[5]%2ba[4]%2ba[3]%2ba[2]%2ba[1];b=[]%2b/LMTHrenni/;b=b[9]%2bb[8]%2bb[7]%2bb[6]%2bb[5]%2bb[4]%2bb[3]%2bb[2]%2bb[1];c=[]%2b/Fa=crsFgmi%3C/;c=c[11]%2bc[10]%2bc[9]%2bc[8]%2bs%2bc[6]%2bc[5]%2bc[4]%2bc[3]%2bc[2]%2bs;d=[]%2b/X1Ztrela=rorreno/;d=d[16]%2bd[15]%2bd[14]%2bd[13]%2bd[12]%2bd[11] %2bd[10]%2bd[9]%2bd[8]%2bd[7]%2bd[6]%2bd[5]%2bd[4]%2bl%2bd[2]%2br;window[a].body[b]=c%2bd%2bwindow[a].body[b][8]//Update: Philip has written an explanation for his huge solution here - https://gist.github.com/latentflip/8580688
Later, he managed to shorten it upto 97 characters:
Length: 97 Characters
POC #2