I had a situation recently where every tool that talked to LDAP or LDAPS failed. That means no SharpHound, no, no certipy and no ldapdomaindump. The one tool I could get working however, was msldap. This blog post aims to give some examples of how to use this tool for real-world engagements.

Thanks to p0dalirius1 for their very useful blog post that includes a bunch of LDAP queries.

What is LDAP?

LDAP (Lightweight Directory Access Protocol)

It’s the protocol used to query and modify the structure of Active Directory. Think SQL… but for a database of users, groups, network resources etc.

When performing a penetration test, LDAP contains useful information about how Active Directory is configured, about who users are and what access they get to the network, about what groups users are placed into and so much more.

Typically, any account can query Active Directory. This includes computer accounts, service accounts and user accounts.

Using msldap

msldap is a project designed to provide a simple framework for developers to use when querying LDAP. It also includes a built-in LDAP client that we will be using.

Connecting to LDAP server

The first step, of course, is to connect and authenticate against the LDAP server. msldap uses connection urls to define connections. These contain the protocol, authentication method, credentials, hostname or IP address and optional parameters that can change how msldap behaves.

msldap 'ldap+ntlm-password://DOMAIN\Username:Password01@'

Or alternatively, to use LDAPS (the encrypted variant of LDAP):

msldap 'ldaps+ntlm-password://DOMAIN\Username:Password01@'

We can also authenticate using a bunch of other cool supported modes such as:

msldap 'ldaps+ntlm-nt://DOMAIN\Username:8846f7eaee8fb117ad06bdd830b7586c@'

You can find more good examples in the README.

WARNING: The anonymous auth example is outdated/wrong and won’t work, if you want anonymous auth do:

msldap 'ldaps+simple://'

Important Note

The first IMPORTANT command we must run after connecting to a server is login:

$ login

If we don’t do this, every command will fail (because we aren’t connected and authenticated against the LDAP server yet).


msldap includes a handy feature to dump the information stored about users and computers from LDAP:

$ dump

This creates two Tab-Separated Values (TSV) files.

Find Domain Admins

Referring to the aforementioned1 p0dalirius article, there is a specific query that is useful for finding Domain Admins:

$ query '(&(objectCategory=user)(adminCount=1))'

This will return a list of users that are either Domain Administrators, or transitively Domain Administrators (i.e. they are not part of the Domain Administrator group, but have the same level of privileges).

Enumerate Certificate Templates

Certificate templates can be misconfigured quite easily, and tools such as certipy and certify are usually the go-to for finding and exploiting vulnerable configurations. When LDAP fails though these tools can no longer use LDAP queries to enumerate and identify vulnerable certificate templates.

msldap has some in-built commands that can help find these vulnerable certificate templates.

The certify (confusing I know) msldap command will spit out certificates that have vulnerabilities if you run it as shown. However, I’m not quite sure if this supports checks for all of ESC1-10… you can see a full list of all the checks it goes through here.

$ certify vuln

There is also the more boring option which outputs all the details of the certificate templates:

$ certtemplates

Enumerate Group Policies

Group Policies can be a goldmine23 for information

$ gpos
cn: {31B2F340-016D-11D2-945F-00C04FB984F9}
displayName: Default Domain Policy

cn: {6AC1786C-016F-11D2-945F-00C04fB984F9}
displayName: Default Domain Controllers Policy

Get User Descriptions

I recommend having a quick look at these by-hand after searching for ‘pwd’ and ‘password’. I’ve seen networks where the user description just straight up contained a password, i.e. the description of the Administrator account would be something like S3cur3itY!!--!!Test.

$ query '(&(objectCategory=user))' 'samAccountName','description'
{'objectName': 'CN=Administrator,CN=Users,DC=corp,DC=enterprise,DC=local',
'attributes': {'description': 'Built-in account for administering the
computer/domain', 'sAMAccountName': 'Administrator'}}
{'objectName': 'CN=Guest,CN=Users,DC=corp,DC=enterprise,DC=local',
'attributes': {'description': 'Built-in account for guest access to the
computer/domain', 'sAMAccountName': 'Guest'}}
{'objectName': 'CN=vagrant,CN=Users,DC=corp,DC=enterprise,DC=local',
'attributes': {'description': 'Vagrant', 'sAMAccountName': 'vagrant'}}
{'objectName': 'CN=krbtgt,CN=Users,DC=corp,DC=enterprise,DC=local',
'attributes': {'description': 'Key Distribution Center Service Account',
'sAMAccountName': 'krbtgt'}}
{'objectName': 'CN=testuser,CN=Users,DC=corp,DC=enterprise,DC=local',
'attributes': {'sAMAccountName': 'testuser'}}
{'objectName': 'CN=alice,CN=Users,DC=corp,DC=enterprise,DC=local',
'attributes': {'sAMAccountName': 'alice'}}
{'objectName': 'CN=bob,CN=Users,DC=corp,DC=enterprise,DC=local', 'attributes':
{'sAMAccountName': 'bob'}}
{'objectName': 'CN=svc-mssql,CN=Users,DC=corp,DC=enterprise,DC=local',
'attributes': {'sAMAccountName': 'svc-mssql'}}

Now this can be a little hard to parse as a human, and writing a way to decode this data is a bit of a pain… so here is the hard work done for you! (as a bash script):

while read -r var;
    echo $var | python -c 'print(__import__("json").dumps(eval(input())))';
done < <(\
    msldap 'ldap+ntlm-password://CORP\alice:password123!@' \
    "login" \
    "query '(&(objectCategory=user))' 'samAccountName','description'" \
    | tail -n +2 #remove the BIND OK! from the output

Obviously there is some stuff here that needs to be customised, specifically the connection string and the specific query you want, but this will out JSON objects for each line returned, which is much easier to use with any of your chosen tools. For example, you could one-line this and pipe it into jq4:

while read -r var; do echo $var | python -c 'print(__import__("json").dumps(eval(input())))'; done < <(msldap 'ldap+ntlm-password://CORP\alice:password123!@' "login" "query '(&(objectCategory=user))' 'samAccountName','description'" | tail -n +2) | jq '.attributes'

Which will spit out:

  "description": "Built-in account for administering the computer/domain",
  "sAMAccountName": "Administrator"
  "description": "Built-in account for guest access to the computer/domain",
  "sAMAccountName": "Guest"
  "description": "Key Distribution Center Service Account",
  "sAMAccountName": "krbtgt"

You could also alternatively write a python script to use msldap as a library… but that’s an exercise for the reader :).


One of the fields you need to provide to ADFSSpoof when performing a GoldenSAML attack is the ObjectGUID. Unfortunately most tools don’t output this… but don’t worry, msldap has your back:

$ user 'alice'

This will return a big list of information about the user, and this includes the objectGUID:

sn: None
cn: alice
... excerpt ...
objectGUID: 0c9a578b-eb3f-42bb-8e9a-5e2293c1d6fe
objectSid: S-1-5-21-4065317252-3566386198-2278894098-1105
... excerpt ...


msldap is an effective tool that can connect to LDAP over various methods that allows it to be resilient against jank network set-ups that cause other tools to fail.

  1. Super handy list of LDAP queries, see ↩︎ ↩︎

  2. Auto-login passwords are sin, see ↩︎

  3. If you can control GPOs you can do some funky stuff, see or ↩︎

  4. jq is a json processor for the command line, see ↩︎