端口敲门
端口敲门 是一种隐蔽方法,用于从外部打开默认情况下被防火墙关闭的端口。它的工作原理是要求连接尝试一系列预定义的关闭端口。使用简单的端口敲门方法,当收到正确的端口“敲击”(连接尝试)序列时,防火墙会打开某些端口以允许连接。
其优点是,对于常规端口扫描,端口的服务可能看起来只是不可用。本文展示了如何使用守护进程或仅使用防火墙规则来实现端口敲门。
简介
安装和配置 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
进行一系列单个敲击后,打开标准 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
上时,将端口 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 掩盖了连接尝试对它们中的任何一个是成功敲击的事实。
现在规则已完成,使用规则执行 daemon-reload 和 restart 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 knock="nc -z"
您可以使用 knock HOST PORT1 PORT2 PORTx
调用以上所有方法。
在下面,我们使用脚本。为了避免其他正在进行的网络连接产生不利影响,此测试已在 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。