You can implement a wide array of complexity requirements for your password programs, but in the end users will gravitate towards the same patterns. They want things which are easy to remember, and quite often this will be at ends with a strong security model. There's no real point of requiring one uppercase, one lowercase, and one digit if all the users will do is make the first character uppercase, the last character a digit, and call it a day. Password1... done. Need to rotate your password? Password2. Weak passwords are incredibly dangerous, and are often the sole barrier to sensitive and complex systems which hold personal and confidential information. What we need here are stronger guarantees around the quality of the secrets people are crafting.

LDAP-based infrastructures give you a lot of flexibility in the services you can provide for end-users, administrators, and auditors. In order to validate the strengths of your policies (and effectiveness of user education), it's important to perform internal password audits against your directory's hashes. Fortunately, with LDAP this isn't incredibly hard.

According to the standard, LDAP passwords are normally stored in an attribute called userPassword. Using a sufficiently privileged account (like the Directory Manager) we should be able to query this attribute on all of the user objects in the directory.

NOTE: Ideally the Directory Manager is never used after initial creation and configuration of your directory. All future configurations and processes should be performed with delegated permissions on specific accounts (limited by roles, if possible.) But the real world is what it is, and sometimes we need to do what we need to do.

NOTE: The examples in this article will all be done in Hashcat's working directory. Feel free to adjust these examples as needed if you place your files in a different path.

Using a utility like ldapsearch, we should be able to use our privileged account to extract account information. This utility is available in most Linux distributions, and in Debian/Ubuntu can be installed with:

$ sudo apt install ldaputils

With our utilities installed, we can query the directory and dump the results into a file called useraudit.txt.

$ ldapsearch -o ldif-wrap=no -D "cn=Directory Manager" -x -W -LLL 'uid=*' userPassword > useraudit.txt

In this example, we use -x to do a simple bind to the directory. The -W flag is used to prompt for a password. We also use -LLL to display the output in LDIF format, with comments and versioning suppressed. The -o flag is being used here to pass additional formatting preferences. If your infrastructure has a more complicated configuration, you may need to provide alternate flags to ldapsearch. The man page has a ton of great information, so feel free to consult that for additional options.

Now we should have a file with our user data, which should contain something similar to the following:

# smith, users, accounts, office.example.com
dn: uid=smith,cn=users,cn=accounts,dc=office,dc=example,dc=com
uid: smith
userPassword:: e1NTSEE1MTJ9WkdkbzZFYnF0c3RzNTU0a2VkWFVlTzc3R3duVmV4bmw4OVNkQTZjV2dqT3c0byszRXd6RkFpN1hKUmVGY0c3eHFMU0Rhc2owNXF3OHd1U2pJY3BnaTBYbEdBbFNSZzY3
# sam, users, accounts, office.example.com
dn: uid=sam,cn=users,cn=accounts,dc=office,dc=example,dc=com
uid: sam
userPassword:: e1NTSEE1MTJ9bnFLZG1IV2tJc2N0M2xzRHNtWmUwbjN6Q0dkalFKSU1oYjVrY2M5NEF1L250U2hFWGliMkJCb2sweW9aa2JiQWpMQzRmaUFlY3FCMzBMdVUzcjJEajVKWmtmNU85K3dF
# bob, users, accounts, office.example.com
dn: uid=bob,cn=users,cn=accounts,dc=office,dc=example,dc=com
uid: bob
userPassword:: e1NTSEE1MTJ9OUUrOXR2M1I3QXFNVVFSdFRjVGhYOGoxYlg1YTdGS1F0VXJSMVZVNlp1Z1krcGRiYXM3NFMyMDFhdEdoOGh2blBDckc2a1ZRRlhJaHBLVFJXUVRQQnNxdkttVnhUSElF

The results from our directory should be base64 encoded, and will need to be decoded to get a hash format Hashcat wants. We can use a Bash one-liner to do this work for us:

$ for hash in $(grep --color=none -oE e1NT.+ useraudit.txt); do echo "${hash}" | base64 -d; echo; done > userhashes.txt

This just loops through our data, looking for password hashes, base64 decodes them, and drops the results into a new file. (The --color flag is specified so that terminal escape sequences are not accidentally passed to base64.)

Let's take a quick look at our new file.

$ cat userhashes.txt
{SSHA512}ZGdo6Ebqtsts554kedXUeO77GwnVexnl89SdA6cWgjOw4o+3EwzFAi7XJReFcG7xqLSDasj05qw8wuSjIcpgi0XlGAlSRg67
{SSHA512}nqKdmHWkIsct3lsDsmZe0n3zCGdjQJIMhb5kcc94Au/ntShEXib2BBok0yoZkbbAjLC4fiAecqB30LuU3r2Dj5JZkf5O9+wE
{SSHA512}9E+9tv3R7AqMUQRtTcThX8j1bX5a7FKQtUrR1VU6ZugY+pdbas74S201atGh8hvnPCrG6kVQFXIhpKTRWQTPBsqvKmVxTHIE

Excellent! Now, we are ready to run our password audit. In this example, the hashes are in LDAP-SSHA512. You can use this link on the Hashcat wiki to look up a hash type with it's mode if you need. Our hash is going to be mode 1711.

As an easy first step, let's just run a dictionary attack with the ever popular Rockyou dictionary. This dictionary is freely available on the web, and comes default on Kali Linux. (For our examples, we created a folder inside the Hashcat directory called wordlists, and our dictionaries are in here.) Our output should resemble the following:

$ ./hashcat -m 1711 userhashes.txt wordlists/rockyou.txt
hashcat (v5.1.0-1725-g9f9ed78c) starting...

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256
Minimim salt length supported by kernel: 0
Maximum salt length supported by kernel: 256

Hashes: 3 digests; 3 unique digests, 3 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1

Applicable optimizers:
* Zero-Byte
* Early-Skip
* Not-Iterated
* Raw-Hash
* Uses-64-Bit

Session..........: hashcat
Status...........: Cracked
Hash.Name........: SSHA-512(Base64), LDAP {SSHA512}
Hash.Target......: userhashes.txt
Time.Started.....: Sun Mar  8 21:53:19 2020 (0 secs)
Time.Estimated...: Sun Mar  8 21:53:19 2020 (0 secs)
Guess.Base.......: File (/Users/c/Security/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#2.........:   193.0 kH/s (9.21ms) @ Accel:8 Loops:1 Thr:8 Vec:1
Recovered........: 3/3 (100.00%) Digests, 3/3 (100.00%) Salts
Progress.........: 15360/43033152 (0.04%)
Rejected.........: 0/15360 (0.00%)
Restore.Point....: 3072/14344384 (0.02%)
Restore.Sub.#2...: Salt:1 Amplifier:0-1 Iteration:0-1
Candidates.#2....: adriano -> horoscope

If we check the pot file, we should have our cracks! However, cracking the hashes isn't enough. In order to make this an effective audit, we need to enforce password rotation for our impacted users. And for that, we need to map the cracked passwords to their owners. We can easily do this with a tiny bit of Python:

import base64

with open('useraudit.txt') as fp:
  uid=""
  pwd=""
  for line in fp.readlines():
    line = line.strip()
    tokens = line.split(":")
    if (tokens[0] == "uid"):
      uid=tokens[1]
    if (tokens[0] == "userPassword"):
      pwd=base64.b64decode(tokens[2])
      with open('hashcat.potfile') as fp2:
        for found_hash in fp2.readlines():
          found_hash = found_hash.strip()
          mtch = found_hash.split(":")
          if (mtch[0] == pwd):
            print ("%s:%s") % (uid,mtch[1])
            break
fp.close()

Now, if we dump this into a file called parse.py and run it, we get something like the following:

$ python parse.py
 smith:changeme
 sam:Princess
 bob:1q2w3e4r5t

This is pretty successful! We can now go to our users and help educate them about creating stronger passwords. Perhaps encourage them to create passphrases instead?

We don't have to stop here, of course. If we didn't crack all of the accounts, we can start iterating attacks. For example, combine using the Rockyou dictionary with the d3adhob0 rule set.

$ ./hashcat -m 1711 userhashes.txt wordlists/rockyou.txt -r rules/d3adhob0.rule

NOTE: If you don't have the d3adhob0 rule set, you can find it on GitHub

And we can keep iterating our attacks. That said, you'd be surprised how many passwords can be recovered with just those two.