调试工具 / 环境

1
2
# 运行 http-headers-printer
docker run -d --restart=unless-stopped --name http-headers-printer -p 6130:6130 fangqk1991/http-headers-printer

入口 Nginx 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 443 ssl;
ssl_certificate ……;
ssl_certificate_key ……;
server_name *.fangcha.net;

location / {
proxy_read_timeout 300;
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header Host $host;
}
}

headers.Host

Kong Route preserve_host = true 相当于 Nginx proxy_set_header Host $host;,该值会影响后端应用接收的 HTTP Request Headers Host 值

1
curl https://headers-printer.fangcha.net/
1
2
3
4
5
6
7
8
9
10
11
## preserve_host = false
{
"host": "10.0.4.2:6130",
……
}

## preserve_host = true
{
"host": "headers-printer.staging.fangcha.net",
……
}

如何获得正确的客户端 IP ?

通常情况下,Nginx 转发配置如下

1
2
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  • 典型示意图: Client -> Nginx 1 -> Nginx 2 …… -> Nginx N -> App
  • App 得到的 X-Real-IP 总是 Nginx N 的前一个节点 IP 信息
  • App 得到的 X-Forwarded-For 理论上为 Client IP, Client 1 IP, ……, Nginx N IP
  • 若仅有一层 Nginx 反向代理,App 通过 X-Real-IPX-Forwarded-For 首项均可获得客户端 IP 地址
  • 若有超过一层 Nginx 转发,App 通过 X-Real-IP 不能得到原始的 Client IP,理论上通过 X-Forwarded-For 首项可以获得
  • 因此应用往往将 X-Forwarded-For 首项视为客户端 IP,进行数据记录或风控行为
1
curl https://headers-printer.fangcha.net/
1
2
3
4
5
{
"x-forwarded-for": "1.117.xx.xxx, 127.0.0.1",
"x-real-ip": "1.117.xx.xxx",
……
}

伪造 X-Forwarded-For

客户端发起请求时,可以伪造 X-Forwarded-For 进而达到攻击目的

1
curl -H "X-Forwarded-For: 10.0.0.1, 10.0.0.2" https://headers-printer.fangcha.net/
1
2
3
4
5
{
"x-forwarded-for": "10.0.0.1, 10.0.0.2, 1.117.xx.xxx, 127.0.0.1",
"x-real-ip": "1.117.xx.xxx",
……
}

解决方案 1

  • 最靠近客户端的 Nginx 1 X-Forwarded-For 设置为 $remote_addr 而非 $proxy_add_x_forwarded_for,可以避免来自客户端的 X-Forwarded-For 伪造
  • 在应用多区域部署的网络架构下,代理区 Nginx 1 ~ Nginx N 往往互相连通的网状结构而非单向链式结构,所有代理节点均可能成为「最靠近客户端」的入口节点
  • 因此上述方案实施的必要条件之一是「网关需要具备辨别来自客户端或其他网关节点转发的请求」,攻击者只要知晓用于辨别的特征,大多数情况下仍然可以对请求进行伪造
  • 一般而言直接与服务器建立连接的 IP 地址 $remote_addr 是不易伪造的,可维护一份「网关 IP 名单」,用于辨别客户端直接流量或网关转发流量

解决方案 2

  • 应用的 IP 判定策略调整,不再从左到右将 X-Forwarded-For 首项视为客户端 IP
  • 而是从右到左剔除「网关 IP」直至得到第一个 IP,将其视为客户端 IP