In working with Solar today, I discovered an issue related to the crypt() function and password files generated by Apache htpasswd. Technically, it’s not a security issue with either of those fine programs, because they do work as documented and intended. However, due to my own ignorance of the limitations of crypt(), I created a security issue of my own; perhaps this post will help others avoid it.
The Solar_User_Auth class is very much like the PEAR Auth class, in that it lets you pick different container or storage types for your username/password authentication. You can use a database table, LDAP, POP or IMAP email account, a .ini file, and more. Similar to LiveUser, Solar_User_Auth also comes with a “Multi” container that lets you specify multiple authentication sources, so you can fall back from one to another automatically.
My problem today was with the “Htpasswd” container for Solar_User_Auth. Apache comes with a utility called “htpasswd” which lets you create a file of usernames and encrypted passwords. The file format is pretty straightforward; each line consists of “username:cryptedpass”. To create a htpasswd file and insert the first username/password combination, you would issue “htpasswd -c /where/you/want/htpasswd.data -a someuser”; it will create the htpasswd.data file and prompt you for the password for “someuser”, encrypting the password with the system crypt() function.
Now here’s the thing about crypt() … effectively, it only looks at the first 8 characters of the password to generate the encrypted hash. (Yes, there are ways to make crypt() use a longer salt, but that’s not pertinent to this particular discussion, as we are only concerned with the way Apache htpasswd uses the crypt() function.)
Thus, if you have a password *longer* than 8 characters, as long as the first 8 characters match, crypt() will call it a valid match. For example, if your password is “password” and is stored as a crypted hash in a htpasswd file, checking against “passwordX” will be exactly the same as checking against “password”, “password123”, and so on; that is, it will be returned as a valid check because the first 8 characters match properly. Similarly, if your password is “longpassword” and you check against only “longpass”, that will be returned as valid, too.
Obviously this is a problem. The solution, at least for Solar_User_Auth_Htpasswd, is that from now on, password checks longer than 8 characters will be rejected automatically as invalid. This sidesteps the problem entirely, even though it does limit users who want to use the Htpasswd driver to passwords of 8 characters or less. The next release of Solar will have this patch in place, and it has already been committed to the Subversion repository for Solar.
Have I missed anything important here? Has anyone else out there run into anything like this? If so, what was your solution?
Update: (2005-04-14) Although I’m retaining the 8-character limit under default DES encryption, I’ve added SHA1 and APR1-MD5 support, which should help a great deal.