It’s a fact of life: humans are lazy and terrible with keeping secrets. Nowhere is that more true than password management. Passwords are supposed to be a form of identity verification; by providing a password to a system, you prove that you are who you claim to be. The flaw is that passwords can be guessed, and then whomever guesses correctly can then pretend to be someone else on a system. Because users generally aren’t very creative, and the human brain isn’t designed to memorize long, randomized strings of alphanumeric text, there is always a danger that they’ll pick a password that is easily guessed.
A number of high profile hacks of websites over the years has yielded hundreds of millions of real world user passwords in plaintext. This is really telling from a research standpoint, because we can see how common passwords and their variations are by looking at a substantial data set, and generate lists based on the data. For example, a scary number of users still use “password” as their actual password. If you want to break into a system using a username, try “123456,” “password,” “letmein” or something to that affect. The odds are low, but orders of magnitude better than some random characters.
The threat these bad passwords pose is generally limited in effect to the compromised user, and not the system as a whole. As long as the compromised account doesn’t have any administrative privileges, an attacker can’t necessarily cause damage to others. An attacker is more likely to want to use some social engineering on the compromised account for financial gain (that is, people often use the same password everywhere, so if you figure out their password in one place, there is a good chance you can access their email and banking info with the same password). However, don’t discount the possibility of a privilege escalation exploit, or introduction of a worm or something like that. Having an unauthorized user on your system should always be treated a severe threat.
If you are a Linux sysadmin, then you probably want to exercise control over user passwords and make sure they’re reasonably secure. Unfortunately, it’s not a good idea to assign passwords yourself, because A) that usually entails some sort of transmission that can be captured, B) it’s easily forgotten by the end user, C) rotating credentials becomes an issue, and D) if you have a lot of users, you may find yourself needing to memorize tons of credentials (or storing them all somewhere – a nice, juicy target for a hacker). Prepare to spend a LOT of time reminding people what is their password, as well as generating and sending out new passwords regularly, and tracking every state change for every user.
It’s a much better idea to present users with guidelines about acceptable passwords, then reject anything that either doesn’t meet the guidelines or is on the list of common passwords that a hacker would be likely to use in a dictionary attack. That can be tedious to implement on your own, but fortunately there is a ready-made tool baked into Linux PAM: pam_cracklib.so.
Introducing PAM and pam_cracklib.so
For some background, Linux Pluggable Authentication Modules (PAM) is a system that allows for relieving applications of authentication concerns. In other words, a program that wants to validate a user action can simply pass credentials along to PAM, and rely on the response. This makes life easier for everyone: the application developer maintains a smaller, simpler codebase, and the sysadmin can standardize rules across the system by configuring a single source.
Having PAM running on your system doesn’t mean that all applications will use it, only that they can use it if they want. There are some legitimate reasons why an application would want to handle authentication on its own (lack of support for single sign on (SSO) is one reason SSH implements its own authentication system). Fortunately for us, Linux’s tool for setting passwords, passwd
is PAM-aware, and relies on PAM’s password-checking and authentication modules.
pam_cracklib.so is a module designed specifically to sit in the password stack and check a given password against a dictionary. It can also run a number of other checks (Does the password have at least one digit? One uppercase letter? And so on). Basically it follows this process:
- Prompt for a password
- Check to see if the password is in the supplied dictionary list (if no dictionary is supplied, the module uses its own list)
- If the dictionary check succeeds, it will run a number of other checks: is the new password a palindrome? Did the user simply change the case of their old password? Is it too similar to the old password? There are a large number of checks that can be run at this point, all configurable by the sysadmin.
- If the additional strength checks succeed, it will prompt for the password a second time
- If the second entry matches the first, pam_cracklib will hand the password off to the next module in the password stack.
If any of steps 2-4 fail, the module will restart at 1. The sysadmin can configure the number of available retries.
Custom dictionary with pam_cracklib.so
For this guide, I’m going to use a list of the 500 worst passwords based on popularity. Anyone running a dictionary attack would eventually use all of these. Feel free to substitute this for whatever list you want.
Download the password list
I downloaded a public list of the 500 worst passwords to the Downloads folder.
curl -s https://wiki.skullsecurity.org/images/c/ca/500-worst-passwords.txt -o ~/Downloads/500-worst-passwords.txt
This isn’t the only list out there. Google around and you’ll find a handful of sites purporting to have the “correct” list of worst passwords. YMMV. I went with this list because it’s actually in a wordlist format. Other sites present their lists within HTML tables and other goofy stuff. This was just the easiest to work with.
Create the dictionary files
pam_cracklib.so uses a dictionary composed of a series of binary files. In order to create these files from a wordlist, you first need to use a tool called create-cracklib-dict
and output the results somewhere. Run this from the Downloads folder:
create-cracklib-dict -o ~/Downloads/500passwords 500-worst-passwords.txt
Adding the dictionary to pam_cracklib.so
The create-cracklib-dict
program will output 3 files: 500passwords.pwd
, 500passwords.pwi
, and 500passwords.hwi
. We should place these files somewhere out of the way, in a logical spot. We can copy the list to /etc/pam.d:
sudo cp ~/Downloads/500passwords.* /etc/pam.d/
Next we’ll want to tell PAM to use the cracklib module (it’s not usually enabled by default). Run sudo nano /etc/pam.d/passwd
(use another text editor like vim
if you want, just make sure to edit with superuser permissions). Edit the file so that it looks like this:
#%PAM-1.0
password required pam_cracklib.so retry=2 dictpath=/etc/pam.d/500passwords
password required pam_unix.so use_authtok sha512 shadow
The pam_unix.so module is the one that is generally responsible for setting/retrieving/authenticating a user from /etc/passwd and /etc/shadow. The configuration above basically says: “Give the user two tries to pick a password that isn’t among the list of the top 500 worst passwords. If they succeed, encrypt it with SHA512 and maintain a shadow based system.” pam_unix.so will ask for an input password by default, but in this configuration we’re using the use_authtok
parameter, which tells the module to simply accept the token passed to it from pam_cracklib (this is because cracklib has already performed the validity checks for us).
The only caveat to be aware of here is that the root account is not affected by the cracklib policy unless you specify enforce_for_root
. Also, the root user is able to set a password that bypasses the cracklib validation checks. This is helpful if a sysadmin wants to set a temporary password for a user.
Testing the changes
These changes take effect immediately. Once you’ve saved /etc/pam.d/passwd, you can test out the new policy by running passwd
, and trying out a password in the the list:
[rob@netbook Downloads]$ passwd
Changing password for rob.
(current) UNIX password:
New password: <letmein>
BAD PASSWORD: it is based on a dictionary word
New password: <rob>
BAD PASSWORD: it is based on your username
passwd: Have exhausted maximum number of retries for service
passwd: password unchanged
Cool! So now we can have a password policy on the system that prevents users from setting lazy passwords that are easily guessed. This is sort of basic though, and the average user won’t know the policy before attempting to type in their password. This can be a major point of frustration (and support tickets). Let’s make it better.
Adding some complexity
First of all, lets modify our password stack to tell the user what we’re expecting from them in a password update. Let’s create a file that lays everything out:
sudo nano /usr/share/doc/good-password.txt
-----
Hello, %u! You are attempting to change your password. Great! A few ground rules:
1. No lazy passwords. Your input will be run against a list of banned passwords
(see the list at https://wiki.skullsecurity.org/images/c/ca/500-worst-passwords.txt)
If your password is on this list, it will be denied
2. No short passwords. The longer your password is, the harder it is for someone
to guess or figure out with brute force. Minimum password length is 8.
3. There needs to be at least: one uppercase letter, one lowercase letter, one
digit and one special character. This increases the search space and makes
brute force guessing more difficult.
4. You can use spaces. Feel free to use a sentence as your password. For example:
"I bring 2 hats!" is easy to remember, not cumbersome to type, meets all the
above criteria and would take a supercomputer 1.5 million centuries to brute force.
5. You will need to change your password every 3 months.
This simple document explains in simple, yet straightforward terms what sort of input we’re expecting from the user. That way s/he will not need to waste time figuring out the policy by trial and error. Let’s make this text part of the password changing process by modifying /etc/pam.d/passwd
(again, using a text editor with sudo). We’ll also add in the parameters to enforce the things we talked about in the note (password length, character expectations, etc):
#%PAM-1.0
password optional pam_echo.so file=/usr/share/doc/good-password.txt
password required pam_cracklib.so retry=2 minlen=8 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 difok=6 dictpath=/etc/pam.d/500passwords
password required pam_unix.so use_authtok sha512 shadow
Save /etc/pam.d/passwd
and give it another test:
Hello, rob! You are attempting to change your password. Great! A few ground rules:
1. No lazy passwords. Your input will be run against a list of banned passwords
(see the list at https://wiki.skullsecurity.org/images/c/ca/500-worst-passwords.txt)
If your password is on this list, it will be denied
2. No short passwords. The longer your password is, the harder it is for someone
to guess or figure out with brute force. Minimum password length is 8.
3. There needs to be at least: one uppercase letter, one lowercase letter, one
digit and one special character. This increases the search space and makes
brute force guessing more difficult.
4. You can use spaces. Feel free to use a sentence as your password. For example:
"I bring 2 hats!" is easy to remember, not cumbersome to type, meets all the
above criteria and would take a supercomputer 1.5 million centuries to guess.
5. You will need to change your password every 3 months.
Changing password for rob.
(current) UNIX password:
New password: <rob>
BAD PASSWORD: it is WAY too short
New password: <letmein>
BAD PASSWORD: it is based on a dictionary word
passwd: Have exhausted maximum number of retries for service
passwd: password unchanged
Great. Now the user knows exactly what is expected of them, has some examples to work with, and the policy is enforced correctly. The root user can still have any old password, and can set user passwords to anything on a temporary basis. As a sysadmin, we can update the policy and/or the explanation on the fly at any time.
Final thoughts
The pam_cracklib module really is a neat and necessary tool for Linux sysadmin’s to keep handy. Using it can help seriously harden the system against password crack attempts, particularly in the (hopefully) unlikely situation where a hacker has somehow obtained copies of /etc/passwd
and /etc/shadow
. A brute force and/or dictionary attack with 100 billion guesses/sec still won’t be economically feasible.
The only other thing I would add here that isn’t addressed in this post is limiting the number of guesses per second that can be made against an account. There are really only two situations in which this would occur:
- A hacker somehow has local access to the machine and is trying to log in as a user.
- A hacker is trying to gain remote access to the machine (via SSH for example)
Either situation would be mitigated by employing some kind of rate limiting or blocking. Exponentially increase the wait time between password attempts. Page a sysadmin to repeated failed login attempts on an account. Automatically block IP addresses with X number of failed SSH logins in a given time period. There are quite a few options for this, but they’re outside the scope of this post. I might make a post about this in the next few weeks.
In any case, a combination of pam_cracklib (with reasonably secure parameters and a decent wordlist) and rate limiting the number of guesses/sec that can be made on a system would effectively stop dictionary attacks, and even prevent them from being carried out for more than a few seconds. If you’re looking to harden your Linux machine(s) against intrusion, this is definitely something to consider.