| Small errors can have big consequences |
| Monday, 23 February 2009 14:39 |
|
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 MD5Adding 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:MD5Back 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": /** The validation method salt2_login_validate() again called the custom function salt2_validate_password(). /** 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. /** The programming errorI 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!
|
|
Canonical URLs and Joomla!
|
|
Using Eclipse as Joomla! IDE
|
|
Writing your own Joomla! splitmenu
|
|
Comparing VirtueMart and Magento
|
|
Is Joomla! safe?
|
Follow osSupportDesk on Twitter