现在一般部署 web 服务都会选择使用 nginx 或者 apache 等 web 服务器作为前置,然后进行反向代理将请求转发至真实的后端。
使用前置服务器,可以帮我们完成 https 的加密功能,可以提供负载均衡,可以隐藏源站,甚至可以实现缓存,速率控制等统一的功能。
以 nginx 使用为例,一般大家都是配置很多不同的 server 段,并且每个 server 段中通过 proxy_pass 指令将请求转发至给定的后端。
如果安全部门想要审计流量,在这样的前提下很难操作:
- 如果在交换机 / 路由器上直接镜像流量分析, 则此时流量依然为加密流量
- 即使想在 nginx 和 源站之间的某个路由器 / 交换机抓包,但可能涉及到不同网络范围的后端,一般交换机并不支持将多个网络端口镜像至一个端口,甚至可能涉及不止一个交换机 / 路由器
这种情况下,我们可以尝试通过增加一个(组)通用的 nginx 服务器用来卸载 https 协议将其转换为 http 协议之后,再转发至真实的后端,然后我们可以在这一个(组)通用 nginx 服务器上来进行抓包(或者在其对应交换机上镜像其端口流量),
此时我们的镜像配置逻辑就变得固定起来,无需关注源站的分布与多少。
- 修改前置 nginx 的配置, 将所有的请求由之前的转发至相应的真实后端修改为转发至统一的 nginx 服务器
- 在统一的 nginx 服务器上,根据 Host 配置映射表,将请求映射至真实的源站
- 在统一的 nginx 服务器上抓包
我们使用两台服务器, 10.0.81.97 和 10.0.81.98,其中 97 用于模拟原先的前置 nginx, 而 98 就是我们新设立的统一 nginx 服务器,两者的 nginx 配置分别如下:
97 nginx config:
server {
listen 8443 ssl http2 default_server;
server_name _;
ssl_certificate /etc/nginx/ssl/nginx-selfsigned.crt;
ssl_certificate_key /etc/nginx/ssl/nginx-selfsigned.key;
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
location / {
# 一定要将原请求的真实 request Host header 转发过去
proxy_set_header Host $http_host;
# 通过自定义 request header 记录真实的客户端地址
proxy_set_header X-Real-Client $remote_addr;
# 然后将请求转发至统一的后端, 我们可以在这个统一后端配置 bpf "port 19999" 来抓取所有的 http 消息了
proxy_pass http://10.0.81.98:19999;
}
}
98 nginx config:
# 某域名的真实后端服务器列表信息
upstream upstream_api_test_com {
server 10.0.81.48:80;
}
upstream upstream_dpi_test_com {
server 10.0.81.89:8899;
}
map $http_host $real_upstream {
# 使用 hostnames 指令标记该匹配的源内容是域名, 可能包含通配符号
hostnames;
# 指定默认的映射值, 用来处理未知目标
default "127.0.0.1:40004";
# 定义几个真实的域名与 upstream 名称间的映射
api.tophant.com "upstream_api_test_com";
dpi.test.tophant.com "upstream_dpi_test_com";
}
# 通用代理 server
server {
listen 19999;
server_name _;
location / {
# 通用代理的配置可以非常简单, 只需要根据请求的 Host header 来映射得到真实的服务端地址然后将请求转发过去即可
# 这里我们使用 nginx 的 map 和 upstream 来组合配置, 这样子我们的真实后端可以是一个集群, 允许负载均衡
proxy_pass http://$real_upstream;
}
}
# 该 server 用于处理未配置的目标
server {
listen 40004;
server_name _;
location / {
return 404 "You can't be here";
}
}
然后我们在 10.0.81.98 服务器上使用如下 tcpdump 命令进行抓包,验证我们是否抓到了完整的 http 请求:
# 注意设置 BPF: port 19999
tcpdump -i eno1 -w 'nginx-https-offload.pcap' 'port 19999'
使用任意一台服务器,用 curl 命令访问 10.0.81.97:8443 端口,通过 -H 参数来伪造 Host header 进行测试 (正常我们是直接访问域名):
curl -v 'https://10.0.81.97:8443/' -k -H 'Host: api.test.com'
curl -v 'https://10.0.81.97:8443/' -k -H 'Host: dpi.test.com'
curl -v 'https://10.0.81.97:8443/' -k -H 'Host: 404.test.com'
通过 wireshark 查看抓到的 pcap 文件可以验证,我们的实验是成功的:

附:实验中抓到的 pcap 的在线查看地址: https://pcap.honeynet.org.my/v1/submission/view.php?id=6182.php