curl -H 空格前缀导致代理模式下头部注入漏洞分析

本文详细分析了curl在使用--proxy参数时,-H选项前导空格导致的HTTP头部注入漏洞。通过复现步骤和代码示例展示了漏洞原理,探讨了潜在的安全影响和修复建议。

curl -H 空格前缀导致代理模式下头部注入漏洞

漏洞概述

在curl 8.11.0版本中,当使用--proxy参数时,-H选项如果包含前导空格,会导致HTTP头部被注入到前一个头部中,而不是作为新的独立头部发送。

环境信息

  • 操作系统:macOS Sequoia 15.1
  • curl版本:8.11.0 (aarch64-apple-darwin24.1.0)
  • 发布日期:2024-11-06

复现步骤

基本复现案例

1
curl -X GET "https://example.com" -H "Secure-Header: XYZ" -H " new-header-that-will-inject-to-previous-header: value" --proxy 127.0.0.1:8080

结果分析

当第一个头部本身包含空格时,行为会再次附加到前一个头部:

1
curl -X GET "https://example.com" -H " new-header-that-will-inject-to-previous-header: value" --proxy 127.0.0.1:8080

正常行为对比

不使用代理参数时,行为正常,头部会以新行发送:

1
curl -X GET "https://example.com" -H " new-header-that-will-inject-to-previous-header: value"

文件输入方式

该漏洞同样适用于-H @file.txt方式,当文件内容以空格开头时也会触发相同行为。

安全影响

虽然目前没有具体的利用场景,但由于curl的广泛使用,可以想象在某些情况下@file.txt或单个头部名称输入可能被攻击者控制,从而导致向服务器发送格式错误的请求。

示例攻击向量

1
curl -X GET "https://example.com" -H " new-header-that-will-inject-to-previous-header: value" -H @headers2.txt -H 'User-Agent:' -H 'Accept:' --proxy 127.0.0.1:8080

在这种情况下,头部会直接附加到Host头部,可能导致:

  • 不正确的日志记录
  • 根据程序的不同引入其他问题

技术细节

限制条件

  • 该漏洞仅在使用HTTPS请求时有效
  • 仅在使用--proxy参数时触发

头部格式要求

  • -H "[space]abc":不会被传递
  • -H "[space]abc:abc":会被传递并触发漏洞

解决方案建议

由于HTTP头部字段名不能包含空格,建议的处理方式是:

  1. 去除前导空格
  2. 始终将头部添加到新行

官方回应

curl开发团队认为这是文档记录的行为。curl手册页对-H选项的说明指出:

“curl传递您给出的逐字字符串,没有任何过滤或其他安全保护。这包括空格和控制字符。”

在HTTP/1中,可以通过代理传递"折叠"的头部进行连接,这是一个特性而非漏洞。

测试验证

通过自定义Python代理服务器进行测试,确认该行为确实存在:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python3
import socket
import threading

PROXY_HOST = '127.0.0.1'
PROXY_PORT = 8081

def handle_client(client_socket):
    request = client_socket.recv(4096)
    print("=== HTTP Request Received ===")
    print(request.decode('utf-8', errors='replace'))
    print("=============================")
    client_socket.close()

def start_proxy():
    proxy_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    proxy_socket.bind((PROXY_HOST, PROXY_PORT))
    proxy_socket.listen(5)
    print(f"Proxy server running on {PROXY_HOST}:{PROXY_PORT}...")
    
    while True:
        client_socket, addr = proxy_socket.accept()
        print(f"Connection received from {addr}")
        client_handler = threading.Thread(target=handle_client, args=(client_socket,))
        client_handler.start()

if __name__ == "__main__":
    try:
        start_proxy()
    except KeyboardInterrupt:
        print("\nShutting down the proxy server.")

漏洞状态

  • 报告状态:不适用(Not Applicable)
  • 严重性:中等(4 ~ 6.9)
  • CVE ID:无
  • 弱点类型:异常条件的不当检查或处理
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计