Skip to content

Instantly share code, notes, and snippets.

@Ivlyth
Last active June 30, 2022 09:47
Show Gist options
  • Select an option

  • Save Ivlyth/b589d543a568029fcbedbcb167639cd6 to your computer and use it in GitHub Desktop.

Select an option

Save Ivlyth/b589d543a568029fcbedbcb167639cd6 to your computer and use it in GitHub Desktop.
使用 nginx 统一卸载 https 抓取 http 明文流量

现在一般部署 web 服务都会选择使用 nginx 或者 apache 等 web 服务器作为前置,然后进行反向代理将请求转发至真实的后端。

使用前置服务器,可以帮我们完成 https 的加密功能,可以提供负载均衡,可以隐藏源站,甚至可以实现缓存,速率控制等统一的功能。

以 nginx 使用为例,一般大家都是配置很多不同的 server 段,并且每个 server 段中通过 proxy_pass 指令将请求转发至给定的后端。

如果安全部门想要审计流量,在这样的前提下很难操作:

  1. 如果在交换机 / 路由器上直接镜像流量分析, 则此时流量依然为加密流量
  2. 即使想在 nginx 和 源站之间的某个路由器 / 交换机抓包,但可能涉及到不同网络范围的后端,一般交换机并不支持将多个网络端口镜像至一个端口,甚至可能涉及不止一个交换机 / 路由器

这种情况下,我们可以尝试通过增加一个(组)通用的 nginx 服务器用来卸载 https 协议将其转换为 http 协议之后,再转发至真实的后端,然后我们可以在这一个(组)通用 nginx 服务器上来进行抓包(或者在其对应交换机上镜像其端口流量),

此时我们的镜像配置逻辑就变得固定起来,无需关注源站的分布与多少。

大致步骤

  1. 修改前置 nginx 的配置, 将所有的请求由之前的转发至相应的真实后端修改为转发至统一的 nginx 服务器
  2. 在统一的 nginx 服务器上,根据 Host 配置映射表,将请求映射至真实的源站
  3. 在统一的 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 文件可以验证,我们的实验是成功的: image

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment