📄nginx 获取 XFF 的第一个 ip 和 “真正的” realip
2021-7-2
| 2023-4-9
0  |  0 分钟
type
status
date
slug
summary
tags
category
icon
password

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"';
  • 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; }

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
只有一条代理日志,即 $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
  • 第二条是代理日志,即 $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
  • 第二条是代理日志,即 $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;

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_” 就可以
 
技术
  • 中间件
  • nginx的try_files验证python2和python3共存使用 virtualenvwrapper-win管理环境
    目录