一次穿透iptables防火墙的UDP攻击报文真实案例分析
![]() | ![]() | |
![]() | 一次穿透 iptables 防火墙的 UDP 攻击报文真实案例分析,揭示了一些在使用 iptables 时鲜为人知的安全隐患,希望能给大家带来帮助! 这类攻击确实少见,也是一次好的排错过程,所以转载以总结下来.
现象 一次偶然的机会,发现一台服务器出现了极不正常的情况:cpu 消耗巨大 通过用 top 观察,发现 snmpd 进程持续走高 图片:20130111120832842.png
抓包 重启 snmpd 进程,在正常了几分钟后突然又重现异常,此时感觉是有特殊数据包进来造成的。 为了验证猜测,对 UDP/161 端口进行了抓包,发现了些端倪。 图片:s_nopic.gif 通过抓包发现,原来真的有攻击,从抓包看,攻击是突发性质的,当有攻击时: 频率:≈ 10Hz 周期:≈ 0.1s 类型:请求 snmpd 的根信息 "Linux C" 原理:通过请求比较高级别的根内容,使系统处于高负载状态;由于频率较高,高负载状态很难消除。 恶意性:为了突破某些无状态防火墙,来源端口 sport 特意也是用了与 snmpd 服务相同的 UDP/161,很阴险。
规则检查 不过奇怪的问题来了,我的防火墙是基于状态检测的,可以轻易区分进来的报文是主动请求进来的,还是我主动发出后的回包。 既然这样,为何 snmpd 报文还是会被系统收到并处理呢?是不是 iptables 规则使用存在问题? 经查看,规则很简单,大体如下: iptables -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A INPUT -p udp --dport 161 -s IP1 -j ACCEPT iptables -A INPUT -p udp --dport 161 -s IP2 -j ACCEPT iptables -A INPUT -p udp --dport 161 -s IP3 -j ACCEPT iptables -A INPUT -j DROP 通过观察,未发现异常,针对 snmpd 报文,系统只允许了 3 个特殊 IP 访问,那么就奇怪了,这个攻击报文是怎么进来的呢?
conntrack 特性分析:UDP 与 TCP 的差异 众所周知,iptables 只是 userspace 的 controller,真正的主体是 netfilter,而 conntrack 则是 netfilter 的一个重要组成部分。 conntrack 的核心价值,就是为 Linux 实现了基于状态的防火墙。 在 Linux 中,当启用了 conntrack 后,即使是 UDP 报文,也是有“新建连接”(NEW)和“已连接”(ESTABLISHED)概念的。 不过与 TCP 不同的是,TCP 是有三次握手的,通过分析三次握手时数据报文头部的合规性可以知道当前数据是否有效。 但是,UDP 则不同,在 Linux 中,只有“去”和“回”两个概念,当系统第一次看到 UDP 包,视为 NEW,看到该包的回包后,视为 ESTABLISHED。
推理 知道了这个,大概可以猜出问题所在了,一定和 conntrack 有关。 猜测失控过程: 1、系统未启动 conntrack 时,已经有攻击报文 request 进来了 2、当系统启动 conntrack 机制时,恰好有系统回 response 包,被系统 conntrack 当做了主动发出数据,是 NEW 状态 3、系统的 iptables 规则最后有阻断功能,这个包虽然被拦掉 但后续攻击报文用同样的 SIP/SPORT 访问同样的 DIP/DPORT,导致 hash 后认为是同一条连接 此次的攻击报文被无当做上一次 NEW 状态的回包,此次该连接变成了 ESTABLISHED 状态 4、今后所有 SIP/SPORT 到 DIP/DPORT 的数据均通行无阻,导致最开始看到的场面
验证 为了证明推理是正确的,刻意写了一个 shell,在第一行 -m state --state RELATED,ESTABLISHED -j ACCEPT 后加入 sleep 以增加重现概率 实验表明,推理完全正确,UDP 攻击报文的回包被当做了 NEW,而真正的攻击则成了 ESTABLISHED。 第一句的位置,也是导致问题出现的主要原因。
尝试解决 为了杜绝这种情况的发生,思考,将 state 匹配放到最后是否可行? 答案是“不可以”,因为一样可能发生前面说的时间差导致的状态误判情况发生。 怎样做才是最稳妥的办法呢? 想了个办法: 1、先 service iptables stop 2、先写第一行规则,无条件 -j DROP 任何东西 3、写其他规则,该怎么写怎么写 4、当规则生效后等一段时间(保证此时间超过 snmpd 报文打进来后的系统响应时间),再删掉第一行规则,让数据涌进来(像开闸放水) 可惜,通过实测,攻击报文仍然可以进来,这个太奇葩了! /etc/init.d/iptables stop 行为分析 看到上述疑点,不得不怀疑 iptables 脚本中 stop 功能的实现了。 理论上 iptables 在 stop 的时候,不仅会清除所有规则,也会删掉所有 iptables 所用到的模块,这其中也包含了 conntrack。 推理:如果 iptables 的 stop 功能工作异常,那么就无法正确卸载 conntrack,那么上述方法就无济于事了。
图片:20130111120835970.png
红色部分是清除 conntrack 的关键语句。继续跟踪:
IPTABLES=iptables IPV=${IPTABLES%tables} # ip for ipv4 | ip6 for ipv6
脚本最初有上述定义,也就是说,最后 IPV 的取值是 "ip",卸载模块时卸载的是 ip_conntrrack。 手动验证一下:
[root@platinum-PT ~]# modprobe ip_conntrack [root@platinum-PT ~]# iptables -A INPUT [root@platinum-PT ~]# /etc/init.d/iptables stop Flushing firewall rules: [ OK ] Setting chains to policy ACCEPT: filter [ OK ] Unloading iptables modules: [ OK ] [root@platinum-PT ~]# lsmod |grep conntrack nf_conntrack_ipv4 22028 0 nf_conntrack 67784 1 nf_conntrack_ipv4 [root@platinum-PT ~]#
通过观察发现,conntrack 确实没有被卸载,但注意到了一点,模块名字不是 ip_conntrack 而是 nf_conntrack。 似乎明白了,验证一下:
[root@platinum-PT ~]# cat /etc/redhat-release Red Hat Enterprise Linux AS release 4 (Nahant Update 2) [root@platinum-PT ~]# uname -r 2.6.24.4-7 [root@platinum-PT ~]# 知道原因了:
修改 rmmod_r ${IPV}_conntrack 改为 rmmod_r nf_conntrack 因为是定制系统,未做详细的判断和动态自适应等工作。 总结
| |
![]() | ![]() |