从限流谈到伪造 IP(nginx remote_addr)

2019-03-11 14:01:30 +08:00
 ChristopherWu

来自我的公众号 『 YongHao 写东西的 Cache 』 打个小广告,还是希望写的东西有人看🙊

分享一下见解,权当抛砖引玉


从限流谈到伪造 IP ( nginx remote_addr )

remote_addr

很多流量大的网站会限流,比如一秒 1000 次访问即视为非法,会阻止 10 分钟的访问。

通常简单的做法,就是通过 nginx 时,nginx 设置

    proxy_set_header X-Real-IP $remote_addr;

nginx 的 $remote_addr代表客户端的访问 ip,把它设到 http 请求的头部 X-Real-IP ;然后程序取出并存入数据库,统计访问次数。

remote_addr 基本上不能被伪造,因为是直接从 TCP 连接信息中获取的,也就是 netstatForeign Address那栏。

你想想, 客户端 A 与 B 服务器建立 TCP 连接,是不是 B 肯定知道 A 的公网地址是什么呢,除非客户端 A 是经过了一个代理服务器 Z, 那么就是 A -> Z -> B, 服务器 B 拿到的只能是 Z 的 ip 地址了,但这不意味就是伪造 ip,限流依然有效。

nginx 转发

上述应对外网访问,没有任何问题。假如公司内部需要测试,不停的访问服务器上的程序时,并且经过负载均衡或者 nginx 转发时,也就是 client -> nginx1 -> nginx2 -> server, remote_addr 就变成了 nginx2 的内网地址了。

因此,需要在 nginx1处,e client 的 remote_addr, 再传给 nginx2,server 再取出。

示例:

   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

$proxy_add_x_forwarded_for$remote_addr 加到 X-Forwarded-For 头部后面;最后设在 my_ips

如果是需要做 ip 统计,地理信息获取,天气定位等,需要常用的另一个 http 头部, X-Forwarded-For 来做处理。通过名字就知道,X-Forwarded-For 是一个 HTTP 扩展头部。HTTP/1.1 ( RFC 2616 )协议并没有对它的定义,它最开始是由 Squid 这个缓存代理软件引入,用来表示 HTTP 请求端真实 IP。如今它已经成为事实上的标准,被各大 HTTP 代理、负载均衡等转发服务广泛使用,并被写入 RFC 7239( Forwarded HTTP Extension )标准之中。1

然后 my_ips 就代表了请求从 client 到 server 的完整 ip 路径, 只要由后往前推,直到 找到 外网的 ip,就证明这就是 client 的真正 ip。

     bb_real_ip = request.environ.get('my_ips')
     bb_real_ip = bb_real_ip.replace(" ", "").split(',')
     for ip in reversed(bb_real_ip):
         if not is_private(ip):
             return ip

     # 不可能出现这种情况, 除非 LBS/nginx 没有设$proxy_add_x_forwarded_for
     return request.environ.get('REMOTE_ADDR')

这样取到的也是真实的 ip 地址,但有一个问题,假如 client 跟其他很多 client 通过同一个出口出来,共享一个外网 ip,那么如何获取它的 ip 呢?

这个情况下,限流一样可以生效,最多就是稍微误杀下无辜,影响不大。

伪造 ip (也就是 remote_addr )

那么,有办法伪造 remote_addr吗? 其实本质就是,TCP 连接中,有办法伪造 ip 信息吗?

请看如何用 hping3 工具发出伪装 ip 的包到 google.com

$ sudo apt-get install hping3
$ sudo hping3 --icmp --spoof 6.6.6.6 baidu.com
HPING baidu.com (eth0 220.181.57.216): icmp mode set, 28 headers + 0 data bytes

另一个控制台抓 icmp 包:

$ sudo tcpdump -i eth0 'icmp'
21:24:58.562844 IP 6.6.6.6 > 220.181.57.216: ICMP echo request, id 11035, seq 5120, length 8

可以看到, 我们成功的伪装成 6.6.6.6 并向 baidu.com 发出了 ping (也就是 ICMP 包),不过由于我们的 IP 实质上并不是6.6.6.6,所以收不到 baidu.com 发往 它的 ICMP 包。

模拟的原理是,自己重新实现系统的 tcp ( ICMP )协议栈,然后 自己改变自己的 ip。

值得一提的八卦是,hping 的作者是 Salvatore Sanfilippo,同时他也是 redis 的作者。

试想一下,可以通过这个办法来做借刀杀人——伪造一个 ip (如4.4.4.4),大量发包给第三方(如 Google ),然后第三方返回 TCP reset 或者 ICMP unreachable 给 你伪造的 ip(4.4.4.4), 这样就可以借 Google 来 ddos 4.4.4.4了。2

但是现在运营商的路由器都部署了 uRPF,可以根据你发过来的源 ip 检测是否在路由表中,不在就拒绝掉此请求。

7963 次点击
所在节点    推广
30 条回复
ChristopherWu
2019-03-11 16:20:14 +08:00
@ryd994

关于`proxy_add_x_forwarded_for`, 你怎么伪造都好啊,与我无关,我取的只是最后的 `$remote_addr`,你与前端建立连接时,`$remote_addr`就几乎不可能伪造。

> $proxy_add_x_forwarded_for
> the “ X-Forwarded-For ” client request header field with the $remote_addr variable appended to it, separated by a comma.
请看 http://nginx.org/en/docs/http/ngx_http_proxy_module.html


关于攻击思路,请看 https://www.wikiwand.com/en/Denial-of-service_attack#/Backscatter,写的时候忘记放进去文章了
ChristopherWu
2019-03-11 16:21:55 +08:00
ryd994
2019-03-11 17:31:03 +08:00
@ChristopherWu
“ the “ X-Forwarded-For ” client request header field with the $remote_addr variable appended to it, separated by a comma.“
这就是我说的拼接。正确做法是在前端 proxy_set_header X-Forwarded-For $remote_addr; 在后端 set_real_ip_from。
按你的做法,到应用内再处理,不仅开销大,而且 nginx 的 access_log 就完全没意义了。Nginx 明明做好的你不用,还要自己造轮子。

"backscatter is a side-effect of a spoofed denial-of-service attack ”
你英文不及格。这是副作用而不是攻击手段和主要目的。用你的例子来讲,被攻击的是 Google 的服务器,只不过 Google 会以为是另一个 IP 在 syn flood 它。但是实际上都是有硬件清洗的。硬件先和你握手,三次握手成功之后再去和服务器握手。syn flood 很难完成三次握手。除非你破解了对方的 seq 生成随机算法。对于没有足够熵源的服务器来说,这种攻击从协议上来讲是有机会的。后来的 CPU 都有熵发生器了,这种攻击也就彻底不可行了。

写安全类文章前,不如自己搭个服务器,去 hostloc 挑衅一下,看看自己是怎么死的,再教别人。恕我直言,我认为你没有运维过一个公开服务的 nginx http 反代,很可能连服务器管理的经验也没有。
ChristopherWu
2019-03-11 18:03:49 +08:00
@ryd994 谢谢你的回复,非常有帮助。
我确实之前不知道`set_real_ip_from`这个模块,我稍候再想想怎么改对目前的程序。

> 恕我直言,我认为你没有运维过一个公开服务的 nginx http 反代,很可能连服务器管理的经验也没有。
实不相瞒,我有。。而且就是这样子做的。。

另外,写文章就是总结下自己学到的东西啊,我不可能知道很多对的事情应该怎么做,身边说不定也没有人知道,所以像现在贴上来,不就收到了你的建议了吗 - = -
zeraba
2019-03-11 19:04:20 +08:00
就和 ryd994 说的一样,多层其实设置好 header 后用 realip 模块可以搞定,另外,针对同一个出口的策略也需要调整了,可以自己生成 id 作为用户识别,毕竟移动网络下,这种情况很常见,还有可以深入写下 ng 多层代理后的 header 情况
ChristopherWu
2019-03-11 19:24:58 +08:00
@zeraba
已感谢。 请问『针对同一个出口的策略也需要调整了,可以自己生成 id 作为用户识别』可以细讲一下吗?

『有可以深入写下 ng 多层代理后的 header 情况』这个是指啥意思呢?有哪些 header 需要关注?
zeraba
2019-03-11 20:51:31 +08:00
@ChristopherWu 你现在限流是针对 ip 其实可以通过 cookie lua 可以 set cookie 如果项目不是基于 cookie 比如小程序之类的 可以在 url 设置某个参数标识用户 再限流,至于多层,举个例子,X-Forwarded-For 这个变量就可以展开,一层是什么情况,内网多层是什么情况,如何排除中间的 ip 其他变量当然也可以深入,可以拓宽一点理解整个 proxy 模块
msg7086
2019-03-12 01:04:56 +08:00
我刚打开的时候就觉得这写得都什么玩意儿,谈 nginx 不谈 real_ip 那还谈什么。9012 年了还谈 forwarded-for 伪造。
翻了下回复,看来我不是第一个这么觉得的。

> 实不相瞒,我有。。而且就是这样子做的。。
建议找个懂的来做运维。
抱歉,我说话比较冲。

如果自己不确定,那么就开贴直接问。把错误的知识写成科普性文章是很不好的行为。
freaking
2019-03-12 09:07:54 +08:00
好喷
Paradisiaercy
2019-03-12 09:38:03 +08:00
以网络安全角度来看,不知道说了什么。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://tanronggui.xyz/t/543304

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX