本文来学习安全模块:iptables 企业级安全策略。
说到 iptables,如果你用过 Linux 会比较了解,它是 Linux 上的一套防火墙服务,调用的其实是 Netfilter 内核模块。Netfilter 是 Linux 操作系统核心层内部的一个数据包处理模块,它具有如下功能:
网络地址转换;
数据包内容修改;
数据包过滤防火墙功能。
在现实中,其实很多应用服务都调用了 Netfilter。这里我给你画了一张图:

可以看到,在最中央就是 Netfilter 的 Linux 内核模块了,我们看到最下层 iptables 会调用 Netfilter 模块来作为安全防火墙。在 CentOS7 以后,出来一款新的防火墙服务叫作 firewalld,其实也是调用了 Netfilter 模块。总体来说,无论是 iptables,还是 firewalld,它们都是调用了 Linux 的 Netfilter 内核模块。(另注:较新的发行版默认转向基于内核 nftables 的 nft 工具,RHEL 8 起还提供 iptables-nft 兼容层——nftables 已是现代替代方案,iptables 语法仍可继续使用。)
我们再来看图片左边,这里有一个 ipvs 应用工具,它也调用了 Netfilter,但是它的功能和安全防护的防火墙功能有一些区别,它主要用来实现地址转换这样的一个核心功能,所以 ipvs 会为很多负载均衡提供服务,也就是为 LVS 提供服务,如果你了解过 LVS 就会清楚,其实 LVS 就是用到了 ipvs 的命令模块来对数据包规则进行设置。所以 ipvs 的上一层会供 LVS 调用,它在运维管理中可以作为防火墙服务来使用。所以我们会看到 ipvs 是作为负载均衡的服务来使用的,可以总结为负载均衡的服务其实也是调用了 Netfilter 内核模块。
除此之外,我们在这张图的左下角还会看到一个 Kube-proxy 命令模块,它也调用了 ipvs,同时也调用 iptables。其实 Kube-proxy 是 K8s 里的一个组件,负责管理 K8s 里的 service 模块,也就是后端服务,管理后端服务需要对数据包进行转发,并实现负载均衡。我们会看到在 K8s 整套模块的组件里,Kube-proxy 其实也调用了 ipvs,同时基于 ipvs 去调用 Linux 内核的 Netfilter,那么为什么 Kube-proxy 还要去调用 iptables?这是因为早期的 Kube-proxy 版本是基于 iptables 实现的,但是这里有一个性能问题,因为 iptables 毕竟不是专门用作数据包转发的,所以在 service 高于 1000 个的情况下,性能表现上 ipvs 优于 iptables。所以就逐渐使用了 ipvs 服务来代替 iptables 工具。
在这张图里,我们会看到当前整个 Netfilter 在应用平台服务里面,起到的至关重要的作用。而在其之上发布的其实是两款专业的防火墙工具——iptables 与 firewalld。对于 iptables 和 firewalld,这两个防火墙服务之间有两个比较大的差异,这里给你来介绍一下。
iptables 默认是允许访问服务或者通过数据包进行访问的,如果我们不允许某些数据包访问,就可以设置具体的规则去进行阻断。firewalld 则是默认拒绝所有数据包进行访问,只有我们允许一个数据包满足某个规则的时候,才去做对应的设置。所以说它们是相反的两个默认设置的防火墙规则。
另外就是 firewalld 在对数据安全的防护管理上用到了区域的概念,会把网络环境分成很多个区域来进行安全管理,而 iptables 则不会分区,而是直接基于面的方式去进行规则上的设置。本文重点介绍公司层面的安全,并且通过 iptables 来介绍企业对防火墙的规则设置。
在学习本文之前,我们先要搞懂 iptables 的一些基础知识,iptables 有一个很重要的设置概念就是 4 表 5 链,我们可以理解为是将一些对数据包设置的不同维度共同组合起来,进行数据包安全功能上的一些具体设置。
有如下的 4 张表,包括一个 Filter 表,一个 NAT 表,一个 Mangle 表,一个 Raw 表。它们分别作用于不同的方向,Filter 表用于过滤数据包(默认表),NAT 表用于网络地址转换(IP、端口),Mangle 表用于修改数据包的服务类型、TTL,并且可以配置路由实现 QoS;Raw 表用于决定数据包是否被状态跟踪机制处理。
5 链分别是 INPUT 链,OUTPUT 链,FORWARD 链,PREROUTING 链和 POSTROUTING 链。这 5 条链分别也对应不同的一些设置规则。
INPUT 链:进来的数据包应用此规则链中的规则;
OUTPUT 链:外出的数据包应用此规则链中的规则;
FORWARD 链:转发数据包时应用此规则链中的规则;
PREROUTING 链:对数据包作路由选择前应用此链中的规则;
POSTROUTING 链:对数据包作路由选择后应用此链中的规则。
我们可以认为有了表和链共同的组合,才完成了完整的数据包规则设置。通常一个数据包也需要经过对应表中不同链结合起来的规则,从而完成整个流程的处理。
另外需要特别提及的一个地方是自定义链,它跟我们刚刚讲到的 4 表 5 链相比有一个较特殊的地方,它是为用户方便管理而使用的,它并不会被直接使用,而是需要通过被默认的链引用才能使用。我们可以理解为它是用户指定给设置的一个规则链的名称,用于方便进行整体规则上的设置。接下来本文将为你演示安全防控规则的设置,这通常是通过 iptables 的 Filter 表结合 INPUT 链规则进行设置,我们来看一看通过这样的一种设置方式可以实现哪些 4 层安全上面的功能设置。
那么说到了 iptables 的规则设置,你可能需要了解,通过 iptables 命令进行安全防护规则设置都有哪些选项,下面逐一为你列举了几个具体的一些常见的选项:

-A 指向指定链添加一个或多个新规则;
-D 表示从指定链中删除一个或多个规则;
-N 表示创建一个新的用户定义链;
-F 清空(flush)指定链或所有链中的规则,常用于初始化;
-X 则是删除指定的用户定义链(需先清空链路中的策略);
-p 指定具体一些协议,比如 TCP、UDP、ICMP 等常见的一些协议数据包;
-s 用来指定 IP 地址 [/ mask] 源地址;
-d 用来指定 IP 地址 [/ mask] 目标地址;
-i 指定设备数据包进入的接口,如果是多网卡的话,可能需要指定具体的接口。
这些对应的选项是你需要提前知道的,如果你不了解的话,建议先看一些 iptables 的基础使用规则,然后再学习后面的部分,否则可能会遇到一些小的问题。
接下来,我们用一些场景来给你讲一讲,在 iptables 里面一些常见的设置安全规则的方式。
第一个模块就是对 iptables 进行初始化,所谓 iptables 初始化就是需要将 iptables 的规则清空,同时又要设置一个默认规则。通常我们需要把规则中的策略进行清空。
-X 表示是指定用户自定义链来进行删除;
-Z 则是将指定链路或者策略中的计数归零。
这样的话就完成了整体规则的清空。另外,用 iptables -P 分别为 INPUT、OUTPUT、FORWARD 三条链设置默认策略 ACCEPT,也就是允许所有数据包进行访问,这就完成了整体的初始化。我们看到初始化里没有做任何安全防护规则,而是把以往的规则进行清空,并且设置一条权限比较大的规则,在这个基础上再进行具体的安全防护规则设置。
首先第一个安全防护规则就是通过 iptables 命令来进行设置的,每次设置完具体的命令后,就会把这些命令转化为 iptables 所能识别的一条规则生成到配置文件里。通常对于用户来说对配置文件的管理可能是非常容易理解的,所以为了方便进行 iptables 设置,我们更愿意把 iptables 命令的使用和选项的使用,封装成一些 Shell 脚本来直接批量化设置。这里会看到我使用的 iptables 命令进行设置的同时,也结合了一些 Shell 脚本的语法格式。我们来看一下:
iptables -A INPUT -i lo -j ACCEPT # 允许本地回环访问
LOCAL_NET="xxx.xxx.xxx.xxx/xx" # 允许内部指定网段访问
if [ "$LOCAL_NET" ]
then
iptables -A INPUT -p tcp -s $LOCAL_NET -j ACCEPT
fi
ALLOW_HOSTS=( # 允许访问主机 IP 开白
"xxx.xxx.xxx.xxx"
"xxx.xxx.xxx.xxx"
)
if [ "${ALLOW_HOSTS}" ]
then
for allow_host in ${ALLOW_HOSTS[@]}
do
iptables -A INPUT -p tcp -s $allow_host -j ACCEPT
done
fi
首先在前面我们会看到有一个 -A,表示向 INPUT 链添加规则,配合 -i 指定本地回环网卡(lo),即放行本地回环访问。在下面有一个 LOCAL_NET,这里其实是定义了一个 Shell 变量,在这个变量里我们可以进行一些自定义配置,比如本地的网络段属于哪一个网段。如果允许本地网络段进行访问,就需要定义一整串网段信息,并且把网段信息赋值给 LOCAL_NET,然后判断这个变量是否存在。如果存在的话,则允许本地的网络访问 iptables -A INPUT,-p tcp 表示 TCP 协议,-s 表示源地址,也就是允许所有本地网络源地址来访问这台主机。 下面同样也是白名单设置 Shell 的实施方式:
DENY_HOSTS=( # 直接丢弃列表
"xxx.xxx.xxx.xxx"
"xxx.xxx.xxx.xxx"
)
if [ "${DENY_HOSTS}" ]
then
for deny_host in ${DENY_HOSTS[@]}
do
iptables -A INPUT -s $deny_host -m limit --limit 1/s -j LOG --log-prefix "deny_host: "
iptables -A INPUT -s $deny_host -j DROP
done
fi
这里首先定义了一个数组变量,里面包含了多个 IP。与上面的白名单相反,这里设置的是需要拒绝的名单规则:对列表中的每个源 IP,先用 limit 模块做限频并记录日志,再用第二条规则把它的数据包直接 DROP 丢弃。
DENY_HOSTS 是另外一个数组变量,这里主要用于设置不允许哪些源 IP 访问,并且对其进行限频。我们看到在 do 这个循环体里面,会以 -s 源地址做匹配;-m 用于加载扩展模块,这里加载的 limit 模块表示做速率上的限制。--limit 1/s 表示每秒最多匹配一次,即该 IP 每秒至多记录一条日志(限频);随后再加一条规则,直接 DROP——既做日志记录,也把数据包丢弃。
我们看到,这一块主要做的是黑名单的安全规则,那么下面加了一条额外的规则:
iptables -A INPUT -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT # 建立会话后允许数据包通信
这里有一个 -m state,它是 iptables 里通常需要加入进去的,那么它的特殊的情况是什么呢?它表示建立会话的包是允许进行通信的,比如设置 iptables 规则的主机,访问机制是这台主机主动去访问这些黑名单主机里面的服务,由于是主动发出的,如果建立起了会话,服务端这些黑名单的主机回包到我的主机上,也是允许通过的。
这和黑名单的差异在于没有建立会话,这些黑名单的主机则主动对我的主机进行建联,这样是不允许的。所以同时加上这两点的话会更加符合一些实际的 iptables 规则的设置需求。这样如果主机想要主动去访问任何一个地址,都可以通过这样的规则进行优先匹配了。
其实上面这些设置更多是基于源地址、目的地址、IP 地址这样的访问控制进行设置。那么下面我们会来看一看,对于一些常见的 4 层的攻击,比如说 SYN 的攻击,iptables 里有哪一些设置可以帮助到我们?
iptables -N SYN_FLOOD
iptables -A SYN_FLOOD -p tcp --syn \
-m hashlimit \
--hashlimit 200/s \
--hashlimit-burst 3 \
--hashlimit-htable-expire 300000 \
--hashlimit-mode srcip \
--hashlimit-name t_SYN_FLOOD \
-j RETURN
# --hashlimit 200/s:每秒最多放行 200 个新建连接(超出进入下方 LOG/DROP)
# --hashlimit-burst 3:令牌桶初始容量 3(允许 3 个突发)
# --hashlimit-htable-expire 300000:哈希表记录有效期限(单位:毫秒)
# --hashlimit-mode srcip:通过源地址管理请求数
# --hashlimit-name t_SYN_FLOOD:哈希表名,记录存储在 /proc/net/ipt_hashlimit
iptables -A SYN_FLOOD -j LOG --log-prefix "syn_flood_attack: "
iptables -A SYN_FLOOD -j DROP
iptables -A INPUT -p tcp --syn -j SYN_FLOOD
这里,iptables 有一段规则,加了 -N SYN_FLOOD。-N 表示建立了一条自定义的规则链,我们来看一下这条自定义的规则里添加了哪些具体策略。首先匹配 tcp --syn 协议的数据包,hashlimit 也就是限频,每秒最多 200 次放行,突发容量为 3;未超过限速的数据包会以 -j RETURN 返回默认链继续处理,超过限速的则不再 RETURN,落入后面的 LOG 记录与 DROP 丢弃两条规则。
另外这里对 hashlimit 这个表的数据做了一个过期时间设置,也就是说,因为 hashlimit 表是需要记录这样的一个状态的,所以需要设置它的有效期。
--hashlimit-mode srcip 表示通过源地址管理请求数,也就是说基于客户的源地址来进行具体唯一性的标识来进行判断。那么这一串命令主要来进行频次上具体协议包的限制,也就是不允许单个地址通过 tcp --syn 发起过多的请求,从而导致发起过多的连接。
我们刚刚讲到的是设置一条自定义链,这种自定义链都需要通过默认链来进行调用。我们看到在默认链里面则是使用 INPUT 链来进行具体调用,-j 后面加了自定义链的名称,这样就完成了整个自定义链的设置。
我们刚刚讲到的是对包的频次进行设置,那么对于其他协议的包其实也可以用同样的一些办法,我们可以看一下这里的 ICMP 设置:
iptables -N PING_OF_DEATH # 丢弃超出限制的 ICMP
iptables -A PING_OF_DEATH -p icmp --icmp-type echo-request \
-m hashlimit \
--hashlimit 1/s \
--hashlimit-burst 10 \
--hashlimit-htable-expire 300000 \
--hashlimit-mode srcip \
--hashlimit-name t_PING_OF_DEATH \
-j RETURN
iptables -A PING_OF_DEATH -j LOG --log-prefix "ping_of_death_attack: "
iptables -A PING_OF_DEATH -j DROP
iptables -A INPUT -p icmp --icmp-type echo-request -j PING_OF_DEATH
如果我们是想对 ICMP 的连接进行具体的规则设置的话,同样也需要新建一条自定义链,然后在里面设置它的频次,并且进行具体策略设置,最后由默认链 INPUT 链调用自定义链,这样就实现了对 ICMP 超出规则的设置。这里每秒只放行一次 ICMP 请求,突发容量为 10(--hashlimit-burst 10),超过后的请求会被记录日志并丢弃。
我们刚刚讲到了对于 ICMP 规则的设置,接下来也可以对 HTTP 的位置来进行设置,这里有一个疑问了,可能你会认为 HTTP 是工作在应用层的协议,既然我们讲的是 4 层安全,怎么会有对 HTTP 进行访问的限制?其实对于 HTTP 协议现在也是基于 4 层的策略来进行限制的,这里其实也是基于 TCP 的协议来进行设置。
HTTP="80,443"
iptables -N HTTP_DOS
iptables -A HTTP_DOS -p tcp -m multiport --dports $HTTP \
-m hashlimit \
--hashlimit 1/s \
--hashlimit-burst 100 \
--hashlimit-htable-expire 300000 \
--hashlimit-mode srcip \
--hashlimit-name t_HTTP_DOS \
-j RETURN
# --hashlimit-name t_HTTP_DOS:哈希表记录存储在 /proc/net/ipt_hashlimit
iptables -A HTTP_DOS -j LOG --log-prefix "http_dos_attack: "
iptables -A HTTP_DOS -j DROP
iptables -A INPUT -p tcp -m multiport --dports $HTTP -j HTTP_DOS
因为我们这里是 -p tcp,只不过端口 -dports 是基于 HTTP 端口,也就是说其实是基于 4 层来限制的 7 层,并不是基于 7 层会话的 cookie session 去进行设置的。所以这里也是进行的一个 TCP 连接,只不过是指定了访问具体的某一个服务端口,从而进行频次限制。
所以基于底层的连接式的频次限制,同样有助于我们应用层的安全,基于这样的一个理念,对于 SSH 的安全设置,我们同样也可以通过这样的方式来设置。SSH 最大的问题就是会存在暴力破解,所以我们就需要设置在指定时间内允许多少次访问,否则就需要做具体的安全限制了。
SSH=22
iptables -A INPUT -p tcp --syn -m multiport --dports $SSH -m recent --name ssh_attack --set
iptables -A INPUT -p tcp --syn -m multiport --dports $SSH -m recent --name ssh_attack --rcheck --seconds 60 --hitcount 5 -j LOG --log-prefix "ssh_brute_force: "
iptables -A INPUT -p tcp --syn -m multiport --dports $SSH -m recent --name ssh_attack --rcheck --seconds 60 --hitcount 5 -j REJECT --reject-with tcp-reset
这里会看到同样是基于 TCP 的协议请求,访问 SSH 端口的时候,会判断最近 60 秒之内访问了多少次数,如果是超过了次数限制的话,那么就会进行 REJECT,也就是拒绝 IP 来进行访问了。所以 SSH 还有 FTP 都可以通过这样的一种方式来防止暴力破解的攻击。
接下来我们需要在机器上面设置,哪一些服务是需要正常对外提供服务的,这里我们可以来具体设置一些需要对外提供的服务,比如需要允许某一些网段访问指定的一些服务,或是访问 SSH 服务等。如果是有一些 HTTP 服务,需要公共对外服务的话,同样可以把网段的源地址限制去掉,而把公共服务对外暴露,那也就是把服务设置成对外能够允许提供正常服务,不因为 iptables 规则而阻止,所以这里我们主要是围绕服务来进行具体设置的。
iptables -A INPUT -p tcp -m multiport --dports $HTTP -j ACCEPT
# ...(其他需要开放的服务,同理追加)
LIMITED_LOCAL_NET="xxx.xxx.xxx.xxx/xx"
if [ "$LIMITED_LOCAL_NET" ]
then
# SSH
iptables -A INPUT -p tcp -s $LIMITED_LOCAL_NET -m multiport --dports $SSH -j ACCEPT # LIMITED_LOCAL_NET -> SELF
# FTP
iptables -A INPUT -p tcp -s $LIMITED_LOCAL_NET -m multiport --dports $FTP -j ACCEPT # LIMITED_LOCAL_NET -> SELF
# MySQL
iptables -A INPUT -p tcp -s $LIMITED_LOCAL_NET -m multiport --dports $MYSQL -j ACCEPT # LIMITED_LOCAL_NET -> SELF
fi
ZABBIX_IP="xxx.xxx.xxx.xxx"
if [ "$ZABBIX_IP" ]
then
iptables -A INPUT -p tcp -s $ZABBIX_IP --dport 10050 -j ACCEPT
fi
我们刚刚讲到的 IP 规则设置了基于底层协议的限制规则,还帮助应用层来做安全频率上的一些规则限制,刚刚我们也讲到了,如何允许正常服务对外提供访问设置,最后我们只要进行一条规则来整体收尾,也就是要设置除了这些规则以外的其他的所有服务,默认不允许访问,因为我们知道 iptables 默认不做设置的话,它是允许对外提供服务的。
假设还有一些文件共享类的服务端口,我们没有允许它们对外提供服务,它们默认也是可以对外来提供的,因为 iptables 默认是允许对外提供访问的。所以在最后我们需要设计一条收尾规则,就是除了我们刚刚设置的这些规则以外,其他的访问的数据包一概是 drop 掉的,不允许访问的。所以我们就加一条规则:
iptables -P INPUT DROP # 收尾:未匹配任何放行规则的数据包一律丢弃
这样就完成了一个完整的规则设置了。本文讲解的 iptables 的规则设置,还有它的一些整个使用关系,以及具体的安全设置上的一些设置样例,你可以再多多理解,下个课时我将讲解“应用安全:基于 HTTP、HTTPS 请求过程中常见 waf 攻防策略”,记得按时听课。