Aristotle
发布于 2023-12-11 / 9 阅读 / 0 评论 / 0 点赞

nginx 获取 XFF 的第一个 ip 和 “真正的” realip

nginx 获取 XFF 的第一个 ip 和 “真正的” realip

引用:https://kan.e1cent.top/article/bcdbcf93-fd38-46b7-a3b0-4f4a09ab3467

1. nginx 获取 XFF 的第一个 ip

由于 nginx 既可以做 web 服务器,又可以做代理,故标题获取 “XFF 的第一个 ip” 的主语既可以是代理本身也可以是代理下一跳的 web 服务器。实验环境:

  • ECS 搭建了 nginx,web 服务器和代理都有配置,ECS 之前有 SLB,ECS ip 为 192.168.20.111,slb ip 为 100.123 开头的 ip

    log_format  main  '$http_x_real_ip - $remote_addr - $remote_user [$time_local] "$http_host" "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

Plain text

Copy

  • nginx 配置

     location  /ip_svc {
         #nginx通过real_ip_header在本端直接获取真实ip
         proxy_pass http://81.69.166.140:8080/;
         # set user real ip to remote addr
         set_real_ip_from   0.0.0.0/0;
         real_ip_header     X-Forwarded-For;
         real_ip_recursive on;
     }
      location  /ip_proxy {
         #nginx通过proxy_set_header给下一跳传递真实ip
         #set $Real $proxy_add_x_forwarded_for取XFF第一个字段赋值X-real-ip
         proxy_pass http://192.168.20.111:90/;
         proxy_set_header            Host $host;
         set $Real $proxy_add_x_forwarded_for;
         if ( $Real ~ (\d+)\.(\d+)\.(\d+)\.(\d+),(.*) ){
           set $Real $1.$2.$3.$4;
         }
         proxy_set_header            X-real-ip $Real;
         proxy_set_header            X-Forwarded-For $proxy_add_x_forwarded_for;
     }
     location  /ip_all {
         #nginx在两边获取真实ip
         #端口90为web server
         proxy_pass http://192.168.20.111:90/;
         proxy_set_header            Host $host;
         set $Real $proxy_add_x_forwarded_for;
         if ( $Real ~ (\d+)\.(\d+)\.(\d+)\.(\d+),(.*) ){
           set $Real $1.$2.$3.$4;
         }
         proxy_set_header            X-real-ip $Real;
         proxy_set_header            X-Forwarded-For $proxy_add_x_forwarded_for;
         # set user real ip to remote addr
         set_real_ip_from   0.0.0.0/0;
         real_ip_header     X-Forwarded-For;
         real_ip_recursive on;
     }

Plain text

Copy

1.1 请求 slb_ip:port/ip_svc

自定义 XFF -H "X-Forwarded-For: 1.1.1.1, 192.168.1.0, 110.11.11.11"

- - 1.1.1.1 - - [09/Jun/2022:15:54:52 +0800]  192.168.20.91 192.168.20.91:88 "GET /ip_svc HTTP/1.1" 404 132 "-" "curl/7.29.0" "1.1.1.1, 192.168.1.0, 110.11.11.11, 192.168.20.94" 0.031 0.031

YAML

Copy

只有一条代理日志,即 $http_x_real_ip 为空 , $remote_addr 被 set_real_ip 替换成 XFF 第一个 ip

1.2 请求 slb_ip:port/ip_proxy

自定义 XFF -H "X-Forwarded-For: 1.1.1.1, 192.168.1.0, 110.11.11.11"

1.1.1.1 - 192.168.20.111 - - [09/Jun/2022:15:56:04 +0800]  192.168.20.91 192.168.20.91 "GET / HTTP/1.0" 200 166 "-" "curl/7.29.0" "1.1.1.1, 192.168.1.0, 110.11.11.11, 192.168.20.94, 100.123.241.22" 0.000 -
- - 100.123.241.22 - - [09/Jun/2022:15:56:04 +0800]  192.168.20.91 192.168.20.91:88 "GET /ip_proxy HTTP/1.1" 200 166 "-" "curl/7.29.0" "1.1.1.1, 192.168.1.0, 110.11.11.11, 192.168.20.94" 0.001 0.000

YAML

Copy

  • 第二条是代理日志,即 $http_x_real_ip 为空 , $remote_addr 是 slb 回源 ip

  • 第一条是 web 日志,即 $http_x_real_ip 为 proxy_set_header 传递的 X-real-ip(取的第一个 XFF), $remote_addr 是 nginx 作为代理的 ip

1.3 请求 slb_ip:port/ip_all

自定义 XFF -H "X-Forwarded-For: 1.1.1.1, 192.168.1.0, 110.11.11.11"

1.1.1.1 - 192.168.20.111 - - [09/Jun/2022:16:02:08 +0800]  192.168.20.91 192.168.20.91 "GET / HTTP/1.0" 200 167 "-" "curl/7.29.0" "1.1.1.1, 192.168.1.0, 110.11.11.11, 192.168.20.94, 100.123.240.161" 0.000 -
- - 1.1.1.1 - - [09/Jun/2022:16:02:08 +0800]  192.168.20.91 192.168.20.91:88 "GET /ip_all HTTP/1.1" 200 167 "-" "curl/7.29.0" "1.1.1.1, 192.168.1.0, 110.11.11.11, 192.168.20.94" 0.000 0.001

YAML

Copy

  • 第二条是代理日志,即 $http_x_real_ip 为空 , $remote_addr 被 set_real_ip 替换成 XFF 第一个 ip

  • 第一条是 web 日志,即 $http_x_real_ip 为 proxy_set_header 传递的 X-real-ip(取的第一个 XFF), $remote_addr 是 nginx 作为代理的 ip

2. set_real_ip 的延伸

    # set user real ip to remote addr
    #set_real_ip_from取可信IP,支持多个,也支持网段和IPv6
    #场景1:set_real_ip_from如果写0.0.0.0/0,那就是无脑从右往左取值,如果real_ip_recursive off,那只取到XFF的前一个ip;如果real_ip_recursive on,那直接取到最左边的ip
    #更常见的场景2:set_real_ip_from写多个ip段(如CDN、WAF),real_ip_recursive off或者不配置(默认关闭),那排除CDN和WAF之后的第一个ip即是realip
    #更推荐场景2的配置,因为XFF可以伪造,直接取XFF第一个ip可能是伪造的,而从右往左递归匹配,排除CDN和WAF之后的第一个ip基本就是realip(现在不能100%确定),但是这个ip是进入系统的第一个ip,即是第一个remote_addr,而remote_addr没办法伪造
    #CDN和WAF的回源IP段通常很多,不确定realip模块的性能如何
     set_real_ip_from   0.0.0.0/0;
     real_ip_header     X-Forwarded-For;
    #配合set_real_ip_from使用,默认关闭
     real_ip_recursive on;

Plain text

Copy

3.set_real_ip 配合阿里云 CDN、WAF

业务流向是 client->CDN->WAF->SLB->nginx,且 cdn 加速域名和 waf 绑定的域名不一致参考配置:

WAF 配置前面有 7 层代理,客户端 IP 判定方式选择自定义 header:ali-cdn-real-ip, ali-cdn-real-ip 是阿里 cdn 回源 header 字段,不是自定义WAF 启用流量标记,客户端 ip 选择自定义 header:ali-cdn-real-ip(可以换其他名字,但是不要和标准 header 字段冲突) ,nginx 配置 set_real_ip_from 0.0.0.0/0;real_ip_header ali-cdn-real-ip(和前面标记的自定义字段一致);

上述配置之后:

  • WAF 的 real_client_ip 优先选择 ali-cdn-real-ip 判断的 ip 作为 realip,real_client_ip 是 WAF 判断客户端 ip 的重要字段,如 blacklist经测试,real_client_ip 根据 ali-cdn-real-ip 判断 realip 的规则应该是 real_ip_recursive off,即只递归一次查询,因为伪造 XFF 测试,real_client_ip 始终取 XFF 最右边的 ip,为真实 ip

  • 第二条配置后,nginx 日志的 remote_addr 字段会被替换成通过 WAF 标记 header 字段取的 realip

  • 当然,如果有人绕过 CDN 直接访问 WAF (CDN 域名和源站域名不一致),这样会导致 ali-cdn-real-ip 为空,WAF 的 real_client_ip 会取默认值:XFF 第一个 ip,有点隐患。但如果 WAF 配置不选择前面有七层代理,那大量静态回源请求生成的 real_client_ip 全部是 cdn 的回源 ip,这又是一个问题。

4. nginx 直接取来自 cdn 或者 waf 的 realip 字段

不管是 waf 还是 cdn 都有自己判断的 realip 字段,WAF 会透传 cdn 回源的 header。nginx 可以考虑在 log 添加这个字段作为参考,比如 cdn 的 ali-cdn-real-ip,在 nginx log 配置应该写 $http_ali_cdn_real_ip,即横线变成下划线,再加上前缀 http_nginx 自定义 header 字段参考

Header lines sent to a client have the prefix “sent_http_”, for example, $sent_http_content_range.由于我的实验版本是 1.16,发现添加 prefix “sent_http_” 无效,直接改成 prefix “http_” 就可以