端口敲击
端口敲门是一种外部打开端口的隐蔽方法,默认情况下,防火墙会保持这些端口关闭。它的工作原理是通过要求连接尝试一系列预定义的关闭端口。通过简单的端口敲门方法,当接收到正确的端口“敲击”(连接尝试)序列后,防火墙会打开指定的端口以允许连接。
这样做的好处是,对于常规的端口扫描,它看起来就像是该端口的服务不可用。本文将介绍如何使用守护进程或仅使用防火墙规则来实现端口敲门。
简介
安装和配置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) § 配置 提供了三个示例端口敲门配置。这些配置可以轻松修改,以便与 iptables 防火墙正确集成。如果您遵循了 Simple stateful firewall,则应将 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 列表中,则为 30 秒打开端口 22。它可以放在链的顶部,因为它只在满足此条件时才适用。它还引入了连接尝试列表的第一个,该列表用于跟踪后续的端口敲门序列。在此示例中,端口将在 30 秒后关闭,但不会触发其他任何操作。因此,可以从同一源 IP 进行新的端口敲门尝试。
如果最后一条规则未接受流量(例如,30 秒内没有连接尝试),但连接 IP 在允许 SSH2 的正确列表中,则将其从列表中移除,以便重新开始敲门。在检查相应列表后直接移除此 IP 对于正确处理序列非常重要。
-A TRAFFIC -m state --state NEW -m tcp -p tcp -m recent --name SSH2 --remove -j DROP
现在已经首先处理了序列的末尾,接下来的规则将进行端口序列检查。对于每个要敲击的端口,一条规则会按顺序检查正确的端口。如果序列匹配,则跳转到将 IP 添加到下一个序列敲门的列表的位置。如果未跳转到 SSH-INPUT 或 SSH-INPUTTWO,则只能意味着敲击了错误的端口,或者(更可能)是其他流量。因此,第二条规则会像之前的 SSH2 规则一样,从列表中移除 IP 并丢弃流量。
-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 的连接尝试设置为相应允许 IP 的recent列表以进行敲门序列的下一步。
第一个是序列中的第一个敲门。由于任何新的连接尝试都可能是端口敲门的开始,因此它在主链 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。