Open Source Network Blog

Small errors can have big consequences
Monday, 23 February 2009 14:39
Jisse Reitsma

Jisse Reitsma

Jisse Reitsma is co-founder of Jira ICT, wrote a book on Joomla! Templates, teaches many courses and programs in Joomla!, Magento and Drupal. Authors profile

Some time ago I was developing a Drupal 6 module which enhances the default Drupal passwords with a salt. This did not only involve a change in the password-storage mechanism, but also in the authentencation procedure. But somewhere in the code I made a mistake by returning TRUE where it should return FALSE instead. A bad programming error which could have had fargoing consequences if it was not caught in the act.

By default, Drupal passwords are stored in the database table "users". They are encrypted however with MD5 which makes them less safe. If a hacker somehow got his fingers around these MD5-passwords, he would have the option to brute-force these passwords using something called Rainbow Tables. A lot of webapplications have already been migrating their passwords from this MD5-encryption towards a more secure encryption.

The problem of MD5

One method is to use a different encryption altogether, RSA for instance. But the most common approach is to add a salt to the MD5-encrypted password. First let me explain a bit about MD5. MD5 is an encryption algorithm which in fact hashes the output to a 32-character string might look the following:

5f4dcc3b5aa765d61d8327deb882cf99

The string above is the MD5-checksum of the string "password". Now the problem is that every time when you encrypt this password, you will get the same MD5-checksum. This makes it possible to keep a database of MD5-checksums and match the MD5-checksum with the right password. One way to create this database is by using Rainbow Tables, but the Internet can be seen as another kind of database. Try googling "5f4dcc3b5aa765d61d8327deb882cf99" and you will know what I mean.

Besides the fact that hackers can use Rainbow Tables to crack MD5-passwords, the MD5-algorithm itself is weak as well which is proven by the recent vulnerability of MD5-based SSL-certificates.

Salt plus MD5

Adding a salt to the password ensures it is harder to guess (but not impossible). To explain this, we have to look at the algorithm that is used by the default MD5-encryption:

B = md5( A )

Because of the relation between A and B is static, if you have B, it is quiet easily to guess A. All you need to do is build a database based on possible A-B relations and hope that A is among them. Such a database can be re-used for every website that uses plain MD5-encryption. By adding a salt however, such a database needs to be generated for each salt.

In the pseudo-code below C represents the salt.

B = md5( A + C )

Now the hacker needs to create a specific database of (A+C)-B relations specific for the C-salt. Depending on the salting-mechanism C might be a static string for all passwords on a site, or it might be randomly generated for every password. In the last case, the salt is unique for the password, so the hacker needs to generate a new Rainbow Tables database for every password. This might take weeks if not months depending on the complexity of the password and/or the hackers luck.

How is the salt used during authentication?

Because the salt is needed for encryption, it is almost impossible (at least: very hard) to decrypt the password. Also brute-force is made harder, especially when the salt is newly generated for every password. But how does the authentication work?

It is simple: The script that needs to authenticate an user receives its username and password from some kind of login-form. Now the script uses the algorithm to encrypt the password and then tries to match that temporarily encrypted password with the encrypted password of the user stored in the database. But as soon as the salt is added to the whole encryption, the authentication script also needs the same salt to make the password encrypt in exactly the same way it was stored earlier. The solution is to stored both the encrypted password as well as the salt:

B + C

An example would like this:

9e0ca7eec1f9a44f4e6a8d6cba1de703:salt

Here the MD5-checksum are shown followed by a semi-column (:) followed by the salt "salt". In reality the salt could be a plain word, but also a MD5-like string of 32 characters:

9e0ca7eec1f9a44f4e6a8d6cba1de703:6IMghS4LVP24Txj01EhBx3H00jl6uRTW

MD5:salt or salt:MD5

Back to the beginning: I was writing a Drupal-module that enabled passwords to be stored (and authenticated) using a MD5+salt mechanism. Now this module would add security to Drupal, but it would also allow for a migration from Joomla! to Drupal. In later versions of Joomla! a salting mechanism had already been added, but Drupal was still using plain MD5-encryption.

Now there already was a "salt" module out there which added salting to Drupal. But it had two shortcomings: First of all it used a salt for the entire site, while Joomla! uses a new salt for every password. Second of all the existing module stored passwords using the syntax C:A (so first the salt, then the password), while Joomla! used A:C (first the password, then the salt). Those two shortcomings made it impossible to use the existing salt-module.

A new Drupal salting module

The wonderfull thing about open source is that if you don't like it, you can change it. So based upon the existing module I wrote my own Drupal module that fitted to the needs. Drupal uses hooks to plug your module into certain events. For instance when something happens to the user (the user is created, modified or removed), you can use the HOOK_user() function to alter or extend the default behaviour.

The new module was called "salt2", so the HOOK_user() method was named "salt2_user()". When editing or adding an user the password was taken from the form-array and saved in a seperate database table called "salt2". When deleting an user, the appropriate record from the "salt2" table was removed.

Also the function HOOK_form_alter() was used to add a new authentication-validator to the login-forms "user_login_block" and "user_login":

/**
* Implementation of hook_form_alter.
*/
function salt2_form_alter(&$form, $form_state, $form_id) {
  if ($form_id == 'user_login_block' || $form_id == 'user_login') {
    array_unshift( $form['#validate'], 'salt2_login_validate' );
  }
}

The validation method salt2_login_validate() again called the custom function salt2_validate_password().

/**
 * Validation handler for the user_login form.
 */
function salt2_login_validate($form, &$form_state) {

  global $user;
  $form_values = $form_state['values'] ;
 
  // Name and pass keys are required.
  if (!empty($form_values['name']) && !empty($form_values['pass'])) {
    $account = user_load( array( 'name' => $form_values['name'], 'status' => 1));
    if (salt2_validate_password( $account->uid, trim( $form_values['pass'] ))) {
      $user = $account;
      user_authenticate_finalize($form_values);
    }
    return $user;
  }
}

The function salt2_validate_password() was used to check the database for a match between the original salted password and the salted password handed over from the form. If a match was found the salt2_validate_password() method would return TRUE, and the salt2_login_validate() method would return the appropriate $user object. So far so good.

/**
 * Helper function to validate a salted password
 */
function salt2_validate_password( $uid, $password ) {
  $p = db_fetch_object( db_query( "SELECT * FROM {salt2_passwords} WHERE `uid`='%d'", $uid )) ;
  if ($p == NULL) {
    return TRUE ; // Here's the bug!
  }
  else {
    $encrypted = explode( ':', $p->password ) ;
    if (salt2_encrypt( $password, $encrypted[1] ) == $p->password) {
      return TRUE ;
    }
  }
  return FALSE ;
}

The programming error

I took the liberty of writing a full article on MD5-passwords but in fact the programming error did not have anything to do with it. I was so focussed on getting the salt-authentication done, I forgot about one scenario. What if an user tried to authenticate but no entry was found in the new "salt2" module was found.

The appropriate thing here would be that the salt2_validate_password() method returned FALSE, because no match could be found. This would mean the whole salt2_login_validate() would not alter the $user object, and Drupal would head over to the next validation in line - probably the standard authentication with default MD5-encryption. Instead, salt2_validate_password() returned TRUE if there was no record found. While developing the module I had a different logic in mind and there this return made sense. In the end the logic changed and a big vulnerability was created.

Ofcourse nothing gets put into production without testing, but only user-records that were created after the module was put into use were tested. Older user-records without the new MD5+salt passwords were not checked, and the result of this programming error was that anybody whose password was not yet updated, could login with any password! Horrifying.

What have we learned?

So we made a mistake. We made a very bad mistake. In the end the vulnerability was discovered before it could do any harm. But it points out a very important thing to remember: When you're missing around with authentication procedures, test thoroughly!

 

 
Get help for Joomla Now

Stay In The Loop

Subscribe now and we'll send you our latest News, Tips & Trics and Tutorials by email.
Jira ICT
Open Source Support Desk

Latest Comments

Canonical URLs and Joomla!
... I am working on a pretty huge page for a cli
Using Eclipse as Joomla! IDE
Codelobster isnt bad... I'm a dotnet developer...
Writing your own Joomla! splitmenu
Great job and it's worked! But, how to change the
Comparing VirtueMart and Magento
Magento is way worse of spaghetti code. Do you rea
Is Joomla! safe?
You can nver make any software full proof, the lat

Follow Us on Tiwtter

osSupportDesk Yes! RT @rdeutz: @HermanPeeren in reality after #jab11 doesn't exists, this time frame is called before #jab12 #partyon
ABOUT 24 HOURS AGO
osSupportDesk Available at ourcmsrocks.com soon -> Buy a Brian: http://bit.ly/2wmeeo #joomla
Thursday, 02 September 2010 09:01
osSupportDesk RT @MarcosPeebles: #Joomla vote for the OSS Hall of Fame award http://tinyurl.com/ourcmsrocks-showit go and show your support
Thursday, 02 September 2010 08:06
twitter Follow osSupportDesk on Twitter
Home Blog Programming Small errors can have big consequences