Thursday, August 1, 2013

Avoiding Authentication Mistakes

I do a lot of my shopping online. And given the fact I am located on the beautiful island of Jamaica, most times I use a mail forwarder to get the items to me. This particular mail forwarder has an online portal that allows you to track your package. Pretty convenient might I add.

So I accessed the online service recently and noticed something interesting. I am sure everybody uses some kind of proxy[Burp,Fiddler,ZAP choose your poison.....] to observe the traffic during normal browsing :) Some details have been changed obviously.

GET /VulnerableSite/login.aspx?gtw=JAM&usr=4444&pwd=NkY0RUM1MTRFRUU4NENDNThDOEU2MTBBMEM4N0Q3QTI= HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:22.0) Gecko/20100101 Firefox/22.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: __utma=196028464.1541830775.1375282569.1375282569.1375308802.2; __utmc=196028464;|utmccn=(referral)|utmcmd=referral|utmcct=/web/frontend/tarifas; ASP.NET_SessionId=bnsgfxusegjnoisfxzv2j0b0; TrackMe=usr=4444&gtw=JAM&language=1; __utmb=196028464.1.10.1375308802
Connection: keep-alive

See the problem? If you guessed credentials being being passed in the url, congratz you are now a leet hax0r....:) For those not aware, it is never a good idea to include sensitive information in the url. And I am sure you would classify your login credentials as sensitive. You can check this OWASP entry for additional information on the subject.

Looking at the pwd parameter I initially thought that the password was simply encoded using base64. I tried to decode it in Burp. However that turned didn't work out well. I decided to dig a little deeper and so I fired up chrome developer tools. Looking around I noticed the following code snippet:
var base64Digest = encode64(hex_md5(trim(password.value)).toUpperCase());
var url = '' + selectedGateway + '&usr=' + trim(account.value) + '&pwd=' + base64Digest;
return url;
alert('Error while encrypting password: ' + err);
return '';
Interesting...I accessed the javascript file where these functions were defined. For those that are interested those functions are here and here. On a side note you should never include logic like this on the client side. But I will get to this later.

So now that I knew what as happening to my password the next thing was to check if they were any restrictions on failed login attempts. I also wanted to know if the application was "leaky". In other words did it disclose any information that could be useful to attacker interested in performing a password guessing attack.  As it turns out the application was very helpful on both fronts. There was no threshold on failed login attempts and the application also explicitly stated the which parameter was incorrect on failed login attempts. Good news for the attacker.

You should always seek to implement some form of anti automation mechanism in your application. Especially when it concerns authentication. Whether it be a CAPTCHA after a certain amount of failed logins, account lock outs etc. Implement something. Each of these measures will have their pros and cons. For example implementing an account lock feature might spike the number of calls to your helpdesk. So weigh each approach accordingly. You should also include a generic message on failed login attempts such as "Username or password incorrect".

At this point I decided to do a simple PoC to show how easy it would be to exploit this. Of course I chose python as my language - as any well thinking person would have :) Now given that all the logic was implemented and exposed in the javascript file mentioned earlier I could either reproduce that logic in python or simply call the javascript methods from python. I chose the latter as it's way easier.

I used PyV8 . From the documentation:
PyV8 is a python wrapper for Google V8 engine, it act as a bridge between the Python and JavaScript objects, and support to hosting Google's v8 engine in a python script.
I hadn't used this library before and so this exercise was more about getting familiar with the library than anything else. The documentation is a bit sparse though.

The script basically loads the javascript file which gives us access to the various functions. It then reads a password file - incidentally this is a great resource for password files - passes each entry through the functions mentioned earlier and creates the required url.

Here are some snippets:

# Set up the environment
ctxt = PyV8.JSContext()

# Load script that has the javascript functions we are interested in

# Open our password file for processing
passwdFile = open('password.txt','r')

 It then submits the url and checks for the appropriate response from the server.

for password in passwdFile:

# Replicate client side logic
hashGuess = ctxt.eval("encode64(hex_md5(trim('%s')).toUpperCase())" % (password.rstrip()))

print "TRYING: " + password

# Create custom url
url = "" %(hashGuess)

# Start session
sessions = requests.Session()
req = sessions.get(url)

# If this message is returned continue guessing
errStr = "Forgot your password?"

if errStr in req.text:
print 'INVALID'
print 'FOUND :' + password

The final PoC can be found here.

To wrap up:
  • Never send sensitive information in the url. In fact James Jardine(awesome resource right here) has nice piece on how to do this in .net. Really simple to accomplish. 
  • Implement generic error messages. Instead of saying the "Username is incorrect", say something along the lines of "Username or password incorrect".
  • Do not expose critical logic on the client side. You certainly shouldn't be exposing your password hashing/encryption/whatever on the client side.
  • Implement some sort of anti automation mechanism. 
That's all folks.

No comments:

Post a Comment