WindowsHTTP服务预认证漏洞[复盘]

admin 2026-06-03 04:15:41 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细复盘了BlackHatUSA2025披露的WindowsHTTP服务预认证漏洞,揭示了WindowsHTTPAPI(httpapi.dll)使用模式中存在的系统性安全问题。漏洞覆盖从DoS到RCE的多个危害等级,核心问题在于开发者在接收、响应、引用计数管理等环节对API的错误使用。文章从攻击面视角将漏洞分为接收阶段的逻辑DoS、响应阶段的资源泄露、IIS框架下的引用计数问题以及解析阶段的远程代码执行四大类,并针对每类漏洞给出了具体案例(如CVE-2024-43512、CVE-2025-27471、CVE-2024-38149、CVE-2024-38067、CVE-2024-43639)和深入的技术分析,最后总结了安全开发建议,强调HTTP服务处理函数不应停止接收且必须妥善管理连接与引用计数。 综合评分: 88 文章分类: 漏洞分析,应急响应,安全开发,Web安全,Windows安全


cover_image

Windows HTTP 服务预认证漏洞 [复盘]

kunlun kunlun

只愿壹生爱壹人

2026年5月29日 20:40 安徽

在小说阅读器读本章

去阅读

深入 Windows HTTP 服务

Black Hat USA 2025 复盘

Diving into Windows HTTP: Unveiling Hidden Preauth Vulnerabilities in Windows HTTP Services

概述

Black Hat USA 2025 上,安全研究员披露了 Windows HTTP 服务中一系列预认证漏洞,覆盖从 DoS 到 RCE 的多个危害等级,涉及 Windows 内置的多种 HTTP 服务组件。核心发现并非某个单一漏洞的利用技巧,而是 Windows HTTP API(httpapi.dll)的使用模式存在一类系统性的安全问题——开发者在接收、响应、引用计数管理等环节对 API 的错误使用,导致了大量可被预认证攻击者利用的漏洞。

本文从攻击面视角将这些漏洞分为四大类:接收阶段的逻辑 DoS、响应阶段的资源泄露、IIS 框架下的引用计数问题,以及解析阶段的远程代码执行。以下逐一梳理。

一、Windows HTTP 服务架构概览

Windows 的 HTTP 服务基于 httpapi.dll 提供的 API 实现。应用程序通过 HttpCreateServerSession 初始化服务会话,通过 HttpAddUrl / HttpAddUrlToUrlGroup 注册监听 URL。服务运行时,用户态的 Web 应用程序(IIS 或非 IIS)通过 DeviceIoControl 与内核态的 HTTP.sys 驱动通信,后者负责实际的 TCP/IP 协议收发。

典型的 HTTP 服务处理流程分为三个阶段:

接收阶段(Receive Stage)——调用 HttpReceiveHttpRequest 接收 HTTP 请求头,调用 HttpReceiveRequestEntityBody 接收 HTTP 请求体; 解析处理阶段(Parsing Stage)——服务解析并处理业务数据; 响应阶段(Response Stage)——调用 HttpSendHttpResponse 发送响应,或调用 HttpCancelHttpRequest 断开连接。

几乎所有的 HTTP API 服务在接收阶段都不要求预认证,只有极少数服务会在收到 HTTP 请求头后进行 NTLM 等认证。接收阶段的代码缺陷天然暴露在攻击面下。

一个实用的目标发现方法是:通过 netsh http show servicestate 命令和 HttpQueryServiceConfiguration API,可以枚举系统中所有注册的 HTTP 服务,包括绑定的 URL、SSL 证书、请求队列等信息,帮助快速定位研究对象。

二、接收阶段的逻辑 DoS

HTTP 服务的接收方式可以归纳为三种:同步接收、异步 WaitForMultipleObjects 接收、以及异步回调接收。每种模式下都存在因 API 使用不当导致的逻辑 DoS。

2.1 同步接收:CVE-2024-43512

Windows Standards-based Storage Management Service(具体实现位于 concrete.dll)使用同步方式接收请求。其接收循环大致如下:

while (1) {
    memset(v3, 0, 0x1360);
    BytesReturned = 0;
    v6 = HttpReceiveHttpRequest(this[1], RequestId, 0, v5, 0x1360, &BytesReturned, 0);
    if (v6 == 0xEA) {
        RequestId = v5->RequestId;
        v3 = realloc(v3, BytesReturned);
        goto LABEL_3;
    }
    ...
}

问题在于:服务固定了 0x1360 字节作为 HTTP 请求头接收缓冲区。当攻击者发送超过 0x1360 字节的 HTTP 请求头时,HttpReceiveHttpRequest 返回 0xEA(ERROR_INSUFFICIENT_BUFFER)。虽然代码正确地调用了 realloc 来扩展缓冲区,但 goto LABEL_3 跳回循环起点后,缓冲区大小再次被设为 0x1360,而非使用 realloc 后重新计算的大小。结果就是服务反复接收、反复失败,陷入死循环。

触发后,正常客户端只能收到超时错误。服务实际上已经永久停止处理请求。

2.2 异步 WaitForMultipleObjects:CVE-2025-27471

UPnP 服务(upnphost.dll)使用异步接收方式,其核心逻辑如下:

NumberOfBytesTransferred = 0;
HttpReceiveHttpRequest(this+14, RequestId, 0, v2, v4, 0, &Overlapped);
// 返回 0x3E5 (ERROR_IO_PENDING),不阻塞
...
switch (error_code) {
case 0xEA:
    v4 = NumberOfBytesTransferred;  // 此时仍为 0
    RequestId = v2->RequestId;
    free(v3);
    v2 = malloc(v4);
    ...
}
if (GetOverlappedResult(..., &NumberOfBytesTransferred, 0)) {
    ...
}

当攻击者在短时间内同时发送多个恶意请求时,HttpReceiveHttpRequest 不会正常返回 0 或 0x3E5,而是直接返回错误。而 NumberOfBytesTransferred 在初始化后尚未被 GetOverlappedResult 更新——如果 0xEA 错误提前返回,接收长度被设置为 0,缓冲区分配为零,后续处理进入死循环。一旦触发,即使攻击者断开连接,服务也会在该循环中无限自旋,不再响应任何请求。

2.3 异步回调:WSDApi.dll 的线程池耗尽

异步回调是 Windows HTTP 服务中最常见的接收模式——KDC Proxy、RDP、RDG、WinRM、ADFS 乃至 IIS 都使用这种方式。服务通过 CreateThreadpoolIo / StartThreadpoolIo 创建线程池,注册回调函数处理各类事件。

在回调模式下,每次处理完请求后,回调函数需要调用 StartThreadpoolIo 并再次调用 HttpReceiveHttpRequest,在线程池中创建新的接收线程。如果处理出错后回调直接返回而不创建新线程,该工作线程就会退出。当线程池中所有接收线程都因此退出时,服务不再处理任何新的客户端请求。

WSDApi.dll(Web Services Dynamic Discovery)中存在此类问题:

ioresult = *((_DWORD *)a2 + 15);
if (ioresult) {
    // 错误路径:直接返回,不调用 IssueReceiveRequest
    return ioresult;
} else {
    // 正常路径:调用 IssueReceiveRequest → StartThreadpoolIo → HttpReceiveHttpRequest
    CWSDHttpListener::IssueReceiveRequest(this, ...);
}

当 ioresult 非零(接收失败),函数直接返回,不会调用 IssueReceiveRequest 来创建新接收线程。攻击者通过反复发送构造的恶意数据包导致所有接收线程依次退出后,线程池为空,服务永久停摆。

微软确认了这是一个预认证 DoS,但评级为低严重性——因为该服务默认仅在受信网络中可用。这里需要指出:虽然评级不高,但此类问题的根因(错误路径下不退出的保障缺失)在跨网络暴露的服务中同样可能出现,届时影响要大得多。

安全开发建议

从这三个漏洞可以归纳出一条核心原则:HTTP 服务的处理函数永远不应停止接收。无论请求有效还是无效,服务都必须以 RequestId = 0 调用 HttpReceiveHttpRequest 等待新的客户端请求。需要仔细检查 API 的返回值(尤其是 0xEA 和 0x4CD),并在错误返回后妥善更新和验证接收参数的状态。

三、响应阶段的连接资源泄露

3.1 原理

HTTP.sys 内核驱动在建立连接时,通过 UxTlAllocateConnectionForLookaside 从非分页池分配连接结构并初始化引用计数。正常流程中,服务调用 HttpSendHttpResponse(发送响应后自动关闭)或 HttpCancelHttpRequest(强制断开),触发 UxTlFreeConnectionFromLookaside 释放连接、减少引用计数。

一个被忽略的场景是:如果服务在收到并处理完 POST 数据后,既不调用 HttpSendHttpResponse 也不调用 HttpCancelHttpRequest,而是直接调用 HttpReceiveHttpRequest 开始接收新数据,HTTP.sys 不会自动关闭该连接或减少引用计数。连接的底层结构驻留在非分页池中,持续不被释放会逐渐耗尽非分页池,最终导致系统挂起或蓝屏。

3.2 BranchCache:CVE-2024-38149

BranchCache 服务是这一问题的典型案例。BranchCache 的 POST 处理函数 CTnoDownloadMgr::OnMessage 根据 POST 数据中的字段分发到不同的消息处理分支:

switch (a3) {  // a3 由 POST 数据控制
case 1:        // MSG_NEGO_REQ
    ...
    break;
...
}
CTnoDownloadMgr::LogInvalidMessage(...);
SystemError::ThrowHelper("CTnoDownloadMgr::OnMessage", -2147024122);

当攻击者发送畸形的 POST 数据时,ThrowHelper 抛出异常。异常处理后,服务直接调用 HttpReceiveHttpRequest 接收新数据,而没有调用 HttpSendHttpResponse 或 HttpCancelHttpRequest 来关闭当前连接。如果攻击者也不主动断开连接,这个连接就在 HTTP.sys 中永久驻留,非分页池内存永远不会释放。

教训是:服务在处理完请求后,无论成功还是失败,都必须发送响应或主动关闭连接。

四、IIS 框架下的引用计数问题

4.1 ISAPI_CONTEXT 的生命周期管理

IIS 自身是一个基于 HTTP API 的服务,它为 Web 应用提供了基础框架。IIS 下的每个 Web 服务都通过 ISAPI 扩展来处理请求——即便是 .asp 和 C#/.aspx 应用,底层也是由内置的 ISAPI 扩展(如 aspnet_isapi.dllwebengine64.dll)支撑。

IIS 为每个 Web 服务维护一个 ISAPI_CONTEXT 结构。每当一个请求到达时,IIS 递增该结构的引用计数;请求处理完成后递减。当引用计数降到零时释放该结构。IIS 设置了一个引用上限 0x1366,一旦引用计数超过该值,IIS 返回 503 Service Unavailable

关键的管理接口是 ServerSupportFunction——IIS 提供的调度函数,ISAPI 扩展通过它向 IIS 发送信号(如 SSFDoneWithSession 会触发 ISAPI_CONTEXT::DereferenceIsapiContext 递减引用计数)。ServerSupportFunction 是由 ISAPI 扩展在其 HttpExtensionProc 中主动调用的,IIS 不会代劳。每个 IIS 服务必须正确、及时地调用它,否则引用计数就会失衡。

4.2 OCSP:CVE-2024-38067

OCSP(在线证书状态协议)服务运行在 IIS 框架下,其 ISAPI 扩展为 ocspisapi.dll。正常流程如下:

服务通过 HttpExtensionProcDispatchStencilCall接收客户端 POST 请求;使用CryptDecodeObjectEx解码 OCSP 请求数据;调用SendOCSPStatusSendResponseToClientServerSupportFunction发送 OCSP 响应。ServerSupportFunction内部经过W3_RESPONSE::WriteEntityChunksW3_RESPONSE::Flush发送数据,成功后调用PostCompletionPostQueuedCompletionStatus),最终触发ISAPI_CONTEXT::DereferenceIsapiContext 减少引用计数。

漏洞的触发条件是:攻击者在发送 POST 数据后、收到服务器响应之前主动断开 TCP 连接。此时 W3_RESPONSE::Flush 调用失败,返回负值错误,函数直接返回而不会调用 PostCompletionISAPI_CONTEXT 的引用计数因此永远不会减少。当攻击者反复执行此操作,引用计数累积到 0x1366 上限后,OCSP 服务对任何正常客户端都返回 503 Service Unavailable,造成持久 DoS。

IIS 下的引用计数问题不止于 DoS——引用计数的错误管理同样可能导致 use-after-free,进而升级为远程代码执行。

五、解析与处理阶段的预认证 RCE

5.1 KDC Proxy Service:CVE-2024-43639

KDC Proxy Service(KPS)是 Windows 中为 Kerberos 协议提供 HTTP 代理的服务。客户端通过 HTTPS 连接到 KPS 服务,KPS 解析客户端提供的域名,与后端的 KDC 服务器建立 Socket 连接,作为双向消息通道。

KPS 使用异步回调架构,且没有注册客户端断连回调——客户端断开时服务端不会收到通知,这本身也带来了额外的攻击面。

漏洞出在消息长度处理上。KPS 通过 Socket 接收 KDC 服务器的响应数据,其中消息长度由一个 4 字节字段指定。从补丁逆向分析来看,在调用 ASN.1 编码函数时存在整数溢出,大致可以还原为:

v9 = message_length / 0x18;
v10 = 0x18 + v9;
alloc_size = v10;  // 当 message_length 很大时,v10 可能溢出为极小值

当消息长度经过计算后导致 v10 回绕为 0 或极小值时,后续的 memcpy 操作就会越界写入堆内存。从崩溃栈可以看到:

0:007> k
 # Child-SP          RetAddr
00 0000004a`8837f230 00007ffd`52176a4b     MSASN1!ASN1BEREncLength+0x4d
01 0000004a`8837f260 00007ffd`41d2ea03     MSASN1!ASN1BEREncCharString+0x2b
02 0000004a`8837f290 00007ffd`52177802     kpssvc!ASN1Enc_KDC_PROXY_MESSAGE+0x73
03 0000004a`8837f2d0 00007ffd`41d40900     MSASN1!ASN1_Encode+0xa2
04 0000004a`8837f300 00007ffd`41d42325     kpssvc!KpsPackProxyResponse+0xcd
05 0000004a`8837f360 00007ffd`41d3e9e5     kpssvc!KpsSocketRecvDataIoCompletion+0x20d
06 0000004a`8837f460 00007ffd`52f01f31     kpssvc!KpsSocketIoCompletion+0xb2

RCX 指向不可写地址,调用链从 kpssvc!KpsSocketRecvDataIoCompletion 一路经 KpsPackProxyResponseKpsDerPack 直到 ASN.1 编码函数。这是一个典型的整数溢出导致堆溢出的 RCE 漏洞,入口是未认证客户端发送的 POST 数据。

5.2 RDP Gateway:CVE-2025-21309

Windows Remote Desktop Service 除了在 3389 端口提供 RDP 协议外,还在 3387 端口实现了一个支持 WebSocket 的 HTTP 服务。Remote Desktop Gateway(RDG)服务在 443(HTTPS)或 3391(DTLS)端口处理客户端连接,经认证和协商后,将 RDP 协议消息转发到后端 RDP 服务器。

RDG 使用异步回调架构,注册了客户端断连回调以及 WebSocket 和非 WebSocket 请求的分发回调。

漏洞的核心是一个竞态条件 + use-after-free。关键数据结构的关系如下:

Client ──ConID──→ hash_table ──→ Connection ──bar──→ &HTTPCon.offset
                                      ↑
                               arg3 (传入 ReceiveData 的参数)

正常流程中,Client 以某 ConID 发起 WebSocket 请求后,服务创建 Connection 存入哈希表,同时分配 HTTPCon(引用计数初始为 2——断连回调和 send completion 各占一份)。ReceiveData 收到参数 arg3(指向 HTTPCon 内部某偏移),从哈希表取出 Connection,将 arg3 赋给 Connection.bar,然后注册 HttpReceiveRequestEntityBody 回调。

攻击者利用断连和重连的时间差,制造了以下竞态条件:

  1. Client1 以 ConID1 连接,服务创建 Conn1 和 HTTPCon1(ref=2)ReceiveData 将 arg3(&HTTPCon1.off)写入 Conn1.bar

  2. 攻击者立即断开 Client1,断连回调将 Conn1 从哈希表删除,并对 HTTPCon1 解引用(ref→1)。但此时 send completion 尚未完成,HTTPCon1 仍存活

  3. 攻击者立即用 Client2 以相同 ConID1 发起连接,服务创建 Conn2 并存入哈希表。仍在运行的 ReceiveData 线程通过 ConID1 取出 Conn2,将 arg3(仍指向 HTTPCon1)赋给 Conn2.bar

  4. send completion 完成,HTTPCon1 最后一份引用解除(ref→0),内存释放。Conn2.bar 成为悬空指针

  5. Client2 发送 WebSocket 数据,HandleWebSocketReceiveRawDataCompletion 回调取出 Conn2,通过 Conn2.bar 向已释放内存写入攻击者可控的数据

// 步骤 5 的崩溃现场
HandleWebSocketReceiveRawDataCompletion:
  conn = hash_table[ConID1]        // 取出 Conn2
  memcpy(conn->bar, src, size)     // Conn2.bar 指向已释放的 HTTPCon1
  → 写入悬空内存

这是一个条件竞争 + use-after-free 的 RCE 漏洞,需要精确的时序控制。但需要指出:在实际利用中,时间窗口可以通过大量并发请求来覆盖,不宜因为”理论上需要竞态”就低估其实际可利用性。

六、总结

微软已在 SDL 安全服务条款中更新了对 DoS 类漏洞的处置:资源耗尽类漏洞不再给予 bounty 奖励。逻辑 DoS 对于高价值资产(如 DHCP Server、DNS Server、RDP Server、Hyper-V、SMB、Kerberos 等)仍在 bounty 范围内。RCE 漏洞在 HTTP 服务的 POST 数据解析阶段尤其常见,对这些解析路径做充分的模糊测试是发现类似问题的有效手段。

将上述漏洞的根因抽象来看,可以提炼出几条通用的安全工程原则:

系统化地寻找同类问题。 一旦发现某个 HTTP 服务的 API 使用模式有缺陷,同一模式很可能存在于其他服务中。

DoS 不需要崩溃。 请求处理中的逻辑缺陷(不释放连接、不创建新线程、引用计数不平衡)足以使服务永久不可用。

错误处理路径是漏洞的高发区。 半数以上的漏洞都发生在错误处理分支上——开发者通常在主路径上小心维护状态,但在异常和错误返回路径上疏忽了必要的清理和推进操作。

RCE 往往藏在更深层的解析逻辑中。 接收和响应阶段的问题通常是 DoS,而数据解析(ASN.1、消息长度计算)中的整数处理问题才是 RCE 的温床。


*参考:Black Hat USA 2025 – Diving into Windows HTTP: Unveiling Hidden Preauth Vulnerabilities in Windows HTTP Services*


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:只愿壹生爱壹人 kunlun kunlun《Windows HTTP 服务预认证漏洞 [复盘]》

评论:0   参与:  0