iptables的recent模块用于限制一段时间内的连接数, 是谨防大量请求攻击的必杀绝技! 善加利用该模块可充分保证服务器安全。
recent常用参数
- --name 设定列表名称,即设置跟踪数据库的文件名. 默认DEFAULT;
- --rsource 源地址,此为默认。 只进行数据库中信息的匹配,并不会对已存在的数据做任何变更操作;
- --rdest 目的地址;
- --seconds 指定时间内. 当事件发生时,只会匹配数据库中前"几秒"内的记录,--seconds必须与--rcheck或--update参数共用;
- --hitcount 命中次数. hits匹配重复发生次数,必须与--rcheck或--update参数共用;
- --set 将地址添加进列表,并更新信息,包含地址加入的时间戳。 即将符合条件的来源数据添加到数据库中,但如果来源端数据已经存在,则更新数据库中的记录信息;
- --rcheck 检查地址是否在列表,以第一个匹配开始计算时间;
- --update 和rcheck类似,以最后一个匹配计算时间。 如果来源端的数据已存在,则将其更新;若不存在,则不做任何处理;
- --remove 在列表里删除相应地址,后跟列表名称及地址。如果来源端数据已存在,则将其删除,若不存在,则不做任何处理;
recent模块需要注意的地方
- 目录/proc/net/下的xt_recent目录是在启用recent模块之后才有的,如果没有在iptables中使用recent模块,/proc/net/目录中是没有xt_recent目录的;
- 因recent模块最多只能记录20条记录,所以当源发送的数据包超过20后,recent模块的计数器会立刻减掉20,这也就是为什么old_packets的值就总是处于1-20之间;
- 如果配合seconds参数使用的是--rcheck参数而不是--update,则recent模块会从收到第一个数据包开始计算阻断时间,而--update是从收到的最后一个数据包开始计算阻断时间,即如果服务器在8点收到了源发出第一个icmp数据包,在8点15分收到源发出的第20个数据包,如果使用的是--rcheck参数,那么8点半的时候,用户就又可以发送icmp数据包了,如果使用是--update参数,则用户必须等到8点40才能发送icmp数据包;
- 当源发送数据包的个数大于或等于recent模块的hitcount参数所指定的值时,相应的iptables规则才会被激活;
recent命令大体有如下三个排列组合
- --set句在前,--update(或--rcheck)句在后;
- --update(或--rcheck)句在前,--set句在后;
- --set句带或不带-j ACCEPT。
基本是上面这三项的排列组合;
下面通过几个案例进行说明
1) 利用iptables的recent模块来抵御简单的DOS攻击, 下面以限制ssh远程连接为例
|
$ iptables -I INPUT -p tcp --dport 22 -m connlimit --connlimit-above 3 -j DROP $ iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH $ iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 300 --hitcount 3 --name SSH -j DROP |
以上三条规则的解释如下:
- 利用connlimit模块将单IP的并发设置为3;会误杀使用NAT上网的用户,可以根据实际情况增大该值;
- 利用recent和state模块限制单IP在300s内只能与本机建立3个新连接。被限制一分钟后即可恢复访问。
- 第一句是记录访问tcp 22端口的新连接,记录名称为SSH, --set 记录数据包的来源IP,如果IP已经存在将更新已经存在的条目
- 第三句是指SSH记录中的IP,300s内发起超过3次连接则拒绝此IP的连接。 --update 是指每次建立连接都更新列表; --seconds必须与--update同时使用 --hitcount必须与--update同时使用
- 可以使用下面的这句记录日志:
|
$ iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --name SSH --second 300 --hitcount 3 -j LOG --log-prefix "SSH Attack" |
recent模块可以看作iptables里面维护了一个地址列表,这个地址列表可以通过"--set"、"--update"、"--rcheck"、"--remove"四种方法来修改列表,每次使用时只能选用一种。 还可附带"--name"参数来指 定列表的名字(默认为DEFAULT),"--rsource"、"--rdest"指示当前方法应用到数据包的源地址还是目的地址(默认是前者)。recent语句都带有布尔型返回值,每次执行若结果为真,则会执行后续的语句,比如"-j ACCEPT"之类的。"--seconds"参数表示限制包地址被记录进列表的时间要小于等于后面的时间。
基于上面的说明,现在来看四个基本方法的作用:
- --set 将地址添加进列表,并更新信息,包含地址加入的时间戳。
- --rcheck 检查地址是否在列表。
- --update 跟rcheck一样,但会刷新时间戳。
- --remove 就是在列表里删除地址,如果要删除的地址不存在就会返回假。
例1:限制无法ssh直接连接服务器,需先用较大包ping一下,此时在15秒内才可以连接上
|
$ iptables -P INPUT DROP $ iptables -A INPUT -s 127.0.0.1/32 -j ACCEPT $ iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT $ iptables -A INPUT -p icmp --icmp-type 8 -m length --length 128 -m recent --set --name SSHOPEN --rsource -j ACCEPT $ iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT $ iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --rcheck --seconds 15 --name SSHOPEN --rsource -j ACCEPT |
以上命令解说
- 将INPUT链默认策略置为DROP,当包走完INPUT链而没被拿走时就会丢弃掉;
- 本地localhost的包全部接受;
- 对于已建立连接或是与已连接相关的包都接受,服务器对外连接回来的包一般都走这条;基本环境已经配好了,现在开始要为连接到服务器的ssh打开通路。
- icmp类型 8 是ping包;指定包大小为 128 字节;recent用的列表名称为SSHOPEN,列表记录源地址。符合上述条件的数据包都接收。如果ping包内容为 100 字节,则加上IP头, ICMP头的 28 字节,总共 128 字节。
- 接受一般的ping包;
- 对连接ssh 22端口的连接进行处理,来源于SSHOPEN源地址列表并且在列表时间小于等于15秒的才放行。
例2: 限制每ip在一分钟内最多对服务器只能有8个http连接
|
$ iptables -I INPUT -p tcp --dport 80 -d 192.168.10.10 -m state --state NEW -m recent --name httpuser --set $ iptables -A INPUT -m recent --update --name httpuser --seconds 60 --hitcount 9 -j LOG --log-prefix 'HTTP attack: ' $ iptables -A INPUT -m recent --update --name httpuser --seconds 60 --hitcount 9 -j DROP |
以上命令解说
- 192.168.10.10是服务器ip
- 参数-I,将本规则插入到 INPUT 链里头的最上头。只要是 TCP连接,目标端口是80,目标 IP是我们服务器的IP,刚刚新被建立起来时,我们就将这个联机列入 httpuser 这分 清单中;
- 参数-A,将本规则附在 INPUT 链的最尾端。只要是60秒内,同一个来源连续产生多个联机,到达第9个联机时,我们对此联机留下Log记录。记录行会以 HTTP attack 开头。 每一次的本规则比对, –update 均会更新httpuser清单中的列表;
- 参数-A,将本规则附在 INPUT 链的最尾端。同样的比对条件,但是本次的动作则是将此连接丢掉;
- 所以,这三行规则表示,我们允许一个客户端,每一分钟内可以接上服务器8个。具体数值可以看运维者决定。这些规则另外也可以用在其它对外开放的网络服务上,例如port 22 (SSH), port 25 (smtp email)。
2) 对连接到服务器B的SSH连接进行限制,每个IP每小时只限连接5次
|
-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name SSHPOOL --rcheck --seconds 3600 --hitcount 5 -j DROP -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name SSHPOOL --set -j ACCEPT |
服务器A连接服务器B的SSH服务的数据包流程,假设以下数据包是在一小时(3600秒)内到达服务器B(iptables配置如上)的:
- 当这个服务器A的第1个SSH包到达服务器B,规则1检查SSHPOOL列表中这个源IP是否有hitcount,因为是第一个包,显而易见,列表是0,规则1判定这个数据包不必执行 DROP,并且也不处理这个数据包,将数据包转给下条规则。
- 规则2将这个数据包计入SSHPOOL列表,就是做+1,因为规则中有-j ACCEPT,规则2放行这个包。
- 第1个数据包进入服务器B,不用再在iptables里转了。
- 当第2个SSH包到达服务器B,规则1检查SSHPOOL列表的hitcount,发现是1没有超过5,于是判定不执行DROP并转给下条规则处理。
- 规则2在SSHPOOL中+1,并放行,第2个数据包进入服务器B。
- 第3、4、5个包同上。 g) 第6个包到达服务器B,规则1检查SSHPOOL列表中的hitcount,发现是5了已经连接5次了,于是规则2执行DROP,不必再转给下条规则了丢弃该包。 h) 第7、8…个包同上。
实际上recent的处理更为复杂, 从上面的流程可以看出,--set的功能在于计录数据包,将源IP加入列表。--rcheck(update)的功能在于判定数据包在seconds和hitcount条件下是否要DROP。
如果采用下面的配置, 则必须在INPUT链的默认策略为ACCEPT的情况下才能生效并使用, 否则(即INPUT链的默认策略为DROP)所有的SSH包都被丢弃了!!!!
|
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT //必须添加这个前提条件才能生效! -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name SSHPOOL --set -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name SSHPOOL --rcheck --seconds 3600 --hitcount 5 -j DROP |
这个命令与上面命令的区别在于:set句在前,且set句不带-j ACCEPT。这导致数据包的流程也不同。
- 第1个数据包到达规则1后马上计入SSHPOOL列表,并且规则1因为没有-j ACCEPT,直接将数据包转给下条规则。规则2拿到这个数据包后检查SSHPOOL列表,发现是1,也 不处理这个包,转给下条规则。如果后续的规则都没有再处理这个数据包,则最后由INPUT链的默认策略ACCEPT处理。由于上面的策略是DROP,所以丢弃该包,于是这两行命令在我的服务器上不能用。
- 这里有个问题,由于set句在前,数据包进入是先计入列表,再判定是否合法。这导致第5个包到达后,先在列表中+1,结果是5,再由规则2判定,发现是5,结果丢弃该包, 最后真正ACCEPT的只有4个包。其实个人认为这样写的代码不符合正常的思维逻辑, 而且这样写只能正常工作于默认策略是ACCEPT的情况,所以不建议用这个版本的命令,我的版本ACCEPT、DROP策略都能用。
从上面可以看出,在使用recent模块的命令的时候,一定要先确认iptables的INPUT链的默认策略是什么。
接着说下--rcheck 和 --update的区别 --rcheck从第1个包开始计算时间,--update是在rcheck的基础上增加了从最近的DROP包开始计算阻断时间,具有准许时间和阻断时间,update会更新last-seen时间戳。
就拿上面那个配置案例来说, rcheck是接收到第1个数据包时开始计时,一个小时内仅限5次连接,后续的包丢弃,直到一小时过后又可以继续连接。update则是接收到第1个数据包时计算准许时间,在一个小时的准许时间内仅限5次连接,当有包被丢弃时,从最近的丢弃包开始计算阻断时间,在一个小时的阻断时间内没有接收到包,才可以继续连接。所以rcheck类似令牌桶,一小时给你5次,用完了抱歉等下个小时吧。update类似网银,连续输错5次密码,停止一小时,只不过update更严格,阻断时间是从最近的一次输错时间开始算,比如输错了5次,过了半个小时又输错一次,这时阻断时间不是剩半小时,而是从第6次重新计算,剩一小时.
可以拿下面这个命令进行测试, 自行替换rcheck、update,然后ping一下就明白了:
|
-A INPUT -p icmp -m recent --name PINGPOOL --rcheck --seconds 30 --hitcount 5 -j DROP -A INPUT -p icmp -m recent --name PINGPOOL --set -j ACCEPT -A INPUT -p icmp -m recent --name PINGPOOL --update --seconds 30 --hitcount 5 -j DROP -A INPUT -p icmp -m recent --name PINGPOOL --set -j ACCEPT |
温馨提示: ICMP包和UDP包在iptables中的state情况是一样的,因为是无状态的,不同于TCP,iptables可以靠SYN等flags确定state,而iptables是基于ICMP包/UDP包到达服务器的间隔时间来确定state的。比如在做上面测试的时候,使用ping 192.168.10.10 -t时,除了第一个ICMP包state是NEW,后续的包state都是ESTABLISHED,结果因为前面有一句:
|
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT |
结果测试ping一直是通的,这个一定要弄明白!
3) 限制80端口60秒内每个IP只能发起10个新连接,超过记录日记及丢失数据包,可防CC及非伪造IP的syn flood
|
$ iptables -A INPUT -p tcp --dport 80 --syn -m recent --name webpool --rcheck --seconds 60 --hitcount 10 -j LOG --log-prefix 'DDOS:' --log-ip-options $ iptables -A INPUT -p tcp --dport 80 --syn -m recent --name webpool --rcheck --seconds 60 --hitcount 10 -j DROP $ iptables -A INPUT -p tcp --dport 80 --syn -m recent --name webpool --set -j ACCEPT |
以上命令解说
- 第1行规则表示: 60秒10个新连接,超过记录日志。
- 第2行规则表示: 60秒10个新连接,超过记录日志。
- 第3行规则表示: 范围内允许通过。
即每个IP目标端口为80的新连接会记录在案,可在/proc/net/xt_recent/目录内查看,rcheck检查此IP是否在案及请求次数,如果超过规则就丢弃数据包,否则进入下条规则并更新列表信息。
发送特定指定执行相应操作,按上面设置, 如果自己IP被阻止了,可设置解锁。
|
$ iptables -A INPUT -p tcp --dport 5000 --syn -j LOG --log-prefix "WEBOPEN: " #记录日志,前缀WEBOPEN: $ iptables -A INPUT -p tcp --dport 5000 --syn -m recent --remove --name webpool --rsource -j REJECT --reject-with tcp-reset #符合规则即删除webpool列表内的本IP记录 |
4) 默认封闭SSH端口,为您的SSH服务器设置开门暗语
|
$ iptables -A INPUT -p tcp --dport 50001 --syn -j LOG --log-prefix "SSHOPEN: " #记录日志,前缀SSHOPEN: $ iptables -A INPUT -p tcp --dport 50001 --syn -m recent --name sshopen --set --rsource -j REJECT --reject-with tcp-reset #目标端口tcp 50001的新数据设定列表为sshopen返回TCP重置,并记录源地址。 $ iptables -A INPUT -p tcp --dport 22 --syn -m recent --name sshopen --rcheck --seconds 15 --rsource -j ACCEPT #开启SSH端口,15秒内允许记录的源地址登录SSH。 #开门钥匙 nc host 50001 telnet host 50001 nmap -sS host 50001 |
5) 指定端口容易被破解密钥,可以使用ping指定数据包大小为开门钥匙
|
$ iptables -A INPUT -p icmp --icmp-type 8 -m length --length 78 -j LOG --log-prefix "SSHOPEN: " #记录日志,前缀SSHOPEN: $ iptables -A INPUT -p icmp --icmp-type 8 -m length --length 78 -m recent --name sshopen --set --rsource -j ACCEPT #指定数据包78字节,包含IP头部20字节,ICMP头部8字节。 $ iptables -A INPUT -p tcp --dport 22 --syn -m recent --name sshopen --rcheck --seconds 15 --rsource -j ACCEPT |
按照上面配置后, 依然无法ssh登录到指定主机, 因为没有添加"INPUT链的默认策略为ACCEPT"的前提, 即需要下面这个前提条件!
|
$ iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT |
整理后的配置规则
|
$ iptables -F $ iptables -X $ iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT $ iptables -A INPUT -p icmp --icmp-type 8 -m length --length 78 -j LOG --log-prefix 'SSH_OPEN_KEY' $ iptables -A INPUT -p icmp --icmp-type 8 -m length --length 78 -m recent --name openssh --set --rsource -j ACCEPT $ iptables -A INPUT -p tcp --dport 22 --syn -m recent --name openssh --rcheck --seconds 60 --rsource -j ACCEPT $ iptables -P INPUT DROP |
针对上面整理后的配置规则说明
- 第1,2行规则表示: 清空原有的iptables规则
- 第3行规则表示: 已经建立成功的连接和与主机发送出去的包相关的数据包都接受,如果没有这一步,后面的tcp连接无法建立起来
- 第4行规则表示: 出现长度为78字节icmp回响包在/var/log/syslog生成log,log以SSH_OPEN_KEY开头
- 第5行规则表示: 出现长度为78字节icmp回响包,将源地址信息记录在openssh文件中,并接受
- 第6行规则表示: 对于openssh文件中的源地址60s以内发送的ssh连接SYN请求予以接受
- 第7行规则表示: 将INPUT链的默认策略设置为drop
调试过程:
- 如果没有设置第3行规则,则无法建立ssh连接
- 在没有第3行规则的情况下,设置第6行如果不加--syn,则可以ssh连接一会儿,过一会儿又自动断线,除非ping一下目的地址. 原理是:ping目的地址,则会更新openssh的时间,这样ssh连接还在60s之内,所以可以通信,过一会儿,60s超时,则就会断开ssh连接。如果加了--syn,只能进行开始的syn,无法正常连接
在客户机上,如果需要ssh到主机,需要先ping主机进行解锁
|
$ ping -s 50 ip #linux主机的ip $ ping -l 50 ip #windows主机的ip |
并在一分钟之内ssh到主机,这样在/proc/net/xt_recent/目录下生成openssh文件, 内容如下:
|
src=192.168.10.10 ttl: 53 last_seen: 42963x6778 oldest_pkt: 1 4296376778, 4295806644, 4295806895, 4295807146, 4295826619, 4295826870, 4295827122, 4295827372, 4295833120, 4295833369, 4295834525, 4295834777, 4295872016, 4295872267, 4295872519, 4295872769, 4295889154, 4295889406, 4295889658, 4295889910 |
参考链接
Iptables之recent模块小结