产品

攻击者如何滥用访问令牌操纵 (ATT&CK T1134)

在我们之前关于面向安全从业人员的 Windows 访问令牌的博客文章中,我们介绍了以下内容:

  • 登录会话与访问令牌之间的关系
  • Windows 环境中的网络身份验证的工作原理

在介绍了 Windows 安全的一些关键概念之后,我们将在此基础上进一步探讨攻击者如何滥用合法的 Windows 功能进行横向移动并入侵 Active Directory 域。

本博客尽可能地对特定 Windows 网络身份验证协议(例如 NTLM 和 Kerberos)的工作原理进行了抽象化处理。因此,某些仅属于这些协议的独特行为可能与下文描述的情况有所不同。此外,本博客还假设读者对 Kerberos 身份验证协议1有一定的了解。

此外,本博客系列文章中的内容还被用于 BlackHat 2020 大会的演讲《检测访问令牌篡改》。该演讲视频可在此处找到,幻灯片可在此处获取。

访问令牌操纵(ATT&CK 技术:T1134)

我们在之前的博客文章中介绍了本地及分布式应用程序中登录会话和访问令牌的基本工作原理。本节将阐述攻击者如何滥用访问令牌,并利用 Windows 域中的基础信任关系来入侵整个网络。本节旨在描述攻击者在模拟入侵场景下所使用的访问令牌操纵技术

需要指出的是,目前关于访问令牌操纵已有大量优秀的研究成果(本文将大量引用这些内容)。本博客尝试在此基础上,从不同的视角(即通过访问令牌、登录会话和缓存凭据之间的关系)来进一步拓展这一领域的知识。作者认为,任何忽略这些关系的令牌操纵描述都只是冰山一角。因此,本博客对访问令牌操纵的定义可能比通常理解的要广泛得多。

初步入侵

如果攻击者通过鱼叉式网络钓鱼成功入侵网络,通常会得到一个在被攻破用户的安全上下文中运行的 Shell。这可以通过创建新进程或直接注入内存(取决于有效载荷)来实现,但最终结果相同:攻击者的代码在一个拥有被攻破用户访问令牌的进程中运行。

这意味着任何本地访问检查都将使用被入侵用户的访问令牌,而任何远程身份验证尝试都将使用被入侵用户缓存的凭据²。因此,攻击者可以在本地和网络中执行被入侵用户所能进行的所有操作。例如,如果任何内部 Web 应用程序使用 Windows 单点登录身份验证 (SSO),攻击者将能够像该用户一样访问这些应用程序。

令牌操控:可能性的艺术

通常情况下,攻击者会希望尽快从被入侵的终端转移到另一台主机3。从令牌操纵的角度来看,当考虑横向移动时,攻击者实际上有三种选择4,每种选择都受到访问令牌、登录会话和缓存凭据之间基本关系的约束,如下图所示:

1-access-tokens-logon-sessions-blog-access-token-manipulation.png

图 1 - 访问令牌、登录会话和缓存凭据之间的关系


如果攻击者想要通过 Windows 单点登录身份验证进行横向移动,则上述三个环节必须全部具备(例如,他们必须拥有与目标凭据关联的登录会话所绑定的令牌句柄)。否则,攻击者的行动自由将依赖于创建新链接(例如,新的登录会话)或修改现有链接(例如,更改缓存的凭据或其访问令牌指向的登录会话)。以下三个选项将更详细地讨论这些限制:

1. 窃取已登录特权用户的令牌(非网络登录)

如果另一名特权用户已经登录到遭入侵的主机,攻击者便可提升权限,并获取代表该用户的访问令牌句柄。无论攻击者是冒充窃取的令牌,还是启动一个新进程,如果该令牌与非网络登录会话关联,它将包含缓存的凭据,因此攻击者可以跨主机进行身份验证,访问另一台主机5。因此,这种技术允许攻击者使用其他用户的凭据访问网络上的远程主机(通过 Windows 单点登录),从而在无需转储凭据的情况下进行横向移动6

需要注意的是,令牌操纵攻击通常涉及两个不同的目标:横向移动(本文关注的重点)和本地权限提升7。令牌窃取通常与后者相关(例如,窃取/冒充令牌以绕过本地访问检查,而不是使用缓存的凭据进行远程身份验证),因此本文不会对此进行更详细的讨论,但以下资源可供进一步阅读:

2. 使用窃取的凭据创建一个新的登录会话,并冒充返回的令牌,或使用该令牌启动一个新进程。

在这种情况下,还没有特权用户登录(因此也没有相应的有用访问令牌/登录会话),但攻击者仍然需要找到一种方法来更改其安全上下文

因此,攻击者必须在其他地方找到凭据,并使用这些窃取的凭据以被入侵用户的身份创建新的登录会话。由于 Windows 会自动缓存某些登录类型的凭据,攻击者现在可以获取一个由窃取的凭据备份的新生成的访问令牌。一旦攻击者获得了代表被入侵用户的令牌句柄,便可利用标准的 Windows 单点登录 (SSO) 流程在目标主机上进行身份验证。

通常,攻击者会通过 Kerberoasting 攻击或在所有可访问资源(例如网络共享、SharePoint、内部 wiki、企业 GitHub、Zendesk 等)中搜索未加密的明文凭据来获取明文凭据。8

3. 将与其当前访问令牌关联的缓存凭据更改为窃取的凭据(例如,通过 API 合法访问,或通过“非法地”直接修改 lsass 内存)

在这种情况下,攻击者不会创建新的登录会话,而是修改与其当前访问令牌(以及登录会话)关联的缓存凭据。正如我们将看到的,许多 Windows 安全支持提供程序 (SSP) 都提供了实现此目的的原生方法(并且无需需要提升权限)。

或者,攻击者可以采取“直接”的方式,手动修改存储在 lsass 中的缓存凭据。这需要提升权限才能通过 OpenProcess 获取 lsass 的写入句柄(例如 PROCESS_VM_WRITE)。这是典型的“哈希传递”攻击,我们稍后会详细介绍。

访问令牌操纵攻击

本博客文章将探讨攻击者常用的四种技术(所有这些技术都可以归类为上述选项 3 的变体):

  • NETONLY 标志
  • 票据传递
  • 哈希传递攻击
  • 超哈希传递攻击

1. NETONLY 标志

Windows API 提供了 LogonUser 函数,用于为指定用户(或主体)创建新的登录会话9

BOOL LogonUserW(
LPCWSTR lpszUsername、
LPCWSTR lpszDomain、
LPCWSTR lpszPassword、
DWORD dwLogonType、
DWORD dwLogonProvider、
PHANDLE phToken
);

这里需要注意的关键参数是 dwLogonType,它指定要执行的登录类型。例如,当用户实际登录其工作站时,该参数将被设置为 LOGON32_LOGON_INTERACTIVE。指定的登录类型将决定返回的令牌的类型权限

例如,在交互式登录的情况下,LogonUserW 将返回一个主访问令牌,如果启用了 UAC,则此令牌将是经过筛选的令牌(意味着它是具有中等完整性且未提升权限)。但有一种例外情况:如果用户是本地管理员帐户(例如,*-500 SID),Windows 将自动返回一个提升权限的令牌10

在进行网络登录(LOGON32_LOGON_NETWORK)时,会返回一个模拟令牌(通常服务器会使用此令牌代表远程客户端执行操作)。此外,如果用户属于本地管理员组,则该令牌的权限将被提升,并启用所有权限11

下表列出了 LogonUser 的所有可能排列组合:

dwLogonType

返回的令牌

缓存凭据?

返回的令牌是否被提升?(如果是管理员)

交互式(LOGON32_LOGON_INTERACTIVE

主令牌

否(UAC 适用)

交互式(本地管理员帐户,例如 rid-500)

主令牌

网络(LOGON32_LOGON_NETWORK

冒充

12

是(+已启用所有权限)

网络(本地管理员帐户,例如 rid-500)

冒充

取决于远程 UAC 设置13

表 1 - 对应 dwLogonType 的 LogonUser 参数组合


关键在于,LogonUser 返回的是一个指向新生成的令牌的句柄,该令牌现在可以用于冒充用户。

如果返回的令牌是令牌,则必须首先通过调用 DuplicateTokenEx 函数,并传入 TokenType 为 TokenImpersonate 的参数,将其转换为模拟令牌。14

BOOL DuplicateTokenEx (
HANDLE hExistingToken、
DWORD dwDesiredAccess、
LPSECURITY_ATTRIBUTES lpTokenAttributes、
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel、
TOKEN_TYPE TokenType、
PHANDLE phNewToken
);


然后可以使用 SetThreadToken 函数将返回的模拟令牌分配给当前线程:

BOOL SetThreadToken(
PHANDLE 线程、
HANDLE 令牌
);

或者,Windows API 提供了 ImpersonateLoggedOnUser 函数,该函数允许调用线程模拟传入令牌表示的用户的安全上下文:

BOOL ImpersonateLoggedOnUser(
HANDLE hToken
);

ImpersonateLoggedOnUser 的额外优势在于,它会自动检查传入令牌的类型,如果传入的是主令牌(因为此类令牌无法被线程用于模拟),则会通过 NtDuplicateToken 将其转换为一个模拟令牌。15.

需要注意的是,从防御规避的角度来看,这两个模拟 API 都是对未公开的系统调用 NtSetInformationThread 的轻量级封装(例如,使用 ThreadImpersonationToken 为 ThreadInformationClass 进行调用)。因此,它们成为攻击者利用直接系统调用绕过用户模式钩子的理想目标,例如通过 https://github.com/jthuraisamy/SysWhispers 中的技术实现。

此外,需要强调的是,Windows 对身份模拟有严格的规定。这些规则如下所示,摘自 MSDN 页面中关于 ImpersonateLoggedOnUser 的说明:

所有模拟函数(包括 ImpersonateLoggedOnUser)在满足以下任一条件时,均允许请求的模拟操作:
- 请求的令牌模拟级别低于 SecurityImpersonation,例如 SecurityIdentification 或 SecurityAnonymous;
- 调用方拥有 SeImpersonatePrivilege 权限;
- 一个进程(或调用方登录会话中的另一个进程)通过 LogonUser 或 LsaLogonUser 函数使用显式凭据创建了该令牌;

身份验证的身份与调用方相同。

此外,被模拟令牌的完整性级别必须小于或等于调用进程的完整性级别,否则模拟调用也会失败16。因此,假设一个未获得权限的攻击者通过窃取的凭据以交互方式登录一个管理员用户,并且启用了 UAC,他们将收到一个未提升权限(例如,经过筛选)的令牌,从而可以顺利模拟返回的用户并进行横向移动等操作。

“神秘的/NETONLY 标志”17

然而,攻击者可能会发现,尝试使用窃取的凭据登录某个用户账户会失败。这可能是由于多种原因造成的,例如凭据本身有效,但该帐户没有登录特定工作站的权限,或凭据仅在其他域中有效等。此外,攻击者可能还会完全避免登录高权限帐户,因为在某些情况下,这可能会显得极为异常(例如,域管理员登录到低权限业务用户的主机上,就应引起高度怀疑)。18

在这种情况下,LOGON32_LOGON_NEW_CREDENTIALS 标志可为攻击者提供帮助。如果攻击者使用此标志调用 LogonUserW 函数并传入一组有效的凭据(例如,通过监听共享获取的凭据),Windows 将允许调用者复制其当前令牌,但将其指向一个新的登录会话,即所谓的“新凭据登录会话”,该会话会缓存窃取的凭据。因此,用户在本地仍然拥有相同的安全上下文(例如,他们仍然拥有相同的访问令牌副本;只是该令牌指向了一个新的登录会话),但是,任何远程身份验证尝试都将使用在调用 LogonUserW 时传入的新凭据。19下图对此进行了说明:

2-logon-new-credentials-blog-access-token-manipulation.gif

图 2 - LOGON32_LOGON_NEW_CREDENTIALS 标志的底层工作原理


因此,LOGON32_LOGON_NEW_CREDENTIALS 标志提供了一种原生机制,使当前访问令牌指向不同的登录会话,从而指向不同的凭据。20

请注意,使用 LOGON32_LOGON_NEW_CREDENTIALS 标志调用 LogonUserW 时,不会在调用时验证凭据(这些凭据可能完全是无效的),而仅在发出任何远程身份验证请求时由域控制器进行验证。

再举一个例子,快速浏览一下开源 .NET C2 框架 Covenant 中的“MakeToken”任务的代码,会发现其采用的正是完全相同的方法:它接受用户名/密码组合,并通过传递 LOGON32_LOGON_NEW_CREDENTIALS 标志来创建一个新的登录会话/令牌,然后再模拟返回的令牌。

此外,您还可以通过向 CreateProcessWithLogonW 传递 dwLogonFlags 参数的 LOGON_NETCREDENTIALS_ONLY 值,来重现完全相同的行为。21

BOOL CreateProcessWithLogonW(
LPCWSTR lpUsername、
LPCWSTR lpDomain、
LPCWSTR lpPassword、
DWORD dwLogonFlags、
LPCWSTR lpApplicationName、
LPWSTR lpCommandLine、
DWORD dwCreationFlags、
LPVOID lpEnvironment、
LPCWSTR lpCurrentDirectory、
LPSTARTUPINFOW lpStartupInfo、
LPPROCESS_INFORMATION lpProcessInformation
);

关键区别在于,这里涉及的是使用返回的令牌启动一个新进程,而不同于之前讨论的进程内模拟。事实上,Windows 内置实用程序 runas 就是 CreateProcessWithLogonW 的一个简单封装,而 /NETONLY 标志提供了一种使用不同的仅限网络凭据创建新进程的原生方法,如下图所示:

3-using-the-runas-blog-access-token-manipulation.png

图 3 - 使用 runas /NETONLY 标志以用户 astro\cosmo 身份启动新进程,但使用了不同的缓存凭据的示例。


与之前描述的方式完全相同,新的命令提示符在本地看起来是以同一用户身份运行的(即,令牌中缓存的属性对于任何本地访问检查都是相同的;因此 whoami 返回的是“astro\cosmo”),但任何远程身份验证尝试都将使用被盗的“ASTRO\Administrator”用户的凭据执行。

可以使用 SysInternals 的 LogonSessions 工具查看这些登录会话。使用 NewCredentials 标志创建的登录会话可以通过“登录类型”字段确定,如下所示:

4-logon-session-blog-access-token-manipulation.png

图 4 - 通常由 NETONLY 标志生成的 NewCredentials 登录会话示例。


此外,异常的 NewCredentials 登录会话(例如,通过 NETONLY 工具生成的会话)会在 Windows 事件日志中留下痕迹。这些痕迹可以通过事件 ID 4642 和 LogonType 9 来识别。如下图所示:

5-windows-event-log-blog-access-token-manipulation.png

图 5 - Windows 事件日志中事件 ID 4624 的示例,该事件通常由 NETONLY 标志生成。


请注意,SubjectUserName 字段显示的是原始用户,而 TargetOutboundUser/DomainName 字段显示的是指定网络的凭据(例如,传递的凭据)。22

自动提升权限

从本地权限提升的角度来看,还有一个特殊之处,那就是对于 rid-500 帐户,CreateProcessWithLogonW 会自动提升交互式登录所返回的令牌权限(例如,它会忽略 UAC)。因此,可以将本地或域管理员帐户传递给 CreateProcessWithLogonW,以便从中等/非提升上下文中执行具有提升权限的进程

可以使用 runas 命令验证此行为。例如,当使用 runas 以本地管理员帐户启动进程时(例如,runas/user:"Administrator" cmd.exe),生成的进程将具有提升的权限(例如,高完整性级别)。但是,当使用非 RID-500 帐户(但仍属于本地管理员组)时,生成的进程将不具有提升的权限(例如,它将是经过筛选的令牌/中等完整性级别)。

请注意,此行为与表 1 中列出的 LogonUserW 的排列方式一致。一个未提升权限的攻击者也可以以网络登录的方式登录某个(非 rid-500)管理员账户,并获得一个所有权限均被启用的提升权限令牌。

然而,根据之前概述的模拟规则,攻击者实际上不应该能够利用此令牌执行任何操作,因为任何模拟提升权限令牌的尝试都会失败,因为该令牌的完整性级别高于调用方。尽管如此,事实上可以复制该提升权限令牌,将复制令牌的完整性级别降至中等(注意:“isElevated”仍然为真)23,然后从未提升权限/中等完整性级别的上下文中开始模拟该提升权限令牌24。因此,从模拟令牌的角度来看,您可以绕过 Windows 默认的仅提升特定帐户权限的行为,并模拟提升权限的令牌,而无需考虑该帐户是否为 rid-500 帐户。

进程创建

请注意,默认情况下,当您创建子进程时,即使您当前正在模拟另一个安全上下文25,该子进程仍会继承您的主令牌。例如,如果您模拟的是 SYSTEM 令牌,并调用 CreateProcess(),它仍然会继承主进程令牌的副本(而不是继承线程的 SYSTEM 安全上下文)。26

因此,如果攻击者希望在不同的安全上下文中创建新进程,则必须满足以下任一条件:

  • 使用带有显式凭据的 CreateProcessWithLogonW(如前所述)
  • 调用 CreateProcessWithTokenWCreateProcessAsUserW,并传递令牌句柄(例如,通过 LogonUser 返回的令牌,或者更常见的是通过窃取的令牌)。

这两个函数都可以接收一个代表新进程安全上下文的令牌句柄。27

 BOOL CreateProcessWithTokenW (
HANDLE hToken、
DWORD dwLogonFlags、

LPCWSTR lpApplicationName、
DWORD dwCreationFlags、
LPVOID lpEnvironment、
LPCWSTR lpCurrentDirectory、
LPSTARTUPINFOW lpStartupInfo、
LPPROCESS_INFORMATION lpProcessInformation
);
 BOOL CreateProcessAsUserW (
HANDLE hToken、
LPCWSTR lpApplicationName、
LPWSTR lpCommandLine、
LPSECURITY_ATTRIBUTES lpProcessAttributes、
LPSECURITY_ATTRIBUTES lpThreadAttributes、
BOOL bInheritHandles、
DWORD dwCreationFlags、

LPVOID lpEnvironment、{cph0} LPCWSTR lpCurrentDirectory、
LPSTARTUPINFOW lpStartupInfo、
LPPROCESS_INFORMATION lpProcessInformation
);

例如,CreateProcessAsUserW 通常由操作系统自身用于在用户成功登录后启动用户的 Shell(当用户调用 creatProcessWithLogonW 时,辅助登录服务也会使用该函数)。从这个意义上说,它允许用户“将进程注入到自己选择的登录会话中”28。需要注意的是,这两个 API 都是围绕 CreateProcessInternalW(位于 KernelBase.dll 中)的封装。

关键区别在于,调用方必须具备特定权限才能调用这两个 API29。但从攻击者的角度来看,目标是相同的:在目标用户的安全上下文中获取代码执行权限,以便进行横向移动。

一个有趣的细节是,由于 PowerShell 在处理模拟和多线程方面存在限制,PowerShell Empire 框架被迫采用了这种进程生成方法(从检测的角度来看,这种方法可能会产生更多噪音),具体原因如此处注释中所述。

总之,使用进程创建令牌操作技术的工作流

保持不变。一旦攻击者获得了令牌的句柄(通过 OpenProcess / OpenProcessToken 获取主令牌;或在线程模拟的情况下使用 OpenThread / OpenThreadToken),攻击者必须调用 DuplicateTokenEx 来创建目标令牌的本地(主)副本,然后将此副本传递给 CreateProcessWithTokenW 或 CreateProcessAsUserW 函数。

需要注意的是,在这种情况下,攻击者只对特权登录会话感兴趣,且这些会话为非网络登录,因为网络登录不会缓存凭据,因此无法用于向其他主机进行身份验证。

2. 票据传递


Windows 提供了一种使用 Kerberos30执行与 NETONLY 标志非常相似的技术的原生方法。这种技术更为强大之处在于,攻击者无需创建新的登录会话,而是可以随意更改与其当前登录会话(以及相应的访问令牌)关联的缓存 Kerberos 凭据(例如 TGT),如下所示:

6-pass-the-ticket-blog-access-token-manipulation.gif

图 6 - 票据传递攻击的底层工作原理。在此示例中,用户 ASTRO\cosmo 将窃取的 ASTRO\Administrator 用户的 TGT 应用于其当前登录会话。


为了开始与 Kerberos SSP 交互并管理 Kerberos 票据缓存,进程可以调用 LsaCallAuthenticationPackage(位于 Sspicl.dll 中):

NTSTATUS LsaCallAuthenticationPackage(
HANDLE LsaHandle、
ULONG AuthenticationPackage、
PVOID ProtocolSubmitBuffer、
ULONG SubmitBufferLength、
PVOID *ProtocolReturnBuffer、
PULONG ReturnBufferLength、
PNTSTATUS ProtocolStatus
);

请注意,用户必须先调用 LsaConnectUntrusted 以获取与 LSA 服务器的连接句柄,并调用 LsaLookupAuthenticationPackage 来查找 Kerberos 包的 ID (MICROSOFT_KERBEROS_NAME_A)。此外,在 IDA 中检查这些函数(它们同样位于 Sspicl.dll 中)会发现,它们是通过 RPC 与 Lsa 进行连接的。31.

通过 LsaCallAuthenticationPackage,用户可以发起多种敏感请求,但具体可执行的请求取决于用户是否拥有提升权限。例如,未提升权限的用户可以执行基本的票证管理操作32,例如枚举当前活动票证、清除票证缓存以及将任意票证应用到当前登录会话33。因此,这实际上使用户能够更改与当前登录会话关联的缓存凭据,从而指定任意仅限网络使用的凭据

此外,攻击者可以从权限更高的上下文34中枚举并转储其他用户的票证(例如凭据),从而实现与 mimikatz 类似的功能,而无需打开 lsass 35的句柄。

此处提供了可发送给 Kerberos 身份验证包的消息类型的完整列表。要更改与给定登录会话关联的当前 TGT,可以传递 KerbSubmitTicketMessage,该消息使用以下消息结构:

Typedef struct _KERB_SUBMIT_TKT_REQUEST {
KERB_PROTOCOL_MESSAGE_TYPE MessageType;
LUID LogonId;
ULONG Flags;
KERB_CRYPTO_KEY32 Key;
ULONG KerbCredSize;
ULONG KerbCredOffset;
} KERB_SUBMIT_TKT_REQUEST, *PKERB_SUBMIT_TKT_REQUEST

因此,对于 KerbSubmitTicketMessage,ProtocolSubmitBuffer 参数只需指向一块内存区域,该区域包含一个 KERB_SUBMIT_TKT_REQUEST 结构体,其后紧跟一个 ASN 编码的 Kerberos 票据(即要应用于指定登录会话的票据)。mimikatz 中用于提交 KerbSubmitTicketMessage 请求的相关代码可在此处找到,Rubeus 中的代码可在此处找到。

在调用 LsaCallAuthenticationPackage 之后,用户的 TGT 现在已更新为被盗的票据。从现在开始,任何与用户访问令牌或交互式登录会话关联的进程或线程尝试访问网络资源时,都会自动通过 Kerberos 使用窃取的 TGT 进行身份验证(例如,请求域内其他资源的服务票证/ TGS)。

需要注意的是,一个用户在同一登录会话中只能拥有一个 TGT。因此,应用新票据会清除用户之前的票据。如果攻击者想要保留其当前的 TGT,该怎么办?在这种情况下,NETONLY 标志再次发挥作用——攻击者可以使用任意或无效的凭据,通过 CreateProcessWithLogonW 创建一个“牺牲”性质的 NETONLY 进程。这将创建一个新的虚拟进程,并且更重要的是,会创建一个新的登录会话(以及访问令牌),可以将窃取的 TGT 应用于该会话(从而保留用户的当前票据)36

防御人员从这项技术中得出的一个重要结论是,由于所有活动都通过 LsaCallAuthenticationPackage(即通过 RPC)执行,所以无需与 lsass 进行任何直接交互(注意,这里的“直接”指的是通过 OpenProcess 打开 lsass 的句柄)。此外,对于这个特定的用例 (ptt),所有活动都通过本地 RPC 进行,直到攻击者尝试对远程主机进行身份验证(这将生成新的登录信息)。

例如,Rubeus 的 README 文件中包含以下声明:

“Rubeus 没有任何代码会直接访问 LSASS(也无意如此),因此其功能仅限于通过调用 LsaCallAuthenticationPackage() API 来提取 Kerberos 票据。”

因此,任何基于对 lsass 句柄访问的检测逻辑(例如,通过 ObjectPreCallback 内核例程对指定进程线程句柄操作进行检测,或者通过用户模式钩子对 OpenProcess/NtOpenProcess 进行检测)都可能遗漏此活动。因此,对于依赖 Sysmon 进程访问事件来检测可疑进程句柄访问的防御措施而言,这可能是一个潜在的盲点。

3. 哈希传递 (PtH)

本博客最后介绍的两种技术,都是攻击者通过直接修改 lsass 内存,非法更改与其当前访问令牌/登录会话关联的缓存凭据的示例。在 PtH 场景中,攻击者的访问令牌保持不变,仍然指向相同的登录会话,但其关联的缓存凭据会被直接覆盖为窃取的哈希值。从此时起,任何远程身份验证尝试都将使用该窃取的哈希值,如下所示:

7-under-the-hood-blog-access-token-manipulation.gif

图 7 - PtH 攻击的底层工作原理。在此示例中,用户 ASTRO\cosmo 的合法哈希值在内存中被 ASTRO\Administrator 用户的 NTLM 哈希值覆盖。


从这个角度来看,PtH 和 OPtH 在功能上与前面讨论过的 NETONLY 技术相同。

PtH 攻击的典型工作流如下:

  • 打开 lsass 的写入句柄(例如通过 OpenProcess / NtOpenProcess,并指定所需的 PROCESS_VM_WRITE 访问权限)
  • 枚举登录会话的链表
  • 找到目标登录会话,并确定所需的身份验证包(对于 PtH/NTLM,这是 MSV1_0 身份验证包)。
  • 更新关联的缓存凭据

需要注意的是,这些技术通常依赖于解析和修改未公开的 Windows 结构。本博客不会深入探讨这一点,但您可以在此处此处找到有关具体实现方式的更多信息。

因此,一旦缓存的凭据在内存中更新,当任何以该令牌运行的进程/线程尝试访问远程资源时,系统将按照通常的 Windows 单点登录身份验证设计自动使用这些凭据进行远程身份验证。

请注意,在这个简单的示例中,并没有创建任何额外的登录会话/访问令牌。然而,与“票据传递”攻击类似,这些工具通常也需要频繁创建新的临时 NETONLY 进程/登录会话,以便保留现有凭据或应用窃取的凭据。

需要注意的是,为了获得 lsass 的写入权限,恶意软件通常会采取以下两种方法:

  • 获取 SeDebugPrivilege37
  • 窃取并模拟 SYSTEM 令牌

第一种方法已在本系列博客的第一部分中讨论过,而第二种方法则是窃取/模拟令牌以绕过本地访问检查的典型示例(例如,窃取具有特定权限(例如 SeTcbPrivilege)的 SYSTEM 令牌)。SYSTEM 令牌通常通过从 winlogon 中窃取主令牌来获取。

4. 超哈希传递 (OPtH)

“超哈希传递”技术与传统的“哈希传递”概念类似,但有一个关键区别:它将哈希值转换为完整的 TGT 票证。

当用户首次登录 Windows 工作站时,作为 Kerberos 身份验证过程的一部分,系统会使用用户的密码哈希加密一个时间戳,以向域控制器/密钥分发中心 (KDC) 验证用户身份,并获取TGT。“超哈希传递”会修改内存中缓存的这些哈希值,然后启动正常的 Kerberos 身份验证协议(AS-REQ/AS_REP 等),以获取被窃取哈希值的完整 TGT。

这项技术可以通过 mimikatz 的 pth 命令来实现(该命令被误导性地标记为 pth,但实际上它在底层执行的是“超哈希传递”):

mimikatz # sekurlsa::pth /user:Administrator /domain:ASTRO.testlab /ntlm: c0f969f35beb20e8f09ce86ef42ccd51 

该方法本质上执行与 PtH 相同的步骤,但目标是 Kerberos SSP(即 kerberos.dll)。40

8-opth-attack-blog-access-token-manipulation.gif

图 8 - OPtH 攻击的工作原理。在本示例中,用户 ASTRO\cosmo 的合法哈希值在内存中被 ASTRO\Administrator 用户的哈希值覆盖,从而启动正常的 Kerberos 身份验证流程。


由于此技术再次涉及清除与用户登录会话关联的当前 TGT,攻击者可以使用 NETONLY 进程(以及关联的虚拟登录会话)来保留其当前 TGT,这正是 mimikatz 默认执行“超哈希传递”攻击时所采用的方法。

首先,它通过 CreateProcessWithLogonW 函数创建一个处于挂起状态的新进程,并设置 LOGON_NETCREDENTIALS_ONLY 标志。然后,它获取该挂起进程的主令牌句柄,并通过 GetTokenInformation 函数检索新虚拟登录会话的身份验证 ID。此函数用于查询令牌中缓存的信息,查询依据是 TOKEN_INFORMATION_CLASS 枚举类型,在本示例中为 TokenStatistics

获取身份验证 ID 后,mimikatz 现在可以开始枚举 lsass 中的登录会话链表,查找新创建的登录会话。一旦找到目标登录会话(通过身份验证 ID),它就可以更新与其关联的 Kerberos 凭据。凭据更新后,该令牌(其对应的登录会话现在已链接到被窃取的哈希值)可以通过 DuplicateTokenEx 转换为模拟令牌,并像之前看到的那样,通过 SetThreadToken 进行模拟。

在此阶段,攻击者再次尝试访问网络中的资源时,将使用作为 mimikatz 参数提供的 domain\user 和密码哈希组合进行身份验证。因此,所有远程交互都将使用被盗凭据的访问权限和特权进行。

结论

本系列博客文章共分为两部分,旨在解释 Windows Security 的基本概念在底层是如何运作的,并展示攻击者如何滥用这些功能来入侵 Windows 域。本博客已证明,无论滥用何种工具或身份验证提供商,攻击者都会受到一系列约束,从而导致访问令牌操纵出现相同的异常信号(例如,异常的仅限网络登录)。这些限制是由访问令牌、登录会话和缓存凭据之间的基本关系决定的。

准备好使用 Elastic Security 实现全面的数据保护了吗?立即免费试用,或在 Elastic Cloud 上的 Elasticsearch Service 体验我们的最新版本。此外,您还可以参加我们的快速入门培训,助您轻松上手。


参考

1. 有关 Kerberos 身份验证工作原理的回顾,请参阅 Keith Brown 的《Windows Security 编程》或 https://posts.specterops.io/kerberosity-killed-the...。此外,Rubeus(一个用于与 Kerberos 交互的工具包)拥有一个信息量非常大的自述文件,建议进一步阅读。

2. 请记住,根据 Windows 单点登录身份验证机制,每当用户尝试访问网络资源时,Windows 都会自动使用登录会话中缓存的凭据进行身份验证。此处缓存的凭据可以指任何身份验证提供商(例如 NTLM 哈希或 Kerberos 票证)。注意:这假设用户以交互方式登录(非网络登录)。

3. 这通常是为了避免因事件响应或主机隔离而失去立足点。

4. 这显然仅适用于攻击者在被入侵主机上的活动,而不适用于攻击者从其他来源(例如通过 impacket 等远程工具)执行代码的情况。

5. https://clymb3r.wordpress.com/2013/11/03/powershel...{cph0

6. 请参阅 Cobalt Strike 中的“steal_token”命令,以了解此技术的示例:https://www.cobaltstrike.com/help-beacon

7. 来自已存档的 PowerSploit 框架中的这条注释也应能进一步阐明令牌窃取在本地权限提升与横向移动之间的区别。

8. 此外,攻击者还可以采用密码喷射攻击,或尝试使用 responder 等工具进行 NTLM 监听/重放攻击。

9. 请注意,LogonUserA/W 实际上只是 SspiCli.dll 中 LogonUserExExW 的简单封装。

10. 同样地,CreateProcessWithLogonW 可以传入一个本地管理员账户 (rid-500),从而在中等权限或非提升的上下文中启动一个提升权限的进程。

11. 可以通过远程 UAC 注册表选项来修改此行为。

12. 还有一种额外的登录类型,LOGON32_LOGON_NETWORK_CLEARTEXT,它本质上是一种使用缓存凭据的网络登录。有关更多信息,请参阅 Keith Brown 的《Windows Security 编程》。

13. 更多信息请参阅:


https://blueteamer.blogspot.com/2018/12/disabling-...
https://support.microsoft.com/en-gb/help/951016/de...
https://labs.f-secure.com/blog/enumerating-remote-...

14.注意:还有一个 DuplicateToken 函数,但它仅返回一个模拟令牌。

15. 这可以通过在 IDA 中检查该函数来验证。或者,可参考 ReactOS 上的此处进行确认。

16. 此总结对模拟安全机制进行了略微简化。如需更更全面的概述,请参阅 James Forshaw 的《Windows 逻辑权限提升简介》幻灯片(第 26 页):https://conference.hitb.org/hitbsecconf2017ams/mat...

17. 此标题取自 Raphael Mudge 的一篇优秀博客文章:《Windows 访问令牌与备用凭据》

18. 这通常是攻击者不常用选项 2 的主要原因。

19. 因此,即使重复的令牌具有不同的网络凭据,运行“whoami”命令仍然会显示相同的用户(因为令牌仍然相同)。这是使用 Cobalt Strike 的 make_token 命令时常见的混淆点(该命令底层执行的技术与上述技术相同)。

20. Windows RPC/COM API 还允许用户指定仅限网络使用的凭据。例如,可以通过调用 RpcBindingSetAuthInfoExW 函数并通过 AuthIdentity 参数传递 SEC_WINNT_AUTH_IDENTITY 结构来实现对 RPC 的设置。有关详细信息,请参阅 Keith Brown 的《Windows Security 编程》以及 https://docs.microsoft.com/en-us/windows/win32/wmisdk/setting-authentication-using-c-

21. 虽然这两个标志名称不同,但其含义相同;这些凭证只能在网络内部使用。

22. 请注意,仍然存在一些方法可以绕过为异常登录会话创建可疑事件日志的问题。

23. 这是 James Forshaw 的一个技巧——详情请参见以下博客:https://www.tiraniddo.dev/2017/05/reading-your-way...。此外,TokenViewer 是一个用于试验此类技术的优秀工具。

24. 使用此生成的模拟令牌后,即可向 System32 等目录写入文件。

25. 不过,在调用 API 前进行模拟仍可能存在合法原因,例如在调用需要特定权限的 API 之前获取当前不具备的权限(但请注意,某些 API 会自动启用所需特权)。

26. 有几种方法可以解决这个问题。例如,可以通过使用具有 PROCESS_CREATE_PROCESS 访问权限的 OpenProcess 函数获取 SYSTEM 进程的句柄,从而创建一个作为 SYSTEM 进程子进程的进程。然后,可以将此句柄作为 ParentProcess 参数传递给 NtCreateProcess 函数。此外,也可以通过 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 参数配合 CreateProcess 函数来实现该操作:https://gist.github.com/xpn/a057a26ec81e736518ee50...

27. 奇怪的是,CreateProcessWithTokenW 函数虽然也需要一个现有令牌的句柄(而该令牌按定义应已拥有对应的登录会话),但仍接受 dwLogonFlags 参数。这很可能与加载用户配置文件有关。

28.《Windows Security 编程》,Keith Brown

29. 具体而言,CreateProcessWithTokenW 需要 SE_IMPERSONATE_NAME;而 CreateProcessAsUserW 则需要 SE_INCREASE_QUOTA_NAME (&) SE_ASSIGNPRIMARYTOKEN_NAME(如果令牌不可分配)

30. 关于 Kerberos 身份验证的概述请参阅此处,有关 Kerberos 相关攻击的更多信息,请参阅以下内容:https://www.blackhat.com/docs/us-14/materials/us-1...https://github.com/GhostPack/Rubeus#readme

31. https://googleprojectzero.blogspot.com/2019/12/cal...{cph0

32. 例如,Windows 原生工具 klist 提供了类似的功能,显然是 LsaCallAuthenticationPackage 的封装。

33. 请注意,未提升权限的用户只能将票证应用于自己的登录会话;若要将 TGT 应用于其他登录会话,则需要提升权限。

34. 这一说法存在一些注意事项和细节,更详细的解释可参考 Rubeus 的自述文件。简而言之,调用者需要通过 LsaRegisterLsaProcess 注册 LSA 连接,而此操作需要 SeTcbPrivilege 权限(即调用者是可信计算基础的一部分)。

35. 另需注意的是,您还可以通过 LsaCallAuthenticationPackage 与 msv1_0 身份验证包通信,并发送以下消息类型: https://docs.microsoft.com/en-us/windows/win32/api...,虽然我尚未研究是否也可以通过此接口检索 NTLM 凭据。

36. 有关更多信息,请参阅 Rubeus GitHub 存储库 readme,其中对许多与 Kerberos 相关的功能和操作安全注意事项进行详尽的说明。

37. 有关启用权限的示例,请参阅此处

38. 可以通过在 IDA 中查看 PsOpenProcess/Thread 并查找对 SePrivilegeCheck 的调用来验证这一点。

39. 需注意,从检测逻辑的角度来看,获取 SeDebugPrivilege 权限通常会产生较大的噪声。

40. 请注意,哈希/密钥可以是 rc4_hmac(例如 NTLM)、aes128_hmac、aes256_hmac 等。有关更多信息,请参阅此处

41. 有关更多详情,请参阅:https://www.blackhat.com/docs/us-14/materials/us-1...

42. 需要注意的是,Rubeus 的 asktgt 功能通过从未提升权限的上下文中为指定哈希构建原始 AS-REQ 流量,而无需访问 lsass,从而实现了一种“超哈希传递”的变体操作。