前言
最近工作需要部署http/https的代理,所以用squid部署了一下,重新回顾了一下http和https的代理知识。
HTTP代理
根据《HTTP 权威指南》如图:
这种情况下,对访问服务器而言,它会把代理当做客户端,完全察觉不到真正客户端的存在,完全隐藏了客户端的IP,当然代理也可以修改Http头部,X-Forwarded-IP头部告诉服务器真正的客户端IP。
代理可以用两种方式,一种是代码实现,一种用现成的代理软件如squid,本文是用squid,配置文件如下(这里不详细介绍squid的使用,有需要单独另开来讲squid)。然后启动squid起来
代码实现
javascript
var http = require('http');
var net = require('net');
var url = require('url');
function request(cReq, cRes) {
var u = url.parse(cReq.url);
var options = {
hostname : u.hostname,
port : u.port || 80,
path : u.path,
method : cReq.method,
headers : cReq.headers
};
var pReq = http.request(options, function(pRes) {
cRes.writeHead(pRes.statusCode, pRes.headers);
pRes.pipe(cRes);
}).on('error', function(e) {
cRes.end();
});
cReq.pipe(pReq);
}
http.createServer().on('request', request).listen(3128, '0.0.0.0');
squid配置
# Squid normally listens to port 3128
http_port 3128
然后客户端使用curl命令通过代理访问百度(注意这里访问百度用的http协议而非https)
curl -x http://43.129.198.37:3128 http://www.baidu.com
可以看到这时候访问成功了, 分析报文(报文分别是在客户端和服务端这边抓取的)由于是http,所以报文都是明文发送的,分别可以看到客户端和代理TCP的三次握手,代理和服务器TCP三次握手,以及http报文。
但是上述的过程能不能代理https的流量呢,答案是代码形式的不行,squid的可以,这就涉及到第二个内容http的隧道代理
HTTP隧道代理-代理https流量
也就是上述squid配置不变,把命令改成:
curl -x http://43.129.198.37:3128 https://www.baidu.com 把百度协议改成https它依然能够成功,因为squid是有http的隧道代理的。
还是抄《HTTP 权威指南》图,首先客户端会发送connect请求将需要访问域名发送给proxy,proxy会回应http connect established就绪报文,并且proxy和server建立了TCP通道,后续proxy会进行盲转发client和server之间报文
客户端到proxy之间报文:
proxy和server之间的报文:
直接上报文讲解,可以 看到在客户端和proxy之间发送connect请求,然后proxy回了connection established,后续就是TLS的通信过程。proxy和server会创建一个TCP通道,然后后续client发送给server的TLS通信过程,proxy只是进行盲转发,如何验证这点?请看如图:
把TLS第一个Client hello报文拿出来分别对比,可以看到客户端到proxy,proxy到server的client hello的随机值Random都是一样的,证明proxy只是进行盲转发。
https代理
很多时候在讨论代理,因为发现很多人都混淆了https代理和代理https流量。如上面http隧道代理,它能支持客户端访问服务器,也就是说它能代理https流量,但它是http代理,因为站在客户端角度,它配置的就是http代理,如上述curl命令就是客户端:
-x http://43.129.198.37:3128,填的协议就是http
再比如switchyOmega配置也是配置http,因为本质上客户端和proxy之间是http connect明文交互的。
所以https代理本质上指的就是,客户端和proxy之间交互是加密的,也就是上述的http connect是加密的,客户端配置的代理协议是https。
squid配置文件修改:
javascript
https_port 443 cert=./myCA.pem
那么curl命令就变为:
javascript
SSLKEYLOGFILE=./ssl-key.log curl -x https://43.129.198.37:443 https://www.baidu.com --proxy-cacert ./test2.pem
其中myCA.pem里面包含公私钥,curl命令的test.pem只包含公钥,这里加上了一个环境变量SSLKEYLOGFILE是用于wireshark进行解密的。
还是一样抓取client-proxy的流量,以及proxy到server的流量
第一份报文是解密后的数据,可以看到客户端和proxy之间先进行了TLSv1.3协商,协商完成后,加密了http connect数据,以及返回connection established
第二份报文是proxy和服务器的协商是TLSv1.2进行协商的,但是其实这时候proxy依然只是和server进行tcp连接进行盲转发,还是客户端和服务器的tls协商,也就是说客户端和proxy先进行TLSv1.3协商,然后客户端再和服务器再进行TLS1.2协商,这中间TLSv1.3 加密了客户端与服务器的TLSv1.2协商过程。
要验证也很简单跟上述一样找client hello报文的随机数:
Squid-SSL-Bump模式
这种模式下,proxy是一种中间人的角色Man-in-middle,能够对客户端到服务器的加密信息进行解密。
squid的配置
javascript
http_port 3128 ssl-bump \
cert=/etc/squid/ssl_cert/myCA.pem \
generate-host-certificates=on dynamic_cert_mem_cache_size=4MB
# For squid 3.5.x
sslcrtd_program /usr/local/squid/libexec/ssl_crtd -s /var/lib/ssl_db -M 4MB
# For squid 4.x
# sslcrtd_program /usr/local/squid/libexec/security_file_certgen -s /var/lib/ssl_db -M 4MB
acl step1 at_step SslBump1
ssl_bump peek step1
ssl_bump bump all
未完待续
参考文献:
Intercept HTTPS CONNECT messages with SSL-Bump | Squid Web Cache wiki (squid-cache.org)