X-Forwarded-For 该追加还是覆盖?多层网关怎么还原真实 IP,信任范围放多大?Host 头用 $host 还是 $http_host?这篇用两个环回地址(127.0.0.1 与 ::1)模拟不同来源,把它们逐项量出来。
写法 | client 伪造了 XFF: 6.6.6.6 时后端看到 | client 没带 XFF 时后端看到 -------------------------------+-------------------------------+-------------------- 不写 proxy_set_header(默认) | 6.6.6.6 | (没有) $proxy_add_x_forwarded_for(追加) | 6.6.6.6, 127.0.0.1 | 127.0.0.1 $remote_addr(覆盖) | 127.0.0.1 | 127.0.0.1 置空(不转发) | (没有) | (没有) 默认:client 发什么就原样转给后端(伪造的 6.6.6.6 照单全收);$proxy_add_x_forwarded_for 在 client 带的值后面追加 client 的地址,最左边仍然是伪造的值;直接面对公网的最外层网关应该用 $remote_addr 覆盖,不要相信 client 自己带的 XFF
默认原样转发 client 带的值;追加保留伪造值在最左边;最外层网关应该覆盖。
L2 信任谁 | client(来源 ::1)走哪条路 | L2 还原出来的“真实 IP” | L2 收到的 X-Forwarded-For
----------------------+--------------------+-----------------+-----------------------
只信 127.0.0.1(L1) | 经过 L1 | ::1 | 6.6.6.6, ::1
| 直连 L2 | ::1 | 6.6.6.6
信 127.0.0.1 和 ::1 | 经过 L1 | 6.6.6.6 | 6.6.6.6, ::1
| 直连 L2 | 6.6.6.6 | 6.6.6.6
信所有人 0.0.0.0/0 与 ::/0 | 经过 L1 | 6.6.6.6 | 6.6.6.6, ::1
| 直连 L2 | 6.6.6.6 | 6.6.6.6
“真实 IP”应该是 ::1(client 的真实来源)。信任范围太大时,直连 L2 的 client 只要自己带一个 XFF,L2 就把它当成真实 IP(冒充成功);只信 L1 时,不是 L1 发来的连接,XFF 被忽略,真实 IP 就是连接的对端地址;经过 L1 时,real_ip_recursive on 从右往左跳过受信任的地址,取到第一个不受信任的(L1 追加的 client 地址)只信上一跳网关的地址,client 伪造的 XFF 才会被忽略;信任范围包含 client 就会被冒充。
写法 | Host: Shop.Test:8443 | Host: shop.test | HTTP/1.0,没有 Host -----------------------+----------------------+------------------+----------------- (a) 默认 | 'u' | 'u' | 'u' (b) $host | 'shop.test' | 'shop.test' | 'shop.test' (c) $http_host | 'Shop.Test:8443' | 'shop.test' | 'u' (d) $host:$server_port | 'shop.test:9165' | 'shop.test:9165' | 'shop.test:9165' $host 是小写、去掉端口的主机名,没带 Host 时取 server_name;$http_host 是 client 原样发来的(含端口、大小写),没带 Host 时它是空值,而 proxy_set_header 对空值的头不会发送,于是后端看到的是默认的 u;$host:$server_port 里的端口是 nginx 自己监听的端口;后端要拼绝对 URL 或做虚拟主机路由,要选对。nginx 自己按 Host 选 server 块时大小写不敏感
$host、$http_host 和默认值给后端的 Host 不一样,没带 Host 的请求尤其不同。