Windows 认证理论
我们在之前已经讨论了 NTLM 认证、Kerberos 认证、LSA 与 LSASS、SAM 等概念。在这小节,我们将对 Windows 认证理论做一个整合、归纳以及延伸。
本章节所使用的原理示意图和部分理论来自 https://attl4s.github.io/assets/pdf/Understanding_Windows_Lateral_Movements_2023.pdf 。
Windows 认证类型与形式
发生在 Windows 环境中的认证过程,可以是本地认证,以及域认证。
本地认证
本地认证,例如是使用本地账户登陆主机。我们对于个人用的主机进行登陆往往就是本地登陆。本地用户只存在于特定系统,即只有该系统才知道有哪些本地用户,例如 SRV01\David。尽管系统与系统之间是互相独立的,但是不同的系统却可能有着相似甚至相同的帐号密码。例如 SRV01\David,SRV02\David。我们之前知道了本地系统的信息存储于 SAM 之中,当用户以本地账户认证的时候,Windows 会根据 SAM 中的记录验证凭证。
域认证
域认证,例如是员工通过分发的域账户登陆工作站并访问内部资源。总之,域认证与本地认证是不同的机制。域用户和群组呈现在具体的 AD 域中,所有的域系统, (或在受信任的域中) 知道如何应对认证,它们会将认证的流程委派给域控制器。域用户和主机数据存储于 NTDS (NT 目录服务) 数据库中,当有用户尝试认证,域控制器则根据记录来验证凭证。
而认证的形式,可以笼统地分为物理认证以及远程认证。
物理认证
当用户在电脑面前,输入凭证并登陆,那么这是物理认证,适用于本地用户和域用户,只要目标系统知道认证的帐号。在 AD 中,默认情况下,任何域用户可以物理登陆任何域主机。但是,绝大多数情况下,我们没有机会在目标的环境中实现物理登陆。
远程认证
与物理认证有所不同的是,默认情况下需要特定权限才能进行远程登陆,例如属于本地管理员分组,远程桌面用户分组。因此,我们应该关心的还是远程认证。
为了理解接下来的概念,我们需要对 Windows 认证机制很熟悉。
Windows 认证重要概念
认证包/安全支持提供程序 AP/SSP
虽然在前面的小节有讨论过了,让我们再回顾一下。AP/SSP 通过分析登陆数据来认证 Windows 用户,不同的 AP/SSP 对多种登陆过程以及认证协议提供支持。AP/SSP 以 DLL 形式存在,被 LSA 所加载和使用。常见的 AP/SSP 有 NTLM,Kerberos,WDigest,Credman 等。微软所提供的 AP 有 CSSP,Microsoft Negotiate,Microsoft NTLM,Microsoft Kerberos,Microsoft Digest SSP,以及 Secure Channel。
AP 提供了 Windows 充当客户端和认证服务器所需的逻辑。对于客户端,想要通过 Windows 认证连接到特定服务。对于服务器,想要让服务或应用程序支持 Windows 认证
SSP 接口
微软提供 SSP 接口以简单地将应用程序与认证系统无缝继承。
SSPI 的作用主要有这些:安全包管理、凭证管理、安全上下文管理、消息支持。
交互式登陆 vs 非交互式登陆
本地认证与域认证、物理认证与远程认证,这样的分类还不够。认证还有交互式与非交互式之分。
交互式登陆,通常需要指定明文凭证,例如通过 Windows 的登陆页面进行认证。最重要的一点是用户凭证会缓存在 LSA 进程的内存中,为各个 AP 做准备。缓存的凭证还能让 Windows 给用户提供 SSO 体验。
而非交互式认证,应用程序代表用户使用缓存的凭证,也就是非交互式认证只应在交互式认证之后发生,也就是缓存凭证可用时。一些应用通过 SSPI 来实现这些认证。
登陆会话
登陆会话会在成功的认证之后创建,无论是物理认证、远程认证、域认证、本地认证、交互式登陆还是非交互式登陆。AP 缓存的凭证绑定在了登陆会话上。而在用户登出后结束。
当认证成功时,被选中的 AP 将执行这两个重要任务:创建一个新的登陆会话,以及向 LSA 提供认证用户的安全信息。
LSA 使用该信息创建访问令牌来代表用户在系统中的的安全上下文。
通常来说,登陆会话通常在交互式认证之后会有缓存的凭证,而非交互式的认证普遍会导致不缓存凭证的登陆会话。
登陆类型
在 Windows 中,所有的认证都应当作为以下登陆类型之一处理,无论是何种认证协议以及认证形式。
登陆类型 | 编号 | 认证形式 | 凭证是否存储于 LSA 中 | 案例 |
交互式 | 2 | 密码,智能卡,其他 | 是 |
物理登陆 runas IIS 6 之前的基本认证 |
网络 | 3 | 密码,NT 哈希,Kerberos 票据 | 否 (除非委派被启用) |
NET USER RPC 调用 远程注册表 集成 Windows 认证的 IIS SQL Windows 认证 |
批处理 | 4 | 存储于 LSA 机密中的密码 | 是 | 计划任务 |
服务 | 5 | 同上 | 是 | Windows 服务 |
网络明文 | 8 | 密码 | 是 |
IIS 6 及之后的基本认证 带有 CredSSP 的 Windows PowerShell |
新凭证 | 9 | 密码 | 是 | runas /network |
远程交互式 | 10 | 密码,智能卡,其他 | 是 | 远程桌面 |
导入 Get-LogonSession.ps1 脚本 (https://github.com/leechristensen/Random/blob/master/PowerShellScripts/Get-LogonSession.ps1)。查看 Web02 目前的会话列表 (大家自己操作得到的结果可能有所不同),仅筛选出 white-bird 域中的用户
get-logonsession | where-object {$_.Domain -eq 'WHITE-BIRD'}|select UserName, LogonType, AuthenticationPackage
因为 Web02 设置了 serveradm 的自动登陆,那么 serveradm 的交互式登陆自然是预期之中的。而 Web02 上同样运行着 SQL 服务,服务账号是 sql_service,因此服务登陆以及网络登陆同样是预期之中的。
令牌
当一个登陆会话被创建后,返回给 LSA 的信息被用于创建一个访问令牌。访问令牌是被保护的对象,它包含了一个认证用户的本地安全上下文。每个访问令牌被绑定给了一个登陆会话,访问令牌与进程或线程有关。
访问令牌包含了用户与其上下文的重要数据:用户 SID、用户组归属、特权列表、会话 ID、完整度等级、令牌类型等。
在 Windows 中,相同的用户可以有着不同的上下文,例如 UAC 区分了中等和高完整度等级。这是因为 Windows 允许相同用户在同一个系统中有着不同的访问令牌和登陆会话。
Windows 使用访问令牌实施访问控制决定。Windows 对象有着访问控制列表,访问这样的对象的进程或线程有着访问令牌,而令牌信息被拿来与对象的 DACL 进行比较从而决定是否允许访问。
令牌的类型有主要令牌与模仿令牌。主要令牌又被称为进程令牌,每个进程都有关联的主要令牌,当有新的进程被创建,默认是继承父进程的主要令牌。
模仿令牌又被称为线程令牌,得以让一个线程运行在与父进程所不同的安全上下文中,通常用于客户端服务端的情景。当每个客户端连接到服务,新线程得以创建。正是因为模仿令牌,线程得以运行在不同客户端的上下文中,这使得服务可以通过 ACL 控制访问。
支持 Windows 认证的服务执行被称为客户端模仿的操作。当一个客户端连接到服务,客户端的凭证会被验证,以这个客户端为安全上下文的访问令牌被创建,服务在新的线程中置入令牌的备份,进而该线程可以代表这个客户端,并且受 ACL 的限制。因为一些服务只需要客户端的部分信息,即不需要完全的模仿。根据服务的配置,模仿令牌可以有着不同的模仿等级:
SecurityAnonymous:服务器不能模仿客户端的身份
SecurityIdentification:服务器可以获得客户端的身份和特权但不能模仿
SecurityImpersonation:服务器可以在本地主机上模仿用户的安全上下文
SecurityDelegation:服务器可以在远程主机上模仿用户的安全上下文
用户模仿
用户模仿即创建或劫持另一个用户的安全上下文以在网络中代表这个用户。创建一个安全上下文通常需要凭证,而劫持一个安全上下文则需要特权。用户模仿可以通过这些途径实现:令牌操纵、密码、NT 哈希、Kerberos 票据等。
请回顾跟登陆会话绑定的凭证,通常是交互式认证的产物。如果想通过使用一个令牌来访问网络资源,这个令牌必须与一个有着凭证的会话相关联。
Windows API 提供了能用于操纵令牌的功能,例如复制令牌。但是,我们需要特权:本地管理员或者 SYSTEM,服务账号 (土豆家族)。而低权限用户只能操纵自己的令牌。
DuplicateTokenEx API 可用于创建一个复制现有令牌的新访问令牌。
BOOL DuplicateTokenEx(
[in] HANDLE hExistingToken,
[in] DWORD dwDesiredAccess,
[in, optional] LPSECURITY_ATTRIBUTES lpTokenAttributes,
[in] SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
[in] TOKEN_TYPE TokenType,
[out] PHANDLE phNewToken
);
如果是劫持一个已有令牌的方法,有着令牌模仿与进程注入的方法。前者复制目标的令牌并且将其用于已有进程或者新进程,后者将载荷注入到目标令牌所寄居的进程。
SharpToken (https://github.com/BeichenDream/SharpToken) 是一款可以通过操纵令牌来实现用户模仿的工具,在 本地管理员或者 SYSTEM 权限下,我们可以列举所有可用令牌,并窃取想要的令牌。
Cobalt Strike 中的内置命令 steal_token 也可实现令牌窃取
而通过进程注入实现令牌劫持的流程图如下:
有明文密码!
runas.exe
Windows 原生程序 runas.exe 可以通过提供额外的凭证来创建进程,例如用户想以另一个账户的身份运行特定进程。runas.exe 默认 (不提供其他选项) 会通过 LSA 验证提供的凭证的正确与否,类似于交互式认证。
对本地账户以及域账户都成立。
如果没有提供正确的凭证,则会失败。
在 Cobalt Strike 中,runas 命令则是对 Windows 系统中 runas.exe 的实现。
C# 工具 runascs (https://github.com/antonioCoco/RunasCs) 也有着相似的功能和实现,并且自定义程度更高,用户可以指定登陆类型。例如,指定 -l 为 2,则是交互式认证。
在使用 runas.exe 以 white-bird\condrey 身份创建的 cmd.exe 进程以及使用 runascs.exe 以 wanh 身份创建 cmd.exe 进程后,我们可以用之前的脚本命令发现新增了用户的登陆会话。
runas.exe /netonly
runas.exe 程序有着 /netonly 的选项,它告诉 runas.exe 这个指定的凭证仅用于远程访问,而 LSA 并不会验证凭证的正确与否,也就是即便输入错误的凭证也不会有报错信息。
指定了 /netonly 选项后,假设提供的凭证是有效的,那么新的进程在本地层面保持原来的身份,但在网络层面却是新的身份,即有着 2 个不同的安全上下文。prod\john 对于 Srv01 具有 WinRM 访问。通过 /netonly 创建的新 powershell.exe 进程中,我们发现身份还是 prod\alice,但如果使用 john 的访问在 Srv01 上远程执行命令,身份则是 john 了。
runas.exe 的实现使用的是 CreateProcessWithLogonW API,即以提供的凭证的安全上下文创建新的进程。/netonly 选项使用了 LOGON_NETCREDENTIALS_ONLY 登陆选项,创建和使用新的登陆会话,但是令牌还是原来的。
BOOL CreateProcessWithLogonW(
[in] LPCWSTR lpUsername,
[in, optional] LPCWSTR lpDomain,
[in] LPCWSTR lpPassword,
[in] DWORD dwLogonFlags,
[in, optional] LPCWSTR lpApplicationName,
[in, out, optional] LPWSTR lpCommandLine,
[in] DWORD dwCreationFlags,
[in, optional] LPVOID lpEnvironment,
[in, optional] LPCWSTR lpCurrentDirectory,
[in] LPSTARTUPINFOW lpStartupInfo,
[out] LPPROCESS_INFORMATION lpProcessInformation
);
make_token
多个 C2 工具中都有着类似于 make_token 功能的命令,Cobalt Strike 自然也是有这样的功能和命令的。make_token 允许我们在知道其他用户明文密码的情况下模仿他们,调用的是 LogonUserA API。
BOOL LogonUserA(
[in] LPCSTR lpszUsername,
[in, optional] LPCSTR lpszDomain,
[in, optional] LPCSTR lpszPassword,
[in] DWORD dwLogonType,
[in] DWORD dwLogonProvider,
[out] PHANDLE phToken
);
LogonUserA 相比 CreateProcessWithLogon 更好的地方在于,我们可以在不创建新的进程的情况下创建新的登陆会话/令牌对,并且可以指定不同的登陆类型 (在调用 LogonUserA 获得令牌后,可以创建新的进程)。Cobalt Strike 中的 make_token 使用的是 LOGON32_LOGON_NEW_CREDENTIALS 登陆类型,与 runas.exe /netonly 一致、
调用该 API 得到的令牌可以被传递给 ImpersonateLoggedOnUser API,这允许调用的线程模仿令牌的上下文 (被模样的用户的上下文)。
BOOL ImpersonateLoggedOnUser(
[in] HANDLE hToken
);
当不需要模仿的时候,使用 rev2self 命令退出。
BOOL RevertToSelf();
总之,在高完整度下,操纵令牌是容易的,我们可以做这些事情:窃取系统内的任何令牌、注入到任何进程、将窃取的令牌应用于当前的上下文、用窃取的凭证创建新的进程。如果是低完整度下,我们不能使用令牌创建新的进程 (CreateProcessAsUser 或CreateProcessWithToken API 需要管理员特权),因此考虑内联执行,例如 BOF。
有 NT 哈希!
如果我们没有明文密码,只有 NTLM 哈希呢?如何实现用户模仿?可惜的是,Windows 没有这样的认证功能,没有 LogonUserWithHash 或者 CreateProcessWithHash API。如果我们想通过 LSA 来利用哈希,我们需要与 LSASS 进程打交道。LSASS 存储着所有登陆会话以及缓存凭证,需要高权限,而且还很有风险。
但我们其实也不必依赖于 LSA,这不局限于有 NTLM 哈希,包括有明文密码或者其他类型的凭证。我们可以使用对 NTLM 或者 Kerberos 有原生支持的工具。
与 LSA 交互
Mimikatz 可以用来完成基于 LSA 的哈希传递。换句话说,哈希传递也可以不必依赖于 LSA。Mimikatz 可以使用 NTLM 哈希而非明文密码来创建进程,通过向 lsass 进程注入数据实现。因为需要修改 lsass 进程,自然需要高权限。
在 Web02 上,任何目前可用的令牌都不足以访问 Dc05。
在 SYSTEM 权限下,使用 mimikatz 的 PTH 命令
我们可以看到现在 lsass.exe 进程是 RW 权限了。这个命令将新凭证通过命名管道传递,然后 Beacon 再模仿它。
Mimikatz 的 PTH 相当于提供哈希而非明文密码版本的 runas.exe /netonly。先通过 CreateProcessWithLogon API 创建新的进程,其中使用的是 LOGON_NETCREDENTIALS_ONLY 登陆选项。然后,识别出新创建的登陆会话,将凭证内容填充到目标的登陆会话中。
现在,我们可以访问 Dc05 了。
用图来显示普通认证流程以及 Mimikatz 的 PTH 的话,如下所示:
不与 LSA 交互
不与 LSA 交互的 PTH 则就简单多了,只需要找到原生支持我们想要使用的协议的工具即可,例如 Impacket,CrackMapExec 等,而不需要与复杂的 Windows 组件打交道。
如果是 NTLM 协议:
如果是 Kerberos 协议,我们可以使用 Rubeus 或者 Impacket,这类工具可以产生获得 TGT/TGS 的 Kerberos 原始流量。至于如何利用得到的票据,我们下文就说。
有 Kerberos 票据!
虽然 Windows 没有基于 NTLM 哈希的认证功能,但对于 Kerberos 票据是有的。我们同样可以选择与 LSASS 交互,或者不与 LSASS 交互。
与 LSA 交互
我们可以将票据导入到存在的登陆会话中。将票据导入到当前的会话中不需要特权,而导入到其他会话中则需要特权。
假如我们已经得到或伪造了其他用户的 Kerberos 票据,并且打算使用它们。PTK,即票据传递是将这样的票据导入到攻击者所控制的登陆会话的技术,这使得我们可以在网络中模仿受害者。如果导入了一张 TGT 到现有会话中,那么原来的会被覆盖。
Rubeus 的 PTT 命令正是这样的原理
Cobalt Strike 的命令 kerberos_ticket_use 具有同样作用。
这项技术的实现,是使用了 LsaCallAuthenticationPackege API。这个 API 使得应用程序可以与 Windows AP 进行对话
NTSTATUS LsaCallAuthenticationPackage(
[in] HANDLE LsaHandle,
[in] ULONG AuthenticationPackage,
[in] PVOID ProtocolSubmitBuffer,
[in] ULONG SubmitBufferLength,
[out] PVOID *ProtocolReturnBuffer,
[out] PULONG ReturnBufferLength,
[out] PNTSTATUS ProtocolStatus
);
总之,如果使用与 LSA 交互的方法导入票据,我们可以在低权限的情况下将票据导入到当前会话。可以是使用 make_token 创建并模仿一个新的登陆会话,导入票据以防止覆盖原有的 TGT。在 alice 会话的 file01 Beacon 中,我们使用 make_token 命令创建并模仿一个新的登陆会话,密码不需要是有效的。此时,这个新登陆会话的还不存在任何票据,我们使用 kerberos_ticket_use 导入 white-bird\administrator 的票据,发现可以访问 Dc05 了。如果使用 rev2self 退出模仿,便不能访问了。
或者在高权限的情况下,先创建一个牺牲进程用来导入票据,因为一个登陆会话每次只能持有 1 张 TGT,在当前会话导入 TGT 可能导致认证紊乱。将票据导入到该牺牲会话之后,窃取令牌或者在该进程注入载荷。
目前,我们在 Web02 上不能访问 Dc05 (清除了现有票据以避免干扰)。使用 createnetonly 命令创建一个新的隐藏进程,我们可以看到新的会话 ID 以及进程 ID。但目前这个会话里还没有 TGT。我们还指定了域名、帐号、密码(不会被验证),这是为了在日志中看起来更加清白,毕竟 Rubeus 随机生成的这些数据会成为嫌疑。
因此,我们导入 Administrator 的 TGT,指定目标 LUID。
窃取令牌后,方可访问了。
不与 LSA 交互
我们可以用 Impacket 之类的工具来使用得到的票据。我们可以使用 Impacket 中的 ticketConverter 将票据在 kirbi 格式与 ccache 格式直接互相转换。在 Linux 端,所接受的格式是 ccache,我们需要设置 KRB5CCNAME 环境变量指向该 ccache 票据。