How Potential Attackers Can Achieve Privileged Persistence on a DC through DnsAdmins
The Semperis Research Team recently expanded on previous research showing a feature abuse in the Windows Active Directory (AD) environment where users from the DnsAdmins group could load an arbitrary DLL into a DNS service running on a Domain Controller. Yuval Gordon of the Semperis Research Team expanded this research to show how a problem with the previous technique can be overcome, and to show that an adversary could use this tactic to leave an undetected backdoor into a company’s Active Directory—a significant security risk.
In a post on Medium in 2017, security researchers showed how users from the DnsAdmins group could use a “feature” in the Microsoft DNS management protocol to make the DNS service load any DLL. This service runs on Domain Controllers as NT Authority\System, allowing DnsSAdmins to escalate privileges to SYSTEM on DC (with permissions equal at least to Domain Admins). This “cute trick,” as the original researcher, Shay Ber, called it, can be useful for Red Teams exploring AD privilege escalation and is a potential backdoor for attackers into the domain controller.
In this post, I’ll expand on Shay Ber’s research by showing how to overcome a problem with the previous technique and how to make it more stealthy. I’ll also review the required permissions to show that an adversary could use this tactic to leave a backdoor to DC that likely would not be noticed and might bypass some tools.
To recap the previous research conducted by Shay Ber and Nikhil Mittal, they showed that it is possible to load an arbitrary DLL into a DNS service running on a Domain Controller through the following steps:
- The attacker needs to be part of the DnsAdmins group to run dnscmd executable and point the service to a DLL on a UNC path.
- The attacker restarts the DNS service using “sc.exe stop/start dns”—this is a limitation as default configurations do not allow DnsAdmins users to stop and start services.
My research expands understanding of this attack capability in the following ways:
- It is not strictly necessary to be part of DnsAdmins to carry out this attack, but only to have read/write access to the MicrosoftDNS container and RPC access to the DC.
- It is possible to restart the DNS service with only the privileges listed in 1.
It is therefore possible for an attacker with sufficient privileges to create/update a user with minimal permissions and only direct read/write access to the MicrosoftDNS container. This user would, in most cases, fly “under the radar” as container permissions are seldom monitored. This user could remain undetected and could be used to run arbitrary commands at DC SYSTEM privileges by loading a DLL into the DNS process.
Three years ago, in the Medium post “Feature, not bug: DnsAdmin to DC compromise in one line,” Shay Ber noted that any member of the DnsAdmins group could make the DNS Server service load a DLL as NT Authority\System on the DC on next service restart.
DnsAdmins by default does not have the required permission to restart the service on the DC remotely, meaning that attackers who want to abuse it will have to wait (or use other methods) until the server or service is restarted to make their payload run.
My research mission was to search for a way to force the service to restart or load the DLL instantly and do it as stealthily as I could. (Even if I had the permissions for restarting using sc.exe, it would generate numerous logs and would probably be detected.) This approach turned out to be surprisingly easy but had some unexpected consequences. Before reading any further, I recommend reading Ber’s original research first.
A new start
I started by simply reviewing the MS-DNSP specifications, searching for any mention of the word Restart when I encountered the following:
This line indicates that one of the commands the server accepts (pszOperation) in R_DnssrvOperation is restart, which will (unsurprisingly) restart the DNS server process. Sounds perfect!
To make my life easier, I looked at the dnscmd tool (CLI tool for DNS server management) documentation and searched for a way to use it to send a restart command to the DNS server, but found nothing.
I thought I should try looking at the tool’s strings for “restart” and (surprisingly), there it was:
Although neither the Microsoft documentation nor the tool’s help mention it, there is a “/Restart” switch you can use in dnscmd to trigger a DNS server’s process restart.
The service triggered an internal restart, calling an internal function called “reloadShutdown” and then starting everything up again without terminating the process, as opposed to a regular service restart.
So now I knew how to trigger a reload and load the DLL instantly, but what were the required permissions?
Going back to the documentation, the initial check of any DNS operation would be to test the client credentials for Read privilege against MicrosoftDNS container. Write privilege against the same object is also required for our specific operations (restart and ServerLevelPluginDll).
By now I had triggered a reload, making the service load the DLL sent earlier (the command shown in the scenario section) and leaving fewer traces behind as no logs on service restart or process creation were being generated.
The logs that were generated, both in the DNS Server log, were Event ID 770 (a server-level plugin DLL has been loaded) and Event ID 140:
Figure 4: DNS Server log entries
In the next section, I’ll cover why this error is being generated. If you’d like to skip the details, you can jump to the “potential impact” section.
Something unexpected happened when executing the restart command: The DLL got loaded (as expected), and the service continued to run, but the following log was generated in the DNS Server log:
Checking the error code showed that there was a problem with the RPC Endpoint (0x6cc – The endpoint is a duplicate.).
When I tried to send any other command to the same DNS Server, using dnscmd, suddenly nothing worked:
Even the DNS management console did not work now:
To understand what was failing, I used ApiMonitor and saw that there was a problem with registering the endpoint after the restart:
When restarting the DNS server service in the usual way (e.g., services.msc), the service will stop and start with no error generated. Therefore, the next step I took was to see why an error was not thrown here as opposed to when a reload using /restart command is being transmitted to the server.
However, an interesting thing happened. Both service restart and service reload failed on the same API call.
That message rings a bell, doesn’t it? The service failed at unregistering the RPC endpoint and then generated a duplicate error when trying to use the endpoint.
The explanation behind the fact that there were no subsequent errors after a service restart is when service restart is occurring, the process exits, which causes the endpoint to unregister automatically. This does not occur when you trigger a service reload (the process is not actually being restarted, hence no process exit).
Using these methods, an attacker can run arbitrary DLLs within the security context of the DNS service.
Because the DNS service is often running on domain controllers, this vulnerability provides opportunities for attackers to escalate privileges for any user in the domain that is under their control. As described in the next section, in addition to advancing their current attack, the specifics of Active Director container ACLs give attackers an opportunity to create “sleeper admins”—and non-privileged users that can escalate privileges at will.
For these attacks to work, an attacker must have the following:
- Dnscmd (not necessary but saves time and effort)
- RPC open to DC
- Read, write permissions on MicrosoftDNS container (in system container)
Attackers who manage to compromise a user in the DnsAdmin group can expand their foothold in several ways. In addition to immediate privilege escalation to SYSTEM user on a DC (letting them add Domain Admin users or even gain a Skeleton Key), they can also give a low privileged user the ability to elevate privileges by assigning them Read/Write access to the MicrosoftDNS container as part of their initial attack.
This user could be created by the attacker, or by an existing user with a compromised password or known SPN for later kerberoasting. While privileged AD groups such as Domain Admins are often closely monitored, exact object ACLs are notoriously difficult to monitor effectively. This creates a situation where users seem to be unprivileged but, in reality, can restart the DNS service and inject DLLs again to regain privileged access, providing a persistent back door into the environment.
Figure 10: Domain users
In the screenshot above, the user “haxer” looks like a non-privileged user because he is only a member of domain users. But all he needs to become a Domain Admin is to have RPC to the DC and write access to a share accessible by the DC.
In this example, the DLL will start a shell on load that will receive commands from a file in a share (command.txt) and write the output to another file in the same share (out.txt).
The attacker logs in as “haxer” user, currently not a member of any privileged group, and creates a share with read/write access to everyone with his malicious DLL in it:
Figure 11: Attacker logs in as “haxer”
Now all the attacker needs to do is to register the DLL as a ServerLevelPluginDll on the DC, trigger a service restart, and start sending commands using the file he will create:
Figure 12: Registering the DLL
Two aspects of this scenario are important to remember:
- If the DC cannot access the path sent as ServerLevelPluginDLL, the restart trigger will make the DNS service terminate and the service won’t be able to start again until the DC is able to access the path or the path is removed from the DC’s registry. (And as a DnsAdmin, we probably won’t be able to start the service when it’s down.)
- Because of the bug described above, triggering a reload will cause issues with the duplication of the RPC Endpoint, and we will not be able to send any more commands or trigger another restart:
Figure 13: Restart command fails
Logging and detection
Here are some steps companies can take to log and detect potential intrusion. First, it is important to note that if the DNS Server process was restarted using the restart pszOperation, it will NOT generate a log with Event ID 7036 because this is not a service restart.
1. Event ID 770 will be created in the log DNS Server on the DC when it will load the DLL after a restart, with the DLL path specified.
Figure 14: Event 770 logged
2. Event ID 140 will be created when a restart will be triggered using R_DnssrvOperation (just because of the bug; ideally this will not happen in the future).
Figure 15: Event 140 logged
3. Monitor for ntSecurityDescriptor changes on MicrosoftDNS and DnsAdmins group (event ID 5136).
4. Monitor for adding members to DnsAdmins group.
5 Treat users and groups that already have those permissions as Domain Admins and monitor them accordingly.
6. Users and groups with those permissions should be added to the Protected Users group for enhanced protection.
7. Using IPS\IDS, you can monitor for R_DnssrvOperation and R_DnssrvOperation2 requests from non-admin computers to DNS servers.
8. Monitor for changes to registry path “HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters\ServerLevelPluginDLL” on DNS servers.
Guarding your Microsoft DNS service
By implementing these logging and detection best practices, your organization can guard against a scenario where users that already have permissions to manage the DNS can leverage it for a Domain Controller compromise—or leave a stealthy backdoor in the domain for unwanted intruders.