rapid7 / metasploit-framework

Metasploit Framework
https://www.metasploit.com/
Other
32.92k stars 13.71k forks source link

Ldap signing #19127

Closed smashery closed 1 week ago

smashery commented 3 weeks ago

This implements LDAP signing (technically encryption and signing) for both NTLM and Kerberos. LDAP modules should set LDAP::Signing to either auto or required to use it; I've set auto as the default, since most legitimate clients would do the same. When set to auto, LDAP::Signing has no effect when using auth other than NTLM or Kerberos. In this case, signing is used opportunistically based on the authentication mechanism. Setting it to disabled should have the same behaviour as before, which will be needed if we implement relaying. Finally, when set to required, the library will sign traffic, as well as raise an exception if an authentication mechanism is used which is incompatible with signing, e.g. plaintext.

This required some monkey patching of the ruby-net-ldap library, to intercept calls to read (or technically read_ber) and write - we transparently do the encryption or decryption and pass it back.

One weird constraint here is that the 3rd party library pulls BER objects off the network one at a time, thus if a packet contains multiple BER objects, a single call to read_ber would leave the packet half-unread. This doesn't jive super well with a packet-based decryption routine, so we emulate this by effectively batching up the reads of all BER objects in a decrypted packet, and just remembering them for the next time it's called (this is in reference to ber_cache).

The other thing worth pointing out is that RC4_HMAC behaves weirdly. As noted in one of the comments:

The RC4 spec (RFC4757) section 7.3 implies that RC4-HMAC only needs one byte of padding, although it doesn't come straight out and say it. Some protocols (LDAP, at least on a DC) complain if you give it more than a single byte of paddding. Other protocols (DRSR) complain if you don't align it perfectly with an 8-byte boundary. The MS-RPCE spec is a little vague on why exactly that might be, but we can at least show empirically that it is happy if you just give it an 8-byte aligned encrypted stub. Yet other protocols are happy whatever the padding (WinRM).

Even more perplexing is that when I decrypted the comms of the Microsoft's built-in cryptdll.dll for DRSR over RC4 (as called by Mimikatz), it seemed to use a completely different padding (and possibly checksum) mechanism. Rather than being PKCS#5-padded once decrypted, it just had a bunch of null bytes at the end (which would never be valid PKCS#5). I couldn't find what spec this was following. But regardless, it accepts the PKCS#5 behaviour, which matches both the RFCs as I could interpret them, and other implementations. In fact, when reading through other tools, the commented-out lines here seem to imply that others may have been similarly perplexed by the behaviour: https://github.com/skelsec/asyauth/blob/4c6fa5e51ac07237cc53f450df11d66803ea2b40/asyauth/protocols/kerberos/gssapi.py#L243-L251

So the approach I've taken is just to provide both behaviours: 1-byte by default (which is my best interpretation of the spec) and PKCS#5 (which fits the behaviour of DCERPC), and the RC4_HMAC encryption routine can be parameterised.

If using LDAPS, the LDAP Server Signing Requirements are satisfied, even without our new signing/encryption. In fact, the DC will straight up fail if you try to do both, as seen in this error message we encountered:

00002029: LdapErr: DSID-0C090648, comment: Cannot bind using sign/seal on a connection on which TLS or SSL is in effect, data 0, v4f7c

So we prevent this invalid config with an error message before sending any packets to the server.

Verification

Run the following tests, ensuring that there is coverage of old systems (e.g. 2008/2012) and newer systems (2019/2022). Verify that the encryption is doing what it should be doing using Wireshark.

Note that when coercing specific kerberos algorithms, you might need to clear the kerberos cache in between test cases. This is because of a separate bug, #19126

Demo

First a failing case (LDAP::Signing = disabled):

msf6 auxiliary(gather/ldap_query) > run domain=msf.local password=AzureTesting12345 rhosts=20.28.136.116 username=azureadmin ldap::auth=kerberos ldap::rhostname=ldapdc domaincontrollerrhost=20.28.136.116 target_user=azureadmin ldap::krbofferedencryptiontypes=RC4-HMAC action=add LDAP::Signing=disabled                                                                             [*] Running module against 20.28.136.116

[*] Using cached credential for ldap/ldapdc@MSF.LOCAL azureadmin@MSF.LOCAL
[-] Auxiliary aborted due to failure: no-target: Server requires a stronger form of authentication! May require LDAP signing to be enabled (`set LDAP::Signing auto`). The error was: 00002028: LdapErr: DSID-0C09032E, comment: The server requires binds to turn on integrity checking if SSL\TLS are not already active on the connection, data 0, v4f7c                             [*] Auxiliary module execution completed

Then the happy path:

msf6 auxiliary(gather/ldap_query) > run domain=msf.local password=AzureTesting12345 rhosts=20.28.136.116 username=azureadmin ldap::auth=kerberos ldap::rhostname=ldapdc domaincontrollerrhost=20.28.136.116 target_user=azureadmin ldap::krbofferedencryptiontypes=RC4-HMAC action=add LDAP::Signing=auto                                                                              [*] Running module against 20.28.136.116

[*] Using cached credential for ldap/ldapdc@MSF.LOCAL azureadmin@MSF.LOCAL
[+] 20.28.136.116:88 - Received AP-REQ. Extracting session key...
[*] Discovering base DN automatically
[+] 20.28.136.116:389 Discovered base DN: DC=msf,DC=local
[+] 20.28.136.116:389 Discovered schema DN: DC=msf,DC=local
CN=AzureAdmin,CN=Users,DC=msf,DC=local
======================================

 Name                Attributes
 ----                ----------
 badpwdcount         0
 description         Built-in account for administering the computer/domain
 lastlogoff          1601-01-01 00:00:00 UTC
 lastlogon           2024-04-24 03:54:45 UTC
 logoncount          91
 memberof            CN=Group Policy Creator Owners,CN=Users,DC=msf,DC=local
   \_                CN=Domain Admins,CN=Users,DC=msf,DC=local
   \_                CN=Enterprise Admins,CN=Users,DC=msf,DC=local
   \_                CN=Schema Admins,CN=Users,DC=msf,DC=local
   \_                CN=Administrators,CN=Builtin,DC=msf,DC=local
 name                AzureAdmin
 objectsid           S-1-5-21-2233714266-2773167633-2728317905-500
 pwdlastset
 samaccountname      AzureAdmin
 useraccountcontrol  512
...

I've tested most of the test cases against Server 2008, Server 2012, Server 2022.

smcintyre-r7 commented 2 weeks ago

I pushed up some changes to adjust the REQUIRE_SIGNING option to an enum. The new option defaults to signing and sealing opportunistically, but also has explicit settings for required and disabled.

smcintyre-r7 commented 1 week ago

So Server 2000 doesn't work now, and it doesn't look like it worked when 6.3 was originally released. After talking to Alan, I think the oldest Domain Controllers we tested were 2008. I'm going to open a separate ticket to track the problems I'm seeing, but there's no need to block this work because it's not a regression. :+1:

Neustradamus commented 1 week ago

@smashery: Good!

smcintyre-r7 commented 1 week ago

Thanks @smashery and @smcintyre-r7. I still need to test more but I wanted to add my findings so far.

It might be interesting to warn the user who wants to use a specific encryption type (with the option LDAP::KrbOfferedEncryptionTypes) that if cached credentials are used, the encryption type will be the one that has been negociated along with the cached tickets. I was think about adding this along with the status message that says, but only if LDAP::KrbOfferedEncryptionTypes is not the default:

[*] Using cached credential for ...

I think this related to ticket #19126.

cdelafuente-r7 commented 1 week ago

Thanks @smashery and @smcintyre-r7 ! Everything looks good to me now. I tested against Windows 2019, 2016, 2012 and 2008R2 and make sure the following modules work as expected. I'll go ahead and land it.

[+] 192.168.101.134:88 - Received a valid TGT-Response [] 192.168.101.134:389 - TGT MIT Credential Cache ticket saved to /Users/cdelafuente/.msf4/loot/20240507172319_default_192.168.101.134_mit.kerberos.cca_162925.bin [+] 192.168.101.134:88 - Received a valid TGS-Response [] 192.168.101.134:389 - TGS MIT Credential Cache ticket saved to /Users/cdelafuente/.msf4/loot/20240507172319_default_192.168.101.134_mit.kerberos.cca_993010.bin [+] 192.168.101.134:88 - Received a valid delegation TGS-Response [+] 192.168.101.134:88 - Received AP-REQ. Extracting session key... [+] Successfully bound to the LDAP server! [] Discovering base DN automatically [] 192.168.101.134:389 Getting root DSE [+] 192.168.101.134:389 Discovered base DN: DC=oldlab2,DC=local [+] 192.168.101.134:389 Discovered schema DN: DC=oldlab2,DC=local DC=oldlab2,DC=local

Name Attributes


lockoutduration 0:00:30:00 lockoutthreshold 0 maxpwdage 42:00:00:00 minpwdage 1:00:00:00 minpwdlength 7 ms-ds-machineaccountquota 10 name oldlab2 objectsid S-1-5-21-3696071272-508768914-65361377

[] Query returned 1 result. [] Auxiliary module execution completed


- `gather/asrep`

msf6 auxiliary(gather/asrep) > run verbose=true rhosts=192.168.101.224 username=Administrator password=123456 ldap::auth=kerberos ldap::rhostname=dc02 domain=mylab.local domaincontrollerrhost=192.168.101.224 ldap::signing=required KrbCacheMode=none LDAP::KrbOfferedEncryptionTypes=AES128 SSL=false user_file=/Users/cdelafuente/doc/wordlists/usernames.txt action=LDAP [*] Running module against 192.168.101.224

[+] 192.168.101.224:88 - Received a valid TGT-Response [+] 192.168.101.224:88 - Received a valid TGS-Response [+] 192.168.101.224:88 - Received a valid delegation TGS-Response [+] 192.168.101.224:88 - Received AP-REQ. Extracting session key... [+] Successfully bound to the LDAP server! [*] 192.168.101.224:389 Getting root DSE [+] 192.168.101.224:389 Discovered base DN: DC=mylab,DC=local [+] 192.168.101.224:389 Discovered schema DN: DC=mylab,DC=local

[-] No entries could be found for (&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=4194304))! [*] Auxiliary module execution completed


- `gather/ldap_esc_vulnerable_cert_finder`

msf6 auxiliary(gather/ldap_esc_vulnerable_cert_finder) > run verbose=true rhosts=192.168.101.224 username=Administrator password=123456 ldap::auth=kerberos ldap::rhostname=dc02 domain=mylab.local domaincontrollerrhost=192.168.101.224 ldap::signing=required KrbCacheMode=none LDAP::KrbOfferedEncryptionTypes=AES128 SSL=false [*] Running module against 192.168.101.224

[+] 192.168.101.224:88 - Received a valid TGT-Response [+] 192.168.101.224:88 - Received a valid TGS-Response [+] 192.168.101.224:88 - Received a valid delegation TGS-Response [+] 192.168.101.224:88 - Received AP-REQ. Extracting session key... [+] Successfully bound to the LDAP server! [] Discovering base DN automatically [] 192.168.101.224:389 Getting root DSE [+] 192.168.101.224:389 Discovered base DN: DC=mylab,DC=local [] Successfully queried (&(&(&(&(objectclass=pkicertificatetemplate)(!(mspki-enrollment-flag:1.2.840.113556.1.4.804:=2)))(|(mspki-ra-signature=0)(!(mspki-ra-signature=))))(|(|(|(|(pkiextendedkeyusage=1.3.6.1.4.1.311.20.2.2)(pkiextendedkeyusage=1.3.6.1.5.5.7.3.2))(pkiextendedkeyusage=1.3.6.1.5.2.3.4))(pkiextendedkeyusage=2.5.29.37.0))(!(pkiextendedkeyusage=))))(mspki-certificate-name-flag:1.2.840.113556.1.4.804:=1)). [] Successfully queried (objectSID=S-1-5-21-419547006-9459028-4093171872-512). [] Successfully queried (objectSID=S-1-5-21-419547006-9459028-4093171872-512). [] Successfully queried (objectSID=S-1-5-21-419547006-9459028-4093171872-519). [] Successfully queried (objectSID=S-1-5-21-419547006-9459028-4093171872-519). [] Successfully queried (objectSID=S-1-5-21-419547006-9459028-4093171872-512). [] Successfully queried (objectSID=S-1-5-21-419547006-9459028-4093171872-512). [] Successfully queried (objectSID=S-1-5-21-419547006-9459028-4093171872-519). [] Successfully queried (objectSID=S-1-5-21-419547006-9459028-4093171872-519). [] Successfully queried (objectSID=S-1-5-21-419547006-9459028-4093171872-512). [] Successfully queried (objectSID=S-1-5-21-419547006-9459028-4093171872-512). [] Successfully queried (objectSID=S-1-5-21-419547006-9459028-4093171872-519). [] Successfully queried (objectSID=S-1-5-21-419547006-9459028-4093171872-519). [] Successfully queried (objectSID=S-1-5-21-419547006-9459028-4093171872-512). [] Successfully queried (objectSID=S-1-5-21-419547006-9459028-4093171872-512). [] Successfully queried (objectSID=S-1-5-21-419547006-9459028-4093171872-513). ...


- `admin/ldap/shadow_credentials`

msf6 auxiliary(admin/ldap/shadow_credentials) > run verbose=true rhosts=192.168.101.224 username=Administrator password=123456 ldap::auth=kerberos ldap::rhostname=dc02 domain=mylab.local domaincontrollerrhost=192.168.101.224 ldap::signing=required KrbCacheMode=none LDAP::KrbOfferedEncryptionTypes=RC4-HMAC SSL=false target_user=administrator [*] Running module against 192.168.101.224

[+] 192.168.101.224:88 - Received a valid TGT-Response [+] 192.168.101.224:88 - Received a valid TGS-Response [+] 192.168.101.224:88 - Received a valid delegation TGS-Response [+] 192.168.101.224:88 - Received AP-REQ. Extracting session key... [+] Successfully bound to the LDAP server! [] Discovering base DN automatically [] 192.168.101.224:389 Getting root DSE [+] 192.168.101.224:389 Discovered base DN: DC=mylab,DC=local [] The msDS-KeyCredentialLink field is empty. [] Auxiliary module execution completed


- `gather/windows_secrets_dump`

msf6 auxiliary(gather/windows_secrets_dump) > run verbose=true rhosts=192.168.101.224 SMBUser=Administrator SMBPass=123456 SMB::Auth=kerberos Smb::Rhostname=dc02 DomainControllerRhost=192.168.101.224 SMBDomain=mylab.local SMB::KrbOfferedEncryptionTypes=RC4-HMAC KrbCacheMode=none SMB::ProtocolVersion=2 [*] Running module against 192.168.101.224

[+] 192.168.101.224:445 - 192.168.101.224:88 - Received a valid TGT-Response [+] 192.168.101.224:445 - 192.168.101.224:88 - Received a valid TGS-Response [+] 192.168.101.224:445 - 192.168.101.224:88 - Received a valid delegation TGS-Response [] 192.168.101.224:445 - Opening Service Control Manager [] 192.168.101.224:445 - Binding to \svcctl... [+] 192.168.101.224:445 - Bound to \svcctl [] 192.168.101.224:445 - Service RemoteRegistry is already running [] 192.168.101.224:445 - Retrieving target system bootKey [] 192.168.101.224:445 - Retrieving class info for SYSTEM\CurrentControlSet\Control\Lsa\JD [] 192.168.101.224:445 - Retrieving class info for SYSTEM\CurrentControlSet\Control\Lsa\Skew1 [] 192.168.101.224:445 - Retrieving class info for SYSTEM\CurrentControlSet\Control\Lsa\GBG [] 192.168.101.224:445 - Retrieving class info for SYSTEM\CurrentControlSet\Control\Lsa\Data [+] 192.168.101.224:445 - bootKey: 0x620da318ca0997f1d9b6bdc542d5454e [] 192.168.101.224:445 - Checking NoLMHash policy [] 192.168.101.224:445 - LMHashes are not being stored [] 192.168.101.224:445 - Saving remote SAM database [] 192.168.101.224:445 - Create SAM key [] 192.168.101.224:445 - Save key to Y0TcSkyK.tmp [] 192.168.101.224:445 - Dumping SAM hashes [*] 192.168.101.224:445 - Calculating HashedBootKey from SAM ...


- `scanner/winrm/winrm_cmd`

msf6 auxiliary(scanner/winrm/winrm_cmd) > run verbose=true rhosts=192.168.101.224 username=Administrator password=123456 Winrm::Auth=kerberos Winrm::Rhostname=dc02 DomainControllerRhost=192.168.101.224 Domain=mylab.local Winrm::KrbOfferedEncryptionTypes=RC4-HMAC KrbCacheMode=none

[+] 192.168.101.224:88 - Received a valid TGT-Response [+] 192.168.101.224:88 - Received a valid TGS-Response [+] 192.168.101.224:88 - Received a valid delegation TGS-Response [+] 192.168.101.224:88 - Received AP-REQ. Extracting session key...

Windows IP Configuration

Host Name . . . . . . . . . . . . : dc02 Primary Dns Suffix . . . . . . . : mylab.local Node Type . . . . . . . . . . . . : Hybrid IP Routing Enabled. . . . . . . . : No WINS Proxy Enabled. . . . . . . . : No DNS Suffix Search List. . . . . . : mylab.local

Ethernet adapter Ethernet0:

Connection-specific DNS Suffix . : Description . . . . . . . . . . . : Intel(R) 82574L Gigabit Network Connection Physical Address. . . . . . . . . : 00-0C-29-8E-8C-CC DHCP Enabled. . . . . . . . . . . : No Autoconfiguration Enabled . . . . : Yes Link-local IPv6 Address . . . . . : fe80::d61f:95c7:fc98:e008%15(Preferred) IPv4 Address. . . . . . . . . . . : 192.168.101.224(Preferred) Subnet Mask . . . . . . . . . . . : 255.255.255.0 Default Gateway . . . . . . . . . : 192.168.101.2 DHCPv6 IAID . . . . . . . . . . . : 100666409 DHCPv6 Client DUID. . . . . . . . : 00-01-00-01-28-EF-79-40-00-0C-29-8E-8C-CC DNS Servers . . . . . . . . . . . : ::1 192.168.101.223 127.0.0.1 NetBIOS over Tcpip. . . . . . . . : Enabled [+] Results saved to /Users/cdelafuente/.msf4/loot/20240507112845_default_192.168.101.224_winrm.cmd_result_865906.txt [] Scanned 1 of 1 hosts (100% complete) [] Auxiliary module execution completed

cdelafuente-r7 commented 1 week ago

Release Notes

This implements LDAP signing and encryption for both NTLM and Kerberos.