Nico's hax0r blog

Client-side password hashing before log-in.

UPDATE: This method protects your password (slightly, at least), but it does not protect user accounts on the forum. If someone *sniffed* your password hash, it could just be captured and resent to the server it was gathered from, along with your user name. The forum would not know whether the hash was directly submitted, or if your Javascript code created it before logging in.

I’ve seen vBulletin doing this, and I thought it was very interesting.

What they do is, they hash the user password using JavaScript before logging in. How MD5 hashing works is no secret. And not even reverse engineering the code that generates these hashes will make them “decryptable”. But I don’t want to get into this now. What I’m saying is, that JavaScript can create valid MD5 hashes, just like PHP would do. And we can take advantage of this to make our systems a little more secure.

Here’s an example JavaScript code that creates MD5 hashes:

http://github.com/kvz/phpjs/raw/master/functions/strings/md5.js

(Feel free to use it.)

If the user has JavaScript enabled on his browser, vBulletin converts his password as soon as the form is being submitted, and removes the value from the original password field. So that only the hashed password will be sent to the server.

POST /forums/login.php?do=login HTTP/1.1
Host: www.xxxxxx.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv: […]
Accept: text/html,application/xhtml+xml,application/xml;q=0.8 […]
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Referer: http://www.xxxxxx.com/forums/index.php
Cookie: [ … ]
Content-Type: application/x-www-form-urlencoded
Content-Length: 198

vb_login_username=Nico&cookieuser=1&vb_login_password=&s=&securitytoken=guest&do=login&vb_login_md5password=577********************f03d&vb_login_md5password_utf=577******************f03d

This is an example request that my browser sends to the server. As you can see, my password is only being sent in form of MD5 hashes. No trace of the real password!

So why are they doing this you might ask?

Security is the obvious answer. If you’re not connected to the server using a secure (SSL) protocol, your request is being sent “as is”, meaning PLAIN TEXT, and can be seen by others, using certain packet analyzing or HTTP sniffing tools.

Since SSL certs are not always an option, and sometimes a little pricey too… And since I’m yet to see a message board actually using a secure log-in system, I find this method rather interesting. I’m by no means trying to say that this method is anywhere as secure as SSL connections, or that it can replace them.

Implementing this method into existing log-in systems might be a little bit difficult or even impossible if you’re using “salts” or anything besides just MD5.

However, In case you’re interested, here’s an example on how to use this method in your own forms.

<script type="text/javascript" src="http://github.com/kvz/phpjs/raw/master/functions/xml/utf8_encode.js"></script>
<script type="text/javascript" src="http://github.com/kvz/phpjs/raw/master/functions/strings/md5.js"></script>

<script type="text/javascript">
<!--
function pwd_handler(form)
{
if (form.password.value != '')
{
form.md5password.value = md5(form.password.value);
form.password.value = '';
}
}
//-->
</script>

<form action="login.php" method="post" onsubmit="pwd_handler(this);">
<input type="text" name="username" />
<input type="password" name="password" />
<input type="hidden" name="md5password" value="" />
<input type="submit" value="Log in" />
</form>

The md5() function from phpjs relies on their utf8_encode() function as well, so we must include it.

The above code looks pretty much like most log in forms, except that I’ve added a hidden field called “md5password”, which will later hold the hashed password. And I’ve added an “onsubmit” handler to the <form> tag.

The JavaScript code does the same as vBulletin’s does. It prevents the original password from being sent (meaning it removes its value), and instead it includes an MD5 hash of it.

Please note that this only works if the user has JavaScript enabled. So on the server-side, we cannot rely on the password being hashed. We have to check for it.

In this example I’m assuming you’re only using MD5 without salts or any other ingredients on your user’s passwords.

<?php

$username = $db->escape_string((string) $_POST['username']);
$query = $db->query("
SELECT `username`, `password`, `salt`
FROM `users`
WHERE `username` = '{$username}'
");

if (!$userinfo = $query->fetch_assoc())
{
// Error... user not found
}
else
{
if ((!empty($_POST['md5password']) AND $_POST['md5password'] == $userinfo['password']) OR
(!empty($_POST['password']) AND md5($_POST['password']) == $userinfo['password']))
{
// Success... user logged in
}
else
{
// Wrong password
}

}

?>

Please also note that this code is for reference only. A simple copy and paste will not work. Furthermore, I cannot provide many more examples as the password hashing can be done in too many ways. But I’ll post one more for vBulletin-a-like systems where a custom salt is being used:

if ((!empty($_POST['md5password']) AND md5($_POST['md5password'] . $userinfo['salt']) == $userinfo['password']) OR
(!empty($_POST['password']) AND md5(md5($_POST['password']) . $userinfo['salt']) == $userinfo['password']))
{
// Success... user logged in
}

Make sure you’ve previously selected the user’s salt from the database and it’s present in the $userinfo array.

As I said earlier, implementing this method into your existing log-in system might be impossible. It all depends on how you’ve been handling the passwords from the beginning.

Questions?


  1. zoliky reblogged this from nicoswd
  2. nicoswd posted this