HTTP被动扫描代理的那些事
字数 1855 2025-08-18 11:38:56
HTTP被动扫描代理技术详解
1. HTTP代理基础概念
HTTP代理是一种中间人(MITM)技术,用于截获、修改和转发HTTP/HTTPS请求。安全测试工具如Burp Suite就是通过配置HTTP代理来实现请求拦截。
1.1 代理环境变量
HTTP_PROXY:用于HTTP请求的代理设置HTTPS_PROXY:用于HTTPS请求的代理设置- 环境变量值格式:
http://127.0.0.1:7777https://127.0.0.1:7777socks5://127.0.0.1:7777
注意:许多工具不支持https://类型的代理地址,会将其当作http://处理。
2. HTTP代理工作原理
2.1 普通HTTP代理模式
当客户端通过代理访问HTTP站点时,代理会收到如下格式的请求:
GET http://example.com/ HTTP/1.1
Host: example.com
Proxy-Connection: keep-alive
...
关键区别:GET后是完整URI而非路径,以便代理获取原始请求信息。
2.2 简易HTTP代理实现(Go语言示例)
package main
import (
"bufio"
"log"
"net"
"net/http"
)
var client = http.Client{}
func main() {
listener, err := net.Listen("tcp", "127.0.0.1:7777")
if err != nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal(err)
}
go handleConn(conn)
}
}
func handleConn(conn net.Conn) {
// 读取代理请求
req, err := http.ReadRequest(bufio.NewReader(conn))
if err != nil {
log.Println(err)
return
}
req.RequestURI = ""
// 发送请求获取响应
resp, err := client.Do(req)
if err != nil {
log.Println(err)
return
}
// 返回响应给客户端
_ = resp.Write(conn)
_ = conn.Close()
}
3. HTTPS与隧道代理
3.1 CONNECT方法
当客户端通过代理访问HTTPS站点时,会先发送CONNECT请求:
CONNECT baidu.com:443 HTTP/1.1
Host: baidu.com:443
User-Agent: curl/7.54.0
Proxy-Connection: Keep-Alive
3.2 隧道代理工作流程
- 客户端发送CONNECT请求到代理
- 代理尝试连接目标服务器
- 成功:返回200响应,连接控制权转交客户端
- 失败:返回502,连接中止
- 客户端收到200后,在该连接中进行TLS握手
- 握手成功后进行正常HTTP传输
3.3 隧道流量识别
隧道中可以传输任意数据,识别TLS流量的方法:
- TLS协议以0x16(ClientHello)开头
- 非TLS流量视为HTTP流量
伪代码实现:
b = conn.Read(1)
if b == "0x16" {
tlsHandShake(conn)
}
req = readRequest(conn)
handleReq(conn, req)
注意:读取的第一个字节需要"塞回去",否则后续操作会失败。
4. TLS中间人(MITM)技术
4.1 证书信任机制
- 根证书(RootCA) > 中间证书(Intermediates CA) > 终端用户证书(End-User Cert)
- 信任根证书后,其下级签发的证书都会被自动信任
- 系统/浏览器内置了权威机构的根证书
4.2 MITM实现原理
- 代理拥有被系统信任的根证书
- 针对每个HTTPS连接,代理动态签发对应域的证书
- 使用签发的证书与客户端完成TLS握手
- 剥开TLS层获取明文HTTP请求
注意:如果不需要中间人获取请求,可以不信任证书,仅做数据转发。
5. 代理实现细节
5.1 代理认证
代理认证使用Proxy-Authenticate和Proxy-Authorization头,类似于HTTP Basic Auth但加了Proxy-前缀。
5.2 单跳头(Hop-By-Hop header)处理
以下头应当被代理删除:
"Proxy-Authenticate", "Proxy-Authorization", "Connection",
"Keep-Alive", "Proxy-Connection", "Te", "Trailer",
"Transfer-Encoding", "Upgrade"
5.3 连接状态管理
- client->proxy:每次新建TCP连接,不复用
- proxy->server:使用连接池复用TCP连接
- 串联两部分连接(Go示例):
go io.Copy(conn1, conn2)
io.Copy(conn2, conn1)
5.4 特殊协议处理
- Websocket:直接放行,不解析(仅做数据转发)
- HTTP2:拒绝协议转换,强制使用HTTP
6. 现有实现的不完美之处
-
隧道模式下的协议识别:
- 仅以0x16判断TLS流量可能有误判
- 假设TLS层下一定是HTTP协议可能不准确
-
证书签发流程:
- 直接从CONNECT的HOST获取域名不够精确
- 更好的方法是从TLS握手中读取SNI(Server Name Indication)信息
7. 参考实现
- Python的MitmProxy:实现最全面科学
- Go语言目前缺乏类似MitmProxy的完善实现
8. 关键问题解答
-
http_proxy和https_proxy有什么区别?
- http_proxy用于HTTP请求,https_proxy用于HTTPS请求
- 这是约定俗成的规则,并非RFC强制规定
-
为什么需要信任证书才能扫描HTTPS站点?
- 代理需要动态签发目标站点证书进行中间人攻击
- 只有信任了根证书,签发的证书才会被客户端接受
-
代理HTTPS站点一定需要信任证书吗?
- 不需要,如果仅做隧道转发而不解析内容,可以不信任证书
-
隧道模式下如何区分TLS流量?
- 检查第一个字节是否为0x16(ClientHello)
- 注意读取后要将字节"塞回去"
-
如何处理Websocket和HTTP2流量?
- Websocket:直接放行,不解析
- HTTP2:拒绝协议转换,强制使用HTTP
-
是否应该复用连接以及如何复用?
- client->proxy:不复用
- proxy->server:使用连接池复用
- 两部分连接分别管理状态后串联