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_” 就可以