端口敲击
端口敲门 (Port knocking) 是一种隐蔽地从外部打开默认被防火墙关闭的端口的方法。其工作原理是要求连接一系列预定义的关闭端口。在简单的端口敲门方法中,当收到正确的端口“敲击”(连接尝试)序列时,防火墙将打开特定端口以允许连接。
其优势在于,对于常规的端口扫描,服务端口看起来就像是不可用一样。本文将介绍如何通过守护进程或仅通过防火墙规则来使用端口敲门。
简介
安装并配置 nftables 或 iptables 是阅读本文内容的前提条件。
iptables 中的 recent 模块用于根据 IP 地址的端口连接(无论成功与否)动态创建 IP 地址列表。利用 recent,防火墙可以判断某个 IP 地址是否敲击了正确的端口,如果是,则打开特定端口。
一次端口敲门会话可能如下所示
$ ssh username@hostname # No response (Ctrl+c to exit) ^C $ nmap -Pn --host-timeout 201 --max-retries 0 -p 1111 host #knocking port 1111 $ nmap -Pn --host-timeout 201 --max-retries 0 -p 2222 host #knocking port 2222 $ ssh user@host # Now logins are allowed user@host's password:
随机选择用于敲门序列的端口是明智之举。random.org 可以帮你生成 1 到 65535 之间的随机端口。为了确保没有无意中选择了常用端口,请使用此 端口数据库 和/或你的 /etc/services 文件。
简单端口敲门
服务器端
使用守护进程辅助
可以使用专门的守护进程来处理端口敲门。除了简化规则设置外,这些辅助程序还可能提供高级功能。
knockd 就是这样一个端口敲门守护进程,可以为你的网络提供额外的安全层。knockd(1) § CONFIGURATION 提供了三个端口敲门配置示例。这些配置可以轻松修改,以正确集成到 iptables 防火墙中。如果你参考了 简单状态防火墙,你应该将 INPUT 链定义替换为防火墙中使用的自定义 open 链。
例如:
[options]
logfile = /var/log/knockd.log
[opencloseSSH]
sequence = 8881:tcp,7777:tcp,9991:tcp
seq_timeout = 15
tcpflags = syn,ack
start_command = /usr/bin/iptables -A TCP -s %IP% -p tcp --dport 22 -j ACCEPT
cmd_timeout = 10
stop_command = /usr/bin/iptables -D TCP -s %IP% -p tcp --dport 22 -j ACCEPT
仅使用 iptables
接下来我们将构建一个 /etc/iptables/iptables.rules 文件来处理 SSH 的端口敲门。规则设置为在依次对端口 8881、7777 和 9991 进行一系列单次敲击 (knocks) 后,打开标准 SSH 端口 22。
首先,我们为该示例脚本定义默认的过滤策略和链。在此示例中,OUTPUT ACCEPT 是必要的,否则即使 SSH 端口打开,流量也会被丢弃,从而失去意义。最后三条链是我们随后在规则中进行端口敲门所需的。
# Filter definition
*filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] :TRAFFIC - [0:0] :SSH-INPUT - [0:0] :SSH-INPUTTWO - [0:0]
现在我们为主链 TRAFFIC 添加规则。端口敲门的概念是基于按顺序向正确的端口发送单个连接请求。我们需要 ICMP 用于某些网络流量控制,并允许已建立的连接(例如 SSH)。
# INPUT definition
-A INPUT -j TRAFFIC -A TRAFFIC -m state --state ESTABLISHED,RELATED -j ACCEPT -A TRAFFIC -m state --state NEW -m tcp -p tcp --dport 22 -m recent --rcheck --seconds 30 --name SSH2 -j ACCEPT
上述最后一条规则是:如果连接 IP 在 SSH2 列表中,则为该 IP 打开 22 端口 30 秒。它可以放在链的顶部,因为只有满足此条件时才会生效。它还引入了连接尝试列表中的第一个,用于在随后的步骤中跟踪端口敲门序列。在本例中,端口将在 30 秒后再次关闭,且不会触发其他操作。因此,同一个源 IP 可以再次尝试端口敲门。
如果最后一条规则没有接受流量(例如 30 秒内没有连接尝试),但连接 IP 处于允许 SSH2 的正确列表中,则将其从该列表中移除,以便从头开始重新敲门。在检查相应列表后立即将其移除,对于正确处理序列至关重要。
-A TRAFFIC -m state --state NEW -m tcp -p tcp -m recent --name SSH2 --remove -j DROP
既然已经先处理了序列的末端,接下来的规则将执行端口序列的检查。对于每个需要敲击的端口,一条规则会检查序列中的正确端口。如果满足序列,则跳转到将 IP 添加到序列中下一个敲击列表的地方。如果没有跳转到 SSH-INPUT 或 SSH-INPUTTWO,则只能说明敲击了错误的端口,或者(更有可能)是其他流量。因此,第二条规则会将 IP 从列表中移除并丢弃流量,与之前的 SSH2 规则相同。
-A TRAFFIC -m state --state NEW -m tcp -p tcp --dport 9991 -m recent --rcheck --name SSH1 -j SSH-INPUTTWO -A TRAFFIC -m state --state NEW -m tcp -p tcp -m recent --name SSH1 --remove -j DROP
对于下一个要敲击的端口,采取相同的步骤。TRAFFIC 链中序列的顺序可以是任意的,只要对应于同一列表的规则保持在一起且顺序正确即可。
-A TRAFFIC -m state --state NEW -m tcp -p tcp --dport 7777 -m recent --rcheck --name SSH0 -j SSH-INPUT -A TRAFFIC -m state --state NEW -m tcp -p tcp -m recent --name SSH0 --remove -j DROP
在最后一组规则中,实现了将 IP 的连接尝试设置为敲门序列下一步对应的 recent 允许 IP 列表的“魔力”操作。
第一个是针对序列中第一次敲击的规则,由于任何新的连接尝试都可能是端口敲门的开始,因此它作为主链 TRAFFIC 的一部分进行检查。成功(端口正确)后,它将敲击记录在第一个列表 SSH0 中。这在最后一组检查规则中可见:检查第二次敲击 (7777) 的规则要求之前必须有对第一个端口的敲击,然后才能设置下一个 recent 列表 (SSH1)。这种切换实现了列表的顺序化。
-A TRAFFIC -m state --state NEW -m tcp -p tcp --dport 8881 -m recent --name SSH0 --set -j DROP -A SSH-INPUT -m recent --name SSH1 --set -j DROP -A SSH-INPUTTWO -m recent --name SSH2 --set -j DROP -A TRAFFIC -j DROP COMMIT
请注意,即使敲击了正确的端口,流量在最后几条规则中仍会被丢弃 (DROP)。这种 DROP 掩盖了连接尝试是对其中任何端口的成功敲击。
现在规则已完成,请执行 daemon-reload 并使用这些规则重启 iptables.service。
运行上述所有命令后 iptables.rules 文件的示例
*filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] :TRAFFIC - [0:0] :SSH-INPUT - [0:0] :SSH-INPUTTWO - [0:0] # TRAFFIC chain for Port Knocking. The correct port sequence in this example is 8881 -> 7777 -> 9991; any other sequence will drop the traffic -A INPUT -j TRAFFIC -A TRAFFIC -p icmp --icmp-type any -j ACCEPT -A TRAFFIC -m state --state ESTABLISHED,RELATED -j ACCEPT -A TRAFFIC -m state --state NEW -m tcp -p tcp --dport 22 -m recent --rcheck --seconds 30 --name SSH2 -j ACCEPT -A TRAFFIC -m state --state NEW -m tcp -p tcp -m recent --name SSH2 --remove -j DROP -A TRAFFIC -m state --state NEW -m tcp -p tcp --dport 9991 -m recent --rcheck --name SSH1 -j SSH-INPUTTWO -A TRAFFIC -m state --state NEW -m tcp -p tcp -m recent --name SSH1 --remove -j DROP -A TRAFFIC -m state --state NEW -m tcp -p tcp --dport 7777 -m recent --rcheck --name SSH0 -j SSH-INPUT -A TRAFFIC -m state --state NEW -m tcp -p tcp -m recent --name SSH0 --remove -j DROP -A TRAFFIC -m state --state NEW -m tcp -p tcp --dport 8881 -m recent --name SSH0 --set -j DROP -A SSH-INPUT -m recent --name SSH1 --set -j DROP -A SSH-INPUTTWO -m recent --name SSH2 --set -j DROP -A TRAFFIC -j DROP COMMIT # END or further rules
仅使用 nftables
客户端脚本
现在配置已完成,要进行端口敲门,你需要一个工具。前文提到的 knockd 自带 knock 工具,它简单且可能足以满足许多需求。上游网站为其他操作系统也提供了 knock 工具。
nmap 也可以用于此目的。一个简单的 shell 脚本 (knock) 可以使端口敲门自动化
/usr/local/bin/knock
#!/bin/bash
HOST=$1
shift
for ARG in "$@"
do
nmap -Pn --host-timeout 100 --max-retries 0 -p $ARG $HOST
done
或者,你可以使用 openbsd-netcat 并简单地设置一个 shell 别名 (alias)
alias knock="nc -z"
你可以使用上述所有方法通过 knock 主机 端口1 端口2 端口x 来调用。
接下来我们使用该脚本。为了避免其他正在进行的网络活动产生不利影响,此测试在 localhost 上完成。
首先,拔掉网络线,设置 SSHD 监听的 IP
[user@host ~]# ip link set up dev enp8s0 [user@host ~]# ip address add 192.168.1.1/24 dev enp8s0 [user@host ~]# ip route add default via 192.168.1.1 [user@host ~]# systemctl status sshd |grep listening Aug 21 14:36:53 host sshd[3572]: Server listening on 192.168.1.1 port 22
其次,检查 SSHD 是否接受连接,然后执行脚本,最后成功登录 SSH
$ ssh user@host # No response (Ctrl+c to exit) ^C $ knock host 8881 7777 9991 $ ssh user@host # Now logins are allowed user@host's password: Last login: Tue Aug 20 23:00:27 2013 from host
第一次连接尝试必须手动停止,因为连接被 DROP 后不会发送回复。为了测试目的,可以将最后几条规则的 DROP 修改为 REJECT,这样会返回 connection refused。最后,在成功登录后,可以在内核的 recent 列表中看到成功的敲击记录
[user@host ~]$ cat /proc/net/xt_recent/SSH* src=192.168.1.1 ttl: 64 last_seen: 296851 oldest_pkt: 1 296851 src=192.168.1.1 ttl: 64 last_seen: 297173 oldest_pkt: 1 297173 src=192.168.1.1 ttl: 64 last_seen: 297496 oldest_pkt: 1 297496 [user@host ~]$ exit logout Connection to 192.168.1.1 closed.
fwknop
fwknop 试图克服上述简单端口敲门方法的一些局限性。代价是更高的复杂性和资源占用,主要是在被保护的服务端。它提供端口敲门和单包授权 (SPA)。它通过使用 libpcap 和加密方法来实现目标。libpcap 是 tcpdump 的底层库,使用它可以检查所有进入的数据包,包括防火墙不允许通过的数据包,以及没有服务在监听的数据包。加密的使用防止了攻击者通过重新发送之前的数据包来绕过 fwknop。