SSH 端口敲门实战:用 knockd 把 22 端口藏起来,附自动关门与动态口令加固

为什么裸奔的 22 端口一定会被爆破

任何一个把 SSH 直接暴露在公网的服务器,日志里迟早会堆满这样的记录:来自世界各地的 IP 用 root、admin、test 之类的用户名,一秒几次地尝试登录。fail2ban 和改端口能缓解,但本质都是"把门藏起来"或者"撞多了就封",端口本身依然对全网可见。真正把攻击面降到接近零的思路只有一条:让 22 端口在默认状态下根本不存在,只有知道暗号的人才让它临时出现。这就是端口敲门(Port Knocking)。

本文用 knockd 在 Debian 12 / Ubuntu 22.04 上搭一套可用的单包敲门方案,再讲清它和 fwknop 单包授权(SPA)的区别,以及生产环境里必须补上的几个安全细节。全程假设你有 IPMI / VNC 控制台兜底,否则改防火墙有把自己锁在门外的风险。

端口敲门要解决的核心问题

防火墙默认策略是 DROP 掉 22 端口的所有入站包。你在客户端按顺序访问一串事先约定的端口(比如 7000、8000、9000),服务器上的 knockd 监听这些"敲门"包,一旦顺序匹配,就临时在 iptables/nftables 里为你的源 IP 开一个限时窗口,让你能连上 SSH。窗口过期自动关闭。

它防的不是"猜密码",而是"被发现"。扫描器扫 22 端口,收到的是丢包(超时),连 RST 都没有,判定为 filtered,绝大多数自动化爆破工具会直接跳过。攻击面从"一个可交互的服务"缩成"一个沉默的防火墙规则"。

安装与基础配置

先装 knockd,Debian 系在默认源里就有:

apt update && apt install -y knockd
systemctl stop knockd

knockd 的主配置在 /etc/knockd.conf。别用默认那份直接把 22 打开关掉的示例,我们写成"开一个带超时的小窗口":

[options]
    UseSyslog
    LogFile = /var/log/knockd.log
    Interface = eth0

[openSSH]
    sequence    = 7000,8000,9000
    seq_timeout = 5
    command     = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
    tcpflags    = syn

[closeSSH]
    sequence    = 9000,8000,7000
    seq_timeout = 5
    command     = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
    tcpflags    = syn

几个关键点。第一,%IP% 是 knockd 自动替换成敲门来源 IP 的占位符,一定要让防火墙规则精确到源 IP,绝不能写成对全网开放。第二,tcpflags = syn 要求敲门包是真正的 TCP SYN,避免随手一个连接被误判。第三,seq_timeout = 5 表示整串敲门必须在 5 秒内完成,太长等于给暴力猜顺序留时间。

如果系统已经迁移到 nftables,把 command 换成对应的 nft 命令,例如 nft add rule inet filter input ip saddr %IP% tcp dport 22 accept。knockd 本身只管"触发命令",用哪套防火墙由你决定。

把 22 端口默认关掉

敲门的价值建立在"默认关闭"之上。先把当前的放行规则记录下来,再设默认拒绝:

# 先确认别把当前会话踢掉(放行你自己的管理 IP 作为逃生口)
iptables -A INPUT -s 你的固定IP -p tcp --dport 22 -j ACCEPT

# 确保 established 连接不被误杀
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 其余来源的 22 一律 DROP
iptables -A INPUT -p tcp --dport 22 -j DROP

注意顺序,iptables 是从上往下匹配。逃生口规则必须在 DROP 之前。改完先不要断开当前 SSH,另开一个窗口验证新连接是否被拒,确认无误再保存规则(netfilter-persistent save 或对应发行版的持久化方式)。

客户端如何敲门

客户端最省事的做法是装 knock 客户端,或者直接用 bash 手动敲:

for p in 7000 8000 9000; do
    timeout 1 bash -c ">/dev/tcp/服务器IP/$p" 2>/dev/null
done
sleep 1
ssh user@服务器IP

把这段包成一个 shell 函数或 alias,比如 knock-ssh,以后一条命令搞定。用 nmap 也可以:nmap -Pn --max-retries 1 --host-timeout 5s -p 7000,8000,9000 服务器IP,但手动 /dev/tcp 更轻,不依赖额外工具。

敲完马上 ssh,窗口和 SSH 连接是两回事:窗口只是让你"能连上",连上之后连接靠 conntrack 的 ESTABLISHED 维持,即使窗口过期、防火墙规则被删,已建立的会话不会断。这是端口敲门可以用来长期运维的关键。

knockd 的致命短板与加固

敲门序列在网络上是明文传输的,任何在路径上抓包的人都能看到你敲了哪几个端口,重放即可开门。所以端口敲门不适合对抗"已经在链路中间的人",它只是把公网扫描器挡在外面。要让敲门抗重放,就得升级到 fwknop 的 SPA(Single Packet Authorization)方案。

fwknop 用一条加密+HMAC 签名的 UDP 包代替"敲一串端口",报文里带时间戳和随机数,服务端用共享密钥或 GPG 验签,过期或重放的包直接丢弃。抓包者看不到明文序列,也改不动内容。如果你的服务器价值较高,建议直接上 fwknop,把 knockd 当作入门理解。

无论用哪种,几件事必须做:把敲门序列当成密码对待,别用连续端口;Interface 明确指定出口网卡,多网卡机器上配错会监听不到;knockd 日志要纳入监控,频繁的失败敲门是有人在探测的信号;逃生通道(VNC/IPMI 控制台)务必确认可用,否则一次配置失误就是一小时的停机救援。

用 systemd 管理 knockd 并做超时兜底

knockd 官方包自带 systemd unit,但默认配置有个坑:它监听的是网卡上的原始流量,一旦网卡名变了(比如从 eth0 变成 ens3,或者你换了云厂商),服务会启动失败或静默监听不到。启动前先确认网卡名:

ip -br link show
systemctl enable --now knockd
systemctl status knockd
journalctl -u knockd -n 50 --no-pager
tail -f /var/log/knockd.log

日志里应该能看到"listening on eth0"之类的信息。如果没有,多半是 Interface 写错了。

另一个必须补的兜底是"自动关门"。前面我们用反向序列手动关闭,但如果你忘了敲反向序列,那条放行规则会一直留在防火墙里,等于白做了敲门。更稳妥的做法是让开门命令自己带自毁定时器:

[openSSH]
    sequence    = 7000,8000,9000
    seq_timeout = 5
    command     = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j ACCEPT; \
                  sleep 60; \
                  /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
    tcpflags    = syn

这样无论你后续做什么,60 秒后那条规则都会自动消失,只留下已经建立的 SSH 会话。注意 command 里用分号和反斜杠续行时,knockd 是交给 /bin/sh -c 执行的,测试期间务必先手动跑一遍这几条命令确认语法无误。这种"开窗即自毁"的模式比手动关闭安全得多,也是我目前线上实际使用的写法。

把敲门序列换成动态口令

固定端口序列迟早会泄露——可能是你某次调试时截了图,也可能是配置文件被翻出来。进阶做法是让序列随时间变化,比如用 TOTP 的前几位作为端口偏移量。客户端和服务端各自持有同一个密钥,按当前时间算出同样的端口,敲完即失效,抓包者即使拿到这次序列,60 秒后也重放不了。这已经非常接近 fwknop 的效果,只是实现成本更低。

如果你不想自己写脚本,直接用 fwknop 是最省心的选择。它在服务端只监听一个固定 UDP 端口,收到 SPA 包先验签再决定是否放行,全程没有任何"端口序列"可以猜。配置比 knockd 稍复杂,但对安全性要求高的机器值得。

和 fail2ban 该选哪个

两者不冲突,定位不同。fail2ban 是"事后反应":从日志里看到失败就临时封 IP,攻击者已经和你的 sshd 打过照面。端口敲门是"事前隐藏":干脆不给对面交互的机会。对个人站来说,最稳的组合是:默认 DROP 22 + 端口敲门按需开门 + fail2ban 兜底(针对敲开窗口后可能的暴力尝试)+ 密钥登录禁用密码。多层叠起来,你的 SSH 暴露面就基本消失了。

最后提醒,任何安全加固都要留一条"退路验证"流程:改规则前先保留一个已登录的会话,改完另开会话验证,确认无误再退出旧会话。端口敲门这类动防火墙的操作,最大的风险从来不是被黑客攻破,而是管理员把自己关在门外。为此,建议在服务器上跑一个"看门狗"脚本,每隔几分钟检查一次防火墙里是否有合法的放行规则,一旦发现所有管理 IP 都被封死,就在几个小时后自动清空自定义规则恢复默认,作为最后的保险。安全不是把门锁到自己也进不去,而是在守住攻击面的同时,永远给自己留一把确定能用的钥匙。

Last modification:October 7th, 2026 at 09:24 pm

Leave a Comment