理解史上最严重的.NET漏洞:请求走私与CVE-2025-55315
我承认,这个标题非常吸引眼球,但微软已经给这个漏洞打了CVSS 9.9分,这是他们有史以来的最高分。是时候恐慌了吗?在这篇文章中,我试图提供更多的背景信息。我将解释请求走私漏洞的一般工作原理,以及这个特定案例的情况,攻击者能利用它做什么,漏洞是如何被修复的,以及你可以采取什么措施来保护自己。
警告:我不是安全专业人士,所以请不要将本文中的任何内容视为绝对真理或建议。我只是一个试图理解情况的开发者。😄 本文中的所有细节都基于原始公告中提供或引用的信息。
什么是CVE-2025-55315漏洞?
2025年10月14日,在微软一个标准的“补丁星期二”,微软发布了所有受支持版本.NET的新版本,并发布了一份安全公告:Microsoft Security Advisory CVE-2025-55315: .NET Security Feature Bypass Vulnerability。该公告的高级摘要写道:
ASP.NET Core中对HTTP请求的不一致解释(‘HTTP请求/响应走私’)允许授权攻击者通过网络绕过安全功能。
建议是“修补你所有的东西”,但真正的头条新闻是这个漏洞获得了CVSS 9.9分(满分10分),这听起来很糟糕!
.NET安全负责人Barry Dorrans(又名blowdart)在原问题的评论中解释了评分背后的理由:
这个漏洞启用了HTTP请求走私,仅就ASP.NET Core本身而言,其严重性远不会那么高,但我们不是这样评分的……相反,我们根据该漏洞可能对构建在ASP.NET之上的应用程序产生的影响来评分。请求走私允许攻击者将一个额外的请求隐藏在另一个请求中,而这个被隐藏的请求能做什么,具体取决于应用程序。被走私的请求可能导致你的应用程序代码:
- 以其他用户身份登录(权限提升)
- 发起内部请求(SSRF)
- 绕过CSRF检查
- 执行注入攻击 但我们不知道可能发生什么,因为这取决于你如何编写你的应用程序。
这一切听起来确实很可怕!😱 因此,你可以理解这个问题所引起的惊慌,尤其是考虑到官方在解释“你如何编写你的应用程序”的确切含义时有所犹豫。出于好奇,我决定深入研究,以真正理解这个漏洞,它可能如何影响你,以及“你如何编写你的应用程序”可能意味着什么。
请求走私是如何工作的?
在我们讨论ASP.NET Core中已修复的漏洞及其工作原理之前,我认为有必要了解被称为HTTP请求走私的这类攻击的基本背景。
HTTP请求走私是一种已知已久的安全攻击手段(根据维基百科,它于2005年首次被记录)。它从根本上源于两个不同的服务器处理一个HTTP请求时(例如,一个服务器和一个代理服务器),并且这两个服务器在处理“无效”HTTP请求时存在差异。
在所有HTTP请求走私案例中,攻击是通过创建一个看起来像两个HTTP请求粘在一起的无效(或有时只是模糊的)HTTP请求来实现的。
简而言之,攻击过程大致如下:
- 代理服务器接收到模糊的HTTP请求。
- 代理服务器将请求(未修改)转发到目标服务器。
- 服务器将模糊请求解释为发送到服务器的两个流水线式HTTP请求,并分别处理它们。
我认为用一个例子来理解这个问题最容易,所以下面的请求展示了2005年原始论文中的一个例子。
请注意,这不是CVE-2025-55315中请求走私漏洞的例子,它只是请求走私的一个代表性例子。
让我们想象攻击者发送一个看起来像这样的HTTP请求:
|
|
这个请求的重要特点是第一个请求中有两个Content-Length头(后面被走私的请求中还有一个),值不同:9或204。这是攻击的核心;HTTP代理和HTTP服务器对前两个头中哪一个的采用差异导致了漏洞。
让我们逐步看一下攻击是如何进行的:
- 攻击者发送上述HTTP请求。
- HTTP代理接收请求,注意到重复的
Content-Length头,并采用第二个头,即长度为204。这意味着请求的其余部分被视为消息体,就代理而言看起来没问题。 - HTTP代理将请求转发到目标服务器。
- 该服务器也注意到重复的
Content-Length头,但它采用了第一个头,长度为9。 - 服务器读取9个字节的正文(即
this=that)并将其视为整个请求。就服务器而言,整个(有效的)请求已被接收,它将剩余的数据视为一个全新的请求。 - 这意味着目标服务器看到一个全新的HTTP请求需要处理,即
POST /vuln_page.jsp,并将其视为一个新请求。
这就是问题的核心;代理看到一个请求,而目标服务器看到两个——第二个请求被“走私”过了代理,到达了服务器。
这里展示的请求走私技术,即使用多个
Content-Length头,并不是你通常看到的“典型”例子,但我在这里使用它是因为它在很多方面更容易理解。典型的请求走私攻击是发送一个Content-Length头和一个Transfer-Encoding: chunked头(后者将正文长度指定为正文本身的一部分)。和以前一样,请求走私攻击依赖于代理和目标服务器解释这些冲突头时的差异。
因此,正如你所见,请求走私使得向目标服务器发送一个中间代理服务器没有看到的秘密请求成为可能。在下一节中,我们将看看为什么这是一件坏事,以及它如何被利用。
攻击者如何利用请求走私?
表面上看,请求走私可能看起来没什么大不了的。服务器看到两个请求,那又怎样?你总是可以发送两个请求给服务器,对吧?
嗯,既是也不是。请求走私的问题实际上完全在于代理服务器和目标服务器之间的不匹配。由于这种不匹配,并且取决于目标应用程序的行为和期望,攻击者可以利用请求走私来:
- 在易受跨站脚本攻击的站点上,将恶意数据反射给其他用户。
- 用不良数据污染缓存。
- 从客户端请求中窃取身份验证凭据或其他数据。
- 调用不应公开访问的端点(因为代理会阻止外部访问它们)。
- 替换/覆盖由代理处理的身份验证控制。
- 在易受开放重定向攻击的站点上将用户重定向到恶意站点。
- 以及更多……
正如你所见,这些都是“糟糕”的™️,所以你多少能理解为什么给了9.9的评分!😱
也就是说,值得注意的是,并非所有这些攻击都对所有应用程序都有效。其中一些最容易理解的攻击形式是当代理不仅仅是对请求进行“简单”转发,而是以某种方式验证或增强请求时。
例如,如果你在服务器前面有一个负责处理TLS终止和使用证书进行客户端身份验证和识别的代理,那么请求走私可以用来绕过这些检查并插入你自己的身份信息。
作为这种攻击的一个例子,下面的HTTP请求演示了使用Content-Length和Transfer-Encoding请求走私攻击来向代理“隐藏”对/admin的请求,并插入恶意的X-SSL-CLIENT-CN头,该头通常由前端代理添加:
|
|
在这个例子中,服务器假设X-SSL-CLIENT-CN: administrator头是由代理添加的,因此服务器假设代理已经完成了所有必要的身份验证和授权。攻击者能够以完全不同的用户身份执行请求。
显然,每当你的前端代理执行某些功能时,请求走私就是一个大问题,但即使它本质上是一个简单的代理,请求走私仍然可以用来从其他用户的请求中窃取和泄露数据,即使被攻击的站点不易受跨站脚本或其他漏洞攻击。
在这些攻击中,仅仅拥有显示用户提供数据(即使是经过清理的)的功能,就足以窃取其他用户的凭据。所以像显示用户名或评论这样简单的事情可能就足够了。
这篇文章已经够长了,而且有太多不同的攻击方式,关于攻击利用的探讨就到此为止。如果你想了解更多可能性,以及简单的解释和攻击示例,我推荐PortSwigger关于利用请求走私的文档。
只有使用代理时才适用请求走私吗?
一般来说,当人们谈论请求走私时,他们通常会谈到有多个服务器的情况:典型的例子就是我迄今为止讨论的代理服务器和目标服务器。但别被骗了,这些问题和漏洞即使你没有严格使用代理,也可能适用。这个漏洞的关键特征是,两个“系统”之间存在混淆的机会,无论它们是否是完整的“服务器”。
这显然适用于代理服务器,但如果你的应用程序在做任何读取/操作/转发请求流的事情,或者在同一应用程序内部存在混淆的可能性,那么也可能适用于你的应用程序。
对于ASP.NET Core应用程序,如果你在处理HttpRequest.Body或HttpRequest.BodyReader或其他类似方法,那么即使你没有明确使用代理服务器,你也可能容易受到攻击。即使你不认为你的应用程序是代理或使用代理,如果你在做“类似代理”的事情,那么你可能会受到攻击。
换句话说,如果你直接在ASP.NET Core中读取、操作或转发请求流,而不是仅仅依赖内置的模型绑定,那么你可能面临请求走私攻击的风险。很难列举所有的攻击向量,因此你应该将任何这样做的代码都视为潜在的利用途径。
我们现在已经介绍了请求走私的一般工作原理以及如何被利用,现在是时候看看针对.NET CVE-2025-55315漏洞的特定请求走私版本了。
CVE-2025-55315中的请求走私是如何工作的?
正如我们所见,HTTP请求走私是一种通用技术,依赖于代理和服务器在解析HTTP请求时的差异。到目前为止,我已经展示了两种具体的变体:重复的Content-Length头,以及Content-Length/Transfer-Encoding混淆,但这并不是全部。这些方法还有其他变体也会导致请求走私。
CVE-2025-55315中的请求走私漏洞依赖于一种变体,据我所知,该变体由Jeppe Bonde Weikop于2025年6月在其博客上首次报告。这种变体依赖于Transfer-Encoding和分块扩展功能。
本节中的所有细节和图片都基于原始文章中的描述和示例。那篇文章非常棒,所以如果你想要比这里更详细的解释,你一定要读一读,然后你可以跳过我这里提供的简化版本。
为了理解这个漏洞,我们首先需要了解分块传输编码是如何工作的,以及什么是分块扩展。然后,我们将看看无效的换行符如何导致对请求解释的差异。最后,我们将看看这种解释差异如何为请求走私打开大门,以及ASP.NET Core如何修复了这个问题。
Transfer-Encoding: chunked 和 分块扩展
要理解漏洞,我们首先需要了解Transfer-Encoding: chunked是如何工作的,以及分块扩展如何使情况复杂化。
发送请求时,你可能并不总是预先知道要发送的请求有多大。举一个实际的例子:将一个.NET对象序列化为JSON到请求体中。确切知道序列化数据大小的唯一方法是实际进行序列化。所以你可以在写入请求之前将数据序列化到内存中,但如果数据非常大,可能会导致分配大数组的问题。
相反,Transfer-Encoding: chunked允许以多个“块”发送请求数据。你需要知道每个单独块的大小,但不需要知道数据的总大小或有多少个块。这非常适合序列化到一个小缓冲区,将该小缓冲区作为一个块发送,然后重用缓冲区序列化下一部分,直到整个对象序列化完毕。
就HTTP请求本身而言,每个块由一个头部和一个正文组成。头部由一个十六进制格式的字节数后跟\r\n(CRLF)换行符组成。块正文是指定数量的字节,后跟另一个\r\n。你可以有任意多个块,请求会一直被传递,直到你发送一个长度为0的块,表示请求结束。
例如,下面的HTTP POST显示了向一个端点发布一些JSON,但JSON是作为三个不同的块发送的:
|
|
我们很快就会看到,换行符非常重要,所以下图展示了与上述HTTP请求相同的内容,但包含了换行符:
|
|
这就是“正常”的分块传输编码,所以现在我们来看看分块扩展。
分块扩展是HTTP 1.1协议的一部分,它允许向单个块添加键值对形式的元数据。下面的例子展示了与之前相同的请求,但在第二个块中有一个分块扩展;foo=bar:
|
|
分块扩展由块头长度后的;表示,后跟一个或多个key=value形式的键值对。重要的是要理解分块扩展不是请求处理程序看到的数据的一部分;分块扩展只是关于单个块的元数据。
一句话总结:它们完全没用。😅 最接近的说法是,没有人关心分块扩展;客户端实现不发送它们,服务器直接忽略它们。
如果是这样,它们怎么会成为.NET中如此严重漏洞的原因呢?问题在于实现的忽略方式……
具有不正确换行符的无效分块扩展
一般来说,对于HTTP,客户端和服务器实现通常试图遵循“发送时要保守,接收时要宽容”的稳健性原则。不幸的是,正是这种宽容有时会给我们带来麻烦。毕竟,正是因为对同时包含Content-Length和Transfer-Encoding头的请求的宽容,才是原始请求走私攻击的根本原因。
注意:HTTP 1.1 RFC现在禁止转发这两个头,正是为了避免请求走私攻击。
但对于分块扩展,宽容性通常是服务器实现中无意中内置的。鉴于没有实现实际对分块扩展做任何事情,解析块头时处理它们的典型方法就是忽略它们。当解析到;时,常见的做法就是寻找行尾,并忽略中间的所有内容。
对于ASP.NET Core(修复之前),在块头中找到;时,Kestrel会“解析”扩展,但实际上,它会搜索回车符\r,然后检查后面的\n,跳过中间的所有内容,有点像这样(与原始代码相比非常简化):
|
|
ASP.NET Core中的实现并不特别;大多数服务器只是跳过字节,直到它们找到\r\n。关键问题是服务器如何搜索\r\n。如果它们看到一个单独的\r,或一个单独的\n,会发生什么?它们将其视为与\r\n相同吗?如果它们发现一个未配对的\r或\n,会抛出错误吗?还是忽略它并继续寻找\r\n?
这种模糊性正是CVE-2025-55315请求走私漏洞的核心。代理和服务器实现在处理块头中的单独\r或\n时的差异,为利用这种模糊性进行请求走私攻击创造了条件。
注意:根据RFC,实现者不得将
\r或\n视为块头的“有效”行终止符,并且\r或\n不允许出现在块头的其他位置,因此正确的实现必须拒绝在块头中包含这些单独换行符的请求。
为了完全清楚,下面的例子与之前的实现相同,但在第二个块的分块扩展中有一个无效的块头。分块扩展不是以\r\n结尾,而是以单个\n结尾:
|
|
这就是请求走私漏洞的根本原因,所以在下一节中,我们将看看如何利用这一点来制作恶意的HTTP请求。
利用无效分块扩展进行请求走私
与其他请求走私的例子一样,分块扩展方法依赖于代理解析请求的方式与后续服务器的差异。这种差异意味着代理看到一个请求,而目标请求看到两个请求,并允许我之前讨论过的所有相同的攻击。
如前所述,这些例子来自这篇优秀的博客文章,所以更多细节、攻击变体以及进一步利用漏洞的方法,请参阅那篇文章。
下面的例子展示了一个恶意的HTTP请求,它利用代理和目标服务器在处理换行符上的差异,向/admin端点走私一个请求。我们可以想象代理通常配置为自动拒绝访问/admin的请求,而服务器假设代理为我们处理了此事。
|
|
在这个例子中,攻击者通过发送2;\n创建一个格式错误的分块扩展块头。;确保代理和服务器都将该头视为分块扩展,但使用\n而不是\r\n导致了差异化的解析:
- 代理只看到一个请求:
- 它将
\n视为块头的“有效”行终止符。 - 然后将
xx视为块正文。 47是下一个块头。- 接下来的71个字节(47是十六进制,即十进制的71)被视为块正文。
- 最后是空的块结束块。
- 它将
- 服务器看到两个请求:
- 服务器忽略单独的
\n,一直跳到xx\r\n。 - 然后将
47视为块正文。 - 它看到一个结束块
0\r\n\r\n,认为请求结束。 - 剩余的数据被视为一个完全独立的请求,其正文仅包含一个空块。
- 服务器忽略单独的
这差不多是最简单的例子,但你可以基本上以我前面描述的所有方式来利用这种差异。具体对你的应用程序意味着什么很难说,但考虑到可能导致各种安全绕过、凭据窃取和注入攻击,很容易理解为什么这个漏洞获得了CVSS 9.9分。
我发现的非常有趣的一点是,查看其他语言(如Python的aiohttp和Ruby的puma服务器)中相同漏洞的安全公告。例如,在这两种情况下,该漏洞仅被评为中等严重性。在netty中,甚至被评为低严重性。据我所知,这些服务器本质上与ASP.NET Core存在相同方式的漏洞,所以这只是一个有趣的数据点,我认为这反映了微软真的希望确保这个问题得到应有的关注,并让客户修补他们的应用程序!
漏洞是如何修复的?
与大多数修复请求走私的方法一样,解决方案是停止对块头中单独换行符的处理方式采取宽容和/或模糊的态度。在ASP.NET Core中,修复该问题的PR通过明确检查任何换行符而不是仅仅寻找\r来实现。如果它发现一个换行符,并且它严格来说不是\r\n,那么Kestrel现在会抛出一个KestrelBadHttpRequestException并返回400响应。
我在这里提一下,有一个AppContext开关可以在修补应用程序后选择启用危险/易受攻击的解析行为,但请不要使用它,我不相信真的有一个好的(或安全的)理由这样做。😅
漏洞已在ASP.NET Core中修复,那么你应该怎么做?
你应该做什么?
显然,好消息是ASP.NET Core有修复。如原始问题所述,重要的是尽快更新到最新支持的ASP.NET Core版本。
目前没有宣布在野利用此请求走私漏洞的证据,但考虑到请求走私可能被利用的方式多种多样,我们真的能知道吗?🤔
这意味着你应该更新你的.NET 8、.NET 9或.NET 10版本:
| 易受攻击版本 | 最低已修补版本 |
|---|---|
| .NET 10 | 10.0.0-rc1 - 10.0.0-rc2 |
| .NET 9 | 9.0.0 - 9.0.9 |
| .NET 8 | 8.0.0 - 8.0.20 |
| .NET 10 | 10.0.0-rc1 - 10.0.0-rc2 | | .NET 9 | 9.0.0 - 9.0.9 | | .NET 8 | 8.0.0 - 8.0.20 |
- 已修补版本:
- .NET 10: 10.0.0-rc2
- .NET 9: 9.0.10
- .NET 8: 8.0.21
如果你在.NET Framework上使用ASP.NET Core 2.3,那么你需要更新你的Microsoft.AspNetCore.Server.Kestrel.Core版本:
- 易受攻击版本:
Microsoft.AspNetCore.Server.Kestrel.Core2.0.0 - 2.3.0 - 已修补版本: 2.3.6
如果你正在对应用程序进行自包含部署,则需要更新到已修补的版本,然后重新部署你的应用程序。
如果你使用的是旧版本的.NET Core呢?那么你无法打补丁…… HeroDevs为不受支持的.NET版本提供额外支持(并已确认他们将在.NET 6中修补此漏洞),但据我所知,这个漏洞基本上存在于所有版本的.NET Core中。我个人测试到.NET Core 3.0,我可以确认漏洞存在,并且你不会收到补丁。最好的办法是更新到受支持的.NET版本。
⚠️ 如果你正在使用<=.NET Core 3.0、.NET Core 3.1、.NET 5、.NET 6(除非得到HeroDevs支持)或.NET 7运行ASP.NET Core,那么你是易受攻击的,并且没有补丁。你应该尽快更新到受支持的.NET版本。讽刺的是,如果你困在旧的.NET Framework Web Forms或MVC应用程序中,你显然不易受攻击。
值得注意的是,如果你困在这些旧的框架版本上且无法升级,那么保护自己的最佳方法可能是确保在你的应用程序前面有一个已确认不易受攻击的代理(尽管显然你可能容易受到其他攻击😅)。例如,Azure App Services (AAS)确认,即使你尚未更新,在AAS中运行的应用程序也不再易受攻击,因为AAS使用的代理(本身是一个基于YARP的ASP.NET Core代理)已打补丁。通过在代理级别阻止请求,模糊的请求永远不会到达你的应用程序,因此你受到了保护。
不幸的是,目前尚不清楚如果你使用AAS以外的服务托管应用程序,你的处境如何。甚至IIS目前也尚未被确认为安全或易受攻击,但我在我的Windows 11电脑上进行了一些非官方测试,据我所知,它是易受攻击的。
注意:原始问题中的一些人试图使用
Content-Length/Transfer-Encoding版本的请求走私来测试IIS,这不适用于此;我们关心的是基于分块扩展的版本。
另一个有趣的点是,这只是HTTP/1.0和HTTP/1.1中的漏洞;它不是HTTP/2或HTTP/3中的漏洞。HTTP/2和HTTP/3不支持分块传输编码,而是使用不同的、更高效的二进制帧层进行数据流传输。因此,保护那些无法升级的应用程序的另一种方法可能是强制客户端只能使用HTTP/2或HTTP/3。但请注意,这很可能会破坏许多仍在使用HTTP/1.1的客户端!
你可以通过配置Kestrel端点来配置允许的HTTP协议。文档展示了各种方法。
如何知道你是否受到影响?
“最简单”的方法是检查你用来运行应用程序的.NET版本,使用dotnet --info并验证你正在使用一个已修补的版本。如果是,你就是安全的。这是知道你是安全的唯一“受支持”的方式,也是我推荐的方式。
据我所知,目前还没有通用的工具可以指向一个应用程序来找出它是否易受攻击,尽管编写一个应该是可能的。HeroDevs的团队将原始ASP.NET Core修复中的功能测试重新实现为一个针对多个ASP.NET Core版本编译的控制台应用程序。他们用它来确认未修补的.NET 8到.NET 10版本是易受攻击的,而已修补的版本则不是。他们还用它来验证.NET 6是易受攻击的,我调整它以确认至少到.NET Core 3.0的所有版本都是易受攻击的。
该存储库中的测试通过向ASP.NET Core发送一个包含分块扩展头中无效换行符的分块传输编码请求来工作。漏洞的识别依据是ASP.NET Core“挂起”,等待更多数据,直到最终超时。“已修复”的版本会立即抛出修复中包含的BadRequest异常。
我在网上看到一些对此测试的困惑;论点是“如果修复版和损坏版都抛出异常,那有什么关系”?然而,这不是测试的重点。Kestrel暂停等待更多数据的事实表明,一个被走私的HTTP请求本应已被执行。你可以在这个分块扩展博客或PortSwigger的网站上看到这如何被用来泄露数据或攻击其他用户。
我使用了类似的方法,通过向IIS发送相同的精心构造的HTTP请求并查看它是否挂起直到超时,来尝试理解IIS是否可能易受攻击:在我的IIS版本(10.0.26100.1882)上它确实如此:
|
|
那么这是否肯定意味着IIS易受攻击?不,不要相信我,我不是安全研究员😅 但在你听到其他消息之前,我会谨慎行事,假设IIS不会保护你免受分块扩展请求走私攻击。一般来说,我会对你基础设施中依赖的任何其他代理应用相同的规则。
最后再次提醒,尽管请求走私通常被描述和演示为在服务器前面使用代理,但不使用代理并不意味着你自动安全。如果你直接在ASP.NET Core中读取、操作或转发请求流,而不是仅仅依赖内置的模型绑定,那么你可能面临请求走私攻击的风险。最好谨慎行事,修补你的应用程序,并尽可能将操作请求的复杂性留给ASP.NET Core处理。
总的来说,我会确保订阅GitHub上的ASP.NET Core问题,因为很可能任何关于此问题的更多公告也会在那里报告。
总结
在本文中,我讨论了最近的ASP.NET Core漏洞:Microsoft Security Advisory CVE-2025-55315: .NET Security Feature Bypass Vulnerability。该公告警告一个基本上影响所有版本ASP.NET Core的请求走私漏洞。
我描述了请求走私的一般工作原理,使用一个简单的请求走私例子来说明HTTP解析的模糊性如何导致HTTP代理和HTTP服务器以不同方式处理同一个HTTP请求。这可能导致服务器看到两个请求,而代理只看到一个请求。
在逐步讲解了一个请求走私示例之后,我讨论了一些攻击者可以利用请求走私漏洞的方式。这包括向你的应用程序的其他用户反射恶意数据,从客户端请求中窃取身份验证凭据或其他数据,调用不应公开访问的端点,以及各种其他攻击。
接下来,我详细介绍了CVE-2025-55315中确定的特定请求走私漏洞。这利用了在使用分块传输编码发送请求时,解析分块扩展的模糊性。分块扩展通常被所有服务器忽略,但宽容的处理方式可能导致代理和服务器之间的处理差异,从而为请求走私提供了途径。
最后,我讲解了你应该采取的缓解步骤:修补你的应用程序。我描述了关于易受攻击或已修补代理服务器的现有信息,以及旧版本的ASP.NET Core将不会收到补丁,因此将保持易受攻击状态(再次向支持.NET 6的HeroDevs致敬)。如果你在AAS中运行,那么没问题,否则,你需要与你的代理提供商核实,以确定你是否易受攻击。