0x01 關於SPN
服務主體名稱(SPN)是Kerberos客戶端用於唯一標識給特定Kerberos目標計算機的服務實例名稱。
服務主體名稱是服務實例(可以理解為一個服務,比如 HTTP、MSSQL)的唯一標識符。Kerberos 身份驗證使用 SPN 將服務實例與服務登錄帳戶相關聯。
在內部網絡中,SPN掃描通過 查詢向域控制器執行服務發現。這對於紅隊而言,可以幫助他們識別正在運行重要服務的主機,如終端、交換機、微軟SQL等,並隱藏他們。此外,SPN的識別也是kerberoasting攻擊的第一步。
0x02 SPN基礎配置
詳細可以查看微軟官方手冊
https://docs.microsoft.com/zh-cn/windows-server/networking/sdn/security/kerberos-with-spn
在 SPN 的語法中存在四種元素,兩個必須元素和兩個額外元素,其中<service class>和<host>為必須元素:
<serviceclass>/<host>:<port>/<service name> <service class>:標識服務類的字符串 <host>:服務所在主機名稱 <port>:服務端口 <service name>:服務名稱
常見服務和spn服務實例名稱
MSSQL
MSSQLSvc/adsmsSQLAP01.adsecurity.org:1433 Exchange exchangeMDB/adsmsEXCAS01.adsecurity.org RDP TERMSERV/adsmsEXCAS01.adsecurity.org WSMan / WinRM / PS Remoting WSMAN/adsmsEXCAS01.adsecurity.org Hyper-V Host Microsoft Virtual Console Service/adsmsHV01.adsecurity.org VMWare VCenter STS/adsmsVC01.adsecurity.org
提一下SPN的注冊
這里以SQL Server服務為例子。
SQL Server在每次啟動的時候,都會去嘗試用自己的啟動賬號注冊SPN。但是在Windows域里,默認普通機器賬號有權注冊SPN,但是普通域用戶賬號是沒有權利注冊SPN的。這就會導致這樣一個現象,SQL Server如果使用“Local System account”來啟動,Kerberos就能夠成功,因為SQL Server這時可以在DC上注冊SPN。如果用一個域用戶來啟動,Kerberos就不能成功,因為這時SPN注冊不上去。
解決的方法之一,當然可以使用工具SetSPN -S來手動注冊SPN。但是這不是一個最好的方法,畢竟手工注冊不是長久之計。如果SPN下次丟了,又要再次手動注冊。所以比較好的方法,是讓SQL Server當前的啟動賬號有注冊SPN的權力。要在DC上為域賬號賦予“Read servicePrincipalName”和“Write serverPrincipalName”的權限即可。
SetSPN
SetSPN是一個本地windows二進制文件,可用於檢索用戶帳戶和服務之間的映射。該實用程序可以添加,刪除或查看SPN注冊。
這里在我dc上進行SPN服務(MSSQL)的注冊。
Setspn -A MSSQLSvc/DC-1.qing.com:1433 tsvc
注冊成功之后可以通過下面兩個命令來查看已經注冊的 SPN。
setspn -Q */*
setspn -T DC-1.qing.com -Q */*
注意這里是寫機器的FQDN
0x03 SPN掃描
附上MSSQL的spn掃描腳本
function Discover-PSMSSQLServers { <# .SYNOPSIS This script is used to discover Microsoft SQL servers without port scanning. SQL discovery in the Active Directory Forest is performed by querying an Active Directory Gloabl Catalog via ADSI. Discover-PSMSSQLServers Author: Sean Metcalf, Twitter: @PyroTek3 License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None Last Updated: 2/04/2015 Version: 2.3 .DESCRIPTION This script is used to discover Microsoft SQL servers in the Active Directory Forest. Currently, the script performs the following actions: * Queries a Global Catalog in the Active Directory root domain for all Microsoft SQL SPNs in the forest * Displays the Microsoft SQL server FQDNs ports and instances * Identifies any service accounts associated with the SQL instance and includes the account info REQUIRES: Active Directory user authentication. Standard user access is fine - admin access is not necessary. .EXAMPLE Discover-PSMSSQLServers Perform Microsoft SQL Server discovery via AD and returns the results in a custom PowerShell object. .NOTES This script is used to discover Microsoft SQL servers in the Active Directory Forest and can also provide additional computer information such as OS and last bootup time. .LINK Blog: http://www.ADSecurity.org Github repo: https://github.com/PyroTek3/PowerShell-AD-Recon #> Param ( ) Write-Verbose "Get current Active Directory domain... " $ADForestInfo = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() $ADForestInfoRootDomain = $ADForestInfo.RootDomain $ADForestInfoRootDomainDN = "DC=" + $ADForestInfoRootDomain -Replace("\.",',DC=') $ADDomainInfoLGCDN = 'GC://' + $ADForestInfoRootDomainDN Write-Verbose "Discovering Microsoft SQL Servers in the AD Forest $ADForestInfoRootDomainDN " $root = [ADSI]$ADDomainInfoLGCDN $ADSearcher = new-Object System.DirectoryServices.DirectorySearcher($root,"(serviceprincipalname=mssql*)") $ADSearcher.PageSize = 50000 $AllADSQLServerSPNs = $ADSearcher.FindAll() $AllADSQLServerSPNsCount = $AllADSQLServerSPNs.Count Write-Output "Processing $AllADSQLServerSPNsCount (user and computer) accounts with MS SQL SPNs discovered in AD Forest $ADForestInfoRootDomainDN `r " $AllMSSQLSPNs = $NULL $AllMSSQLSPNHashTable =@{} $AllMSSQLServiceAccountHashTable =@{} ForEach ($AllADSQLServerSPNsItem in $AllADSQLServerSPNs) { $AllADSQLServerSPNsItemDomainName = $NULL [array]$AllADSQLServerSPNsItemArray = $AllADSQLServerSPNsItem.Path -Split(",DC=") [int]$DomainNameFECount = 0 ForEach ($AllADSQLServerSPNsItemArrayItem in $AllADSQLServerSPNsItemArray) { IF ($DomainNameFECount -gt 0) { [string]$AllADSQLServerSPNsItemDomainName += $AllADSQLServerSPNsItemArrayItem + "." } $DomainNameFECount++ } $AllADSQLServerSPNsItemDomainName = $AllADSQLServerSPNsItemDomainName.Substring(0,$AllADSQLServerSPNsItemDomainName.Length-1) ForEach ($ADSISQLServersItemSPN in $AllADSQLServerSPNsItem.properties.serviceprincipalname) { IF ( ($ADSISQLServersItemSPN -like "MSSQL*") -AND ($ADSISQLServersItemSPN -like "*:*") ) { IF (($AllADSQLServerSPNsItem.properties.objectcategory -like "CN=Person*") -AND ($ADSISQLServersItemSPNServerFQDN) ) { $AllMSSQLServiceAccountHashTable.Set_Item($ADSISQLServersItemSPNServerFQDN,$AllADSQLServerSPNsItem.properties.distinguishedname) } $ADSISQLServersItemSPNArray1 = $ADSISQLServersItemSPN -Split("/") $ADSISQLServersItemSPNArray2 = $ADSISQLServersItemSPNArray1 -Split(":") [string]$ADSISQLServersItemSPNServerFQDN = $ADSISQLServersItemSPNArray2[1] IF ($ADSISQLServersItemSPNServerFQDN -notlike "*$AllADSQLServerSPNsItemDomainName*" ) { $ADSISQLServersItemSPNServerFQDN = $ADSISQLServersItemSPNServerFQDN + "." + $AllADSQLServerSPNsItemDomainName } [string]$AllMSSQLSPNsItemServerInstancePort = $ADSISQLServersItemSPNArray2[2] $AllMSSQLSPNsItemServerName = $ADSISQLServersItemSPNServerFQDN -Replace(("."+ $AllADSQLServerSPNsItemDomainName),"") $AllMSSQLSPNHashTableData = $AllMSSQLSPNHashTable.Get_Item($ADSISQLServersItemSPNServerFQDN) IF ( ($AllMSSQLSPNHashTableData) -AND ($AllMSSQLSPNHashTableData -notlike "*$AllMSSQLSPNsItemServerInstancePort*") ) { $AllMSSQLSPNHashTableDataUpdate = $AllMSSQLSPNHashTableData + ";" + $AllMSSQLSPNsItemServerInstancePort $AllMSSQLSPNHashTable.Set_Item($ADSISQLServersItemSPNServerFQDN,$AllMSSQLSPNHashTableDataUpdate) } ELSE { $AllMSSQLSPNHashTable.Set_Item($ADSISQLServersItemSPNServerFQDN,$AllMSSQLSPNsItemServerInstancePort) } } } } ### Write-Verbose "Loop through the discovered MS SQL SPNs and build the report " ### $ALLSQLServerReport = @() #$AllMSSQLServerFQDNs = $NULL ForEach ($AllMSSQLSPNsItem in $AllMSSQLSPNHashTable.GetEnumerator()) { $AllMSSQLSPNsItemServerDomainName = $NULL $AllMSSQLSPNsItemServerDomainDN = $NULL $AllMSSQLSPNsItemServiceAccountDN = $NULL $AllMSSQLSPNsItemServiceAccountDomainDN = $NULL $AllMSSQLSPNsItemServerFQDN = $AllMSSQLSPNsItem.Name #[array]$AllMSSQLServerFQDNs += $AllMSSQLSPNsItemServerFQDN $AllMSSQLSPNsItemInstancePortArray = ($AllMSSQLSPNsItem.Value) -Split(';') $AllMSSQLSPNsItemServerFQDNArray = $AllMSSQLSPNsItemServerFQDN -Split('\.') [int]$FQDNArrayFECount = 0 ForEach ($AllMSSQLSPNsItemServerFQDNArrayItem in $AllMSSQLSPNsItemServerFQDNArray) { IF ($FQDNArrayFECount -ge 1) { [string]$AllMSSQLSPNsItemServerDomainName += $AllMSSQLSPNsItemServerFQDNArrayItem + "." [string]$AllMSSQLSPNsItemServerDomainDN += "DC=" + $AllMSSQLSPNsItemServerFQDNArrayItem + "," } $FQDNArrayFECount++ } $AllMSSQLSPNsItemServerDomainName = $AllMSSQLSPNsItemServerDomainName.Substring(0,$AllMSSQLSPNsItemServerDomainName.Length-1) $AllMSSQLSPNsItemServerDomainDN = $AllMSSQLSPNsItemServerDomainDN.Substring(0,$AllMSSQLSPNsItemServerDomainDN.Length-1) $AllMSSQLSPNsItemServerDomainLDAPDN = "LDAP://$AllMSSQLSPNsItemServerDomainDN" $AllMSSQLSPNsItemServerName = $AllMSSQLSPNsItemServerFQDN -Replace(("."+$AllMSSQLSPNsItemServerDomainName),"") $AllMSSQLSPNsItemServiceAccountDN = $AllMSSQLServiceAccountHashTable.Get_Item($AllMSSQLSPNsItemServerFQDN) IF ($AllMSSQLSPNsItemServiceAccountDN) { $ADServiceAccountSearchInfo = @() $AllMSSQLSPNsItemServiceAccountDNArray = $AllMSSQLSPNsItemServiceAccountDN -Split(",") ForEach ($AllMSSQLSPNsItemServiceAccountDNArrayItem in $AllMSSQLSPNsItemServiceAccountDNArray) { IF ($AllMSSQLSPNsItemServiceAccountDNArrayItem -like 'DC=*') { [string]$AllMSSQLSPNsItemServiceAccountDomainDN += "$AllMSSQLSPNsItemServiceAccountDNArrayItem," } } $AllMSSQLSPNsItemServiceAccountDomainDN = $AllMSSQLSPNsItemServiceAccountDomainDN.Substring(0,$AllMSSQLSPNsItemServiceAccountDomainDN.Length-1) $AllMSSQLSPNsItemServiceAccountDomainLDAPDN = "LDAP://$AllMSSQLSPNsItemServiceAccountDomainDN" $ADServiceAccountSearch = New-Object DirectoryServices.DirectorySearcher([ADSI]"") $ADServiceAccountSearch.SearchRoot = $AllMSSQLSPNsItemServiceAccountDomainLDAPDN $ADServiceAccountSearch.PageSize = 50000 $ADServiceAccountSearch.Filter = "distinguishedname=$AllMSSQLSPNsItemServiceAccountDN" $ADServiceAccountSearchInfo = $ADServiceAccountSearch.FindAll() IF ($ADServiceAccountSearchInfo) { [string]$ADServiceAccountSAMAccountName = $ADServiceAccountInfo[0].Properties.samaccountname [string]$ADServiceAccountdescription = $ADServiceAccountSearchInfo[0].Properties.description [string]$ADServiceAccountpwdlastset = $ADServiceAccountSearchInfo[0].Properties.pwdlastset [string]$ADServiceAccountPasswordLastSetDate = [datetime]::FromFileTimeUTC($ADServiceAccountpwdlastset) [string]$ADServiceAccountlastlogon = $ADServiceAccountSearchInfo[0].Properties.lastlogon [string]$ADServiceAccountLastLogonDate = [datetime]::FromFileTimeUTC($ADServiceAccountlastlogon) $ADServiceAccountadmincount = $ADServiceAccountSearchInfo[0].Properties.admincount [string]$ADServiceAccountDistinguishedName = $ADServiceAccountSearchInfo[0].Properties.distinguishedname } $ADServiceAccountLDAPDN = "LDAP://"+$ADServiceAccountDistinguishedName $ADServiceAccountInfo = ([adsi] $ADServiceAccountLDAPDN) } ForEach ($AllMSSQLSPNsItemInstancePortArrayItem in $AllMSSQLSPNsItemInstancePortArray) { $AllMSSQLSPNsItemServerPort = $NULL $AllMSSQLSPNsItemServerInstance = $NULL $SQLServerReport = New-Object -TypeName System.Object $SQLServerReport | Add-Member -MemberType NoteProperty -Name Domain -Value $AllMSSQLSPNsItemServerDomainName $SQLServerReport | Add-Member -MemberType NoteProperty -Name ServerName -Value $AllMSSQLSPNsItemServerFQDN IF ($AllMSSQLSPNsItemInstancePortArrayItem -match "^[\d\.]+$") { [int]$AllMSSQLSPNsItemServerPort = $AllMSSQLSPNsItemInstancePortArrayItem } IF ($AllMSSQLSPNsItemInstancePortArrayItem -NOTmatch "^[\d\.]+$") { [string]$AllMSSQLSPNsItemServerInstance = $AllMSSQLSPNsItemInstancePortArrayItem } $SQLServerReport | Add-Member -MemberType NoteProperty -Name Port -Value $AllMSSQLSPNsItemServerPort $SQLServerReport | Add-Member -MemberType NoteProperty -Name Instance -Value $AllMSSQLSPNsItemServerInstance $SQLServerReport | Add-Member -MemberType NoteProperty -Name ServiceAccountDN -Value $AllMSSQLSPNsItemServiceAccountDN TRY { $ADComputerSearch = New-Object DirectoryServices.DirectorySearcher([ADSI]"") $ADComputerSearch.SearchRoot = $AllMSSQLSPNsItemServerDomainLDAPDN $ADComputerSearch.PageSize = 50000 $ADComputerSearch.Filter = "(&(objectCategory=Computer)(name=$AllMSSQLSPNsItemServerName))" $ADComputerSearchInfo = $ADComputerSearch.FindAll() [string]$ComputerADInfoLastLogonTimestamp = ($ADComputerSearchInfo[0].properties.lastlogontimestamp) TRY { [datetime]$ComputerADInfoLLT = [datetime]::FromFileTime($ComputerADInfoLastLogonTimestamp) } CATCH { } #$ComputerADInfo.Values $SQLServerReport | Add-Member -MemberType NoteProperty -Name OperatingSystem -Value ($ADComputerSearchInfo[0].properties.operatingsystem) $SQLServerReport | Add-Member -MemberType NoteProperty -Name OSServicePack -Value ($ADComputerSearchInfo[0].properties.operatingsystemservicepack) $SQLServerReport | Add-Member -MemberType NoteProperty -Name LastBootup -Value $ComputerADInfoLLT $SQLServerReport | Add-Member -MemberType NoteProperty -Name OSVersion -Value ($ADComputerSearchInfo[0].properties.operatingsystemversion) $SQLServerReport | Add-Member -MemberType NoteProperty -Name Description -Value ($ADComputerSearchInfo[0].properties.description) } CATCH { } IF ($AllMSSQLSPNsItemServiceAccountDN) { $SQLServerReport | Add-Member -MemberType NoteProperty -Name SrvAcctUserID -Value $ADServiceAccountSAMAccountName $SQLServerReport | Add-Member -MemberType NoteProperty -Name SrvAcctDescription -Value $ADServiceAccountdescription #$SQLServerReport | Add-Member -MemberType NoteProperty -Name SrvAcctPasswordLastSet -Value $ADServiceAccountPasswordLastSetDate #$SQLServerReport | Add-Member -MemberType NoteProperty -Name SAadmincount -Value $ADServiceAccountadmincount } [array]$ALLSQLServerReport += $SQLServerReport } } # Find all SQL service account that may be a domain-level admin in the domain # $ALLSQLServerReport | Where {$_.SAadmincount -eq 1} | select ServerName,SrvAcctUserID,SrvAcctPasswordLastSet,SrvAcctDescription | sort SrvAcctUserID -unique | format-table -auto return $ALLSQLServerReport }
下面列出常見spn掃描工具:
由於每台服務器都需要注冊用於Kerberos身份驗證服務的SPN,因此這為在不進行端口掃描的情況下收集有關環境的信息提供了一個完美的方法。
PowerShell-AD-Recon
除了Tim Medin開發的工具外,Sean Metcalf也開發了各種PowerShell腳本來執行Kerberos偵察。這些腳本是PowerShell AD Recon存儲庫的一部分,可以在Active Directory中查詢服務,例如Exchange,Microsoft SQL,Terminal等。Sean將每個腳本綁定到一個特定的服務,具體取決於你想要發現的SPN。以下腳本將標識網絡上的所有Microsoft SQL實例。
參考
http://en.hackdig.com/?17699.htm
GetUserSPNs:
GetUserSPNs 是 Kerberoast 工具集中的一個 powershell 腳本,用來查詢域內注冊的 SPN。
查看當前 域 qing.com的spn
PowerView:
PowerView 是由 Will Schroeder(https://twitter.com/harmj0y)開發的 Powershell 腳本,在 Powersploit 和 Empire 工具里都有集成,PowerView 相對於上面幾種是根據不同用戶的 objectsid 來返回,返回的信息更加詳細。
查看當前 域 qing.com的spn
暫時寫到這里,有空后面補充