端口敲门

出自 ArchWiki

端口敲门 是一种隐蔽方法,用于从外部打开默认情况下被防火墙关闭的端口。它的工作原理是要求连接尝试一系列预定义的关闭端口。使用简单的端口敲门方法,当收到正确的端口“敲击”(连接尝试)序列时,防火墙会打开某些端口以允许连接。

其优点是,对于常规端口扫描,端口的服务可能看起来只是不可用。本文展示了如何使用守护进程或仅使用防火墙规则来实现端口敲门。

警告: 端口敲门应作为安全策略的一部分使用,而不是唯一的保护措施。那将是一种脆弱的隐蔽式安全。对于 SSH 保护,请参阅SSH 密钥,这是一种可以与端口敲门一起使用的强大方法。此外,请勿将本示例中使用的端口序列永久用于实际配置。

简介

安装和配置 nftablesiptables 是本文内容的前提条件。

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:
注意: 当前开发的 iptables 中有一些新增功能,它们在单个规则中具有端口敲门命令。它们还提供了许多其他高级选项,但尚未进入标准内核(2013 年 8 月)。如果感兴趣:xtables-addonsAUR

明智的做法是随机选择用于敲击序列的端口。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 的端口敲门。规则设置为在按顺序对端口 888177779991 进行一系列单个敲击后,打开标准 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-INPUTSSH-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-reloadrestart 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

端口敲门示例,仅使用 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。

参见