发布于 9 小时前
发布于 9 小时前
风熙.
更新于 5 小时前
0
0
不想看长文可以直接翻到最后看结论
经常有大哥遇到套了 CDN 后,IP 获取不正确的问题,包括我的好几个客户,所以发这个帖子来解答一下
X-Forwarded-ForPS: 本段借(抄)鉴(袭)一下( 雷池 WAF 如何配置才能正确获取到源 IP)
X-Forwarded-For 是一个相对通用的 HTTP 请求头。
HTTP 流量在经过代理时,由于网络连接被截胡,服务器无法得知真正的客户端 IP。这时代理设备会给当前的流量加上一个 X-Forwarded-For 头,里面的内容就是连接这个代理的客户端 IP。
下面这个例子中 HTTP 代理通过 X-Forwarded-For 头告诉服务器,真正的客户端地址是 1.2.3.4
GET / HTTP/1.1
Host: demo.waf-ce.chaitin.cn
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36
X-Forwarded-For: 1.2.3.4
X-Forwarded-For 实际上是一个链式结构。如果流量经过了多层代理设备,X-Forwarded-For 会记录途径的所有 IP。
下面这个例子中 HTTP 代理通过 X-Forwarded-For 头告诉服务器,流量经过了三层代理,真正的客户端地址是 1.2.3.4,第一层代理的是 11.12.13.14,第二层代理的地址是 21.22.23.24,第三次代理的地址可以通过 Socket 连接直接来获取。
GET / HTTP/1.1
Host: demo.waf-ce.chaitin.cn
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36
X-Forwarded-For: 1.2.3.4, 11.12.13.14, 21.22.23.24
IP-Forwarded-For 头靠谱么在代理设备和代理链路可信的情况下 IP-Forwarded-For 头传递的内容是很靠谱的,可以放心的试用。
但是呢,如果代理设备不可信,那么攻击者会通过伪造 IP-Forwarded-For 头的办法来实现伪造源 IP。
假设你是小李,你的IP地址是1.2.3.4,你要去访问长亭的网站,那么你的浏览器会向长亭的服务器(CDN)发送这样的报头
GET / HTTP/1.1
Host: demo.waf-ce.chaitin.cn
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36
长亭的CDN收到小李的请求后,判定可以放行,长亭的CDN会向长亭的源站服务器发送这样的报头
GET / HTTP/1.1
Host: demo.waf-ce.chaitin.cn
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36
X-Forwarded-For: 1.2.3.4
我们可以看到,CDN在原来的请求基础上,往报头里面添加了X-Forwarded-For这个报头,里面包含了小李的IP。在流量到达源站雷池后,由于源站前面只有一层代理,即CDN,即客户 → CDN → 源站(雷池),客户和源站之间只有一层反代,因此在源站雷池的IP获取设置里,设置从 X-Forwarded-For 中获取上一级代理的地址,这样就可以正确读的到客户端IP。
那么,如果中间不止一层,还有一层nginx反代,比如客户→ CDN → Nginx反向代理 → 雷池呢?
假设你是小李,你的IP地址是1.2.3.4,你又要去访问长亭的网站了,但是这次你突发奇想想搞点事情,如果我伪造X-Forwarded-For报头,是不是可以瞒天过海骗过雷池呢?于是你说干就干
GET / HTTP/1.1
Host: demo.waf-ce.chaitin.cn
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36
X-Forwarded-For: 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1
于是小李在访问的时候故意添加了X-Forwarded-For报头,里面装着五个白名单ip,那么小李到底能够顺利骗过雷池吗?请继续往下看
流量到达CDN后,CDN获得了访客小李的IP,由于XFF请求头是链式结构,所以CDN在原先的XFF报头后面添加了小李的IP,然后转发到Nginx
GET / HTTP/1.1
Host: demo.waf-ce.chaitin.cn
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36
X-Forwarded-For: 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 1.2.3.4
注意,1.2.3.4是我们故事中小李的IP
我们假设直接访问Nginx反向代理的CDN服务器的IP是4.3.2.1,那么Nginx在接到请求后,会将原来的XFF后面加上CDN的IP,然后转发给雷池
GET / HTTP/1.1
Host: demo.waf-ce.chaitin.cn
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36
X-Forwarded-For: 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 127.0.0.1, 1.2.3.4, 4.3.2.1
这就是在这个示例中雷池最终收到的请求头,我们可以看到,小李的iP地址是XFF链的倒数第二个,那么雷池会不会被小李伪造的XFF骗到呢?
由于是客户 → CDN → Nginx反向代理 → 雷池,客户到雷池中间存在两层反代,所以雷池工程师设置了从 X-Forwarded-For 中获取上上一级代理的地址
数呀数呀数一数,数到一个小朋友~,让我们数一数,上上一级的IP地址是多少
4.3.2.1,是CDN访问Nginx时的IP1.2.3.4,是小李访问长亭CDN的IP,耶!雷池就这样数到了小李的IP,小李的计划就这么被挫败了!
大多数应用情况下,我们只需要数客户到雷池之间有几层代理,比如在客户 → CDN → Nginx反向代理 → 雷池这种环境下,我们可以看到,客户到雷池之间有两层代理
X-Forwarded-For 中获取上一级代理的地址:在流量到达雷池之前还有一层代理设备(如 Nginx,CDN 等)时可选用X-Forwarded-For 中获取上上一级代理的地址:在流量到达雷池之前还有两层代理设备(如 Nginx,CDN 等)时可选用X-Forwarded-For 中获取上上上一级代理的地址:在流量到达雷池之前还有三层代理设备(如 Nginx,CDN 等)时可选用所以在这个环境下我们选用从 X-Forwarded-For 中获取上上一级代理的地址
但是如果有些中间反代(比如CDN之类的)会把自己的IP放进去,那么这一层代理就要按两层代理来计算(其实这是不符合规范的,但是咱们只能配合)
我们可以在部署完后,加个本地DNS host来进行模拟攻击,然后看雷池端的日志
以 https://chaitin.com 为例,然后尝试访问来模拟黑客攻击。
用你的网站地址替换下方的
https://chaitin.com/
https://chaitin.com/?id=1+and+1=2+union+select+1https://chaitin.com/?id=<img+src=x+onerror=alert()>https://chaitin.com/?id=../../../../etc/passwdhttps://chaitin.com/?id=phpinfo();system('id')不出意外的话,这些攻击都将被雷池拦截,如果没有被拦截,说明你的流量根本没经过雷池或者雷池开了观察模式,请自行检查。
拦截之后,请你回到雷池攻击防护——攻击日志里面看报文原文,看看是不是正确获取到了你的IP,看看最终雷池收到的请求头长啥样,前面有几个代理等等。
我们可以直接不使用XFF头,具体操作如下:
在最外端(最外层接受客户访问流量的那端)配置一个自定义请求头,这个请求头里面装着客户IP,然后在之后的代理链路中不对这个头进行任何操作,直到源站直接使用这个请求头进行读取
比如我在CDN中配置将客户的IP放到一个只有我自己知道的请求头,比如X-QwQ-IP,源站直接读取这个头就行。
然后再高级一点,我们可以在CDN中再加一个只有自己知道的请求头和内容,源站设置如果访客没有携带这个请求头直接拒绝,相当于一个账号密码的作用。
2026新年快乐!