这台 2 核 1.9G 的小机器在公网上躺了快两年,我一直觉得改过端口就算安全了。今天翻了一遍 /var/log/secure,从 9 月 20 号到现在不到 25 天,Failed password 出现 101,093 次,平均每天 4,086 次。而 sshd -T 里那行 passwordauthentication,还是 yes。
意思很直白:这些尝试不需要成功很多次,只要有一次撞对了密码,服务器就归别人了。
所以先量,再动手。两条命令就能看清自己家的门被敲成什么样。
先量:25 天 10 万次,是谁在敲
zgrep -h "Failed password" /var/log/secure* | wc -l
zgrep -h "Failed password" /var/log/secure* | grep -oE "from [0-9.]+" | awk '{print $2}' | sort | uniq -c | sort -rn | head -10
本站的输出:
101093
18450 172.239.20.36
14225 5.231.242.178
12242 5.231.242.53
10788 163.5.210.171
6368 104.105.17.43
6325 172.238.114.181
5305 45.148.10.46
4626 5.231.242.198
4593 5.231.242.52
2226 77.239.124.120
前十名加起来 85,148 次,占 84%,剩下的一万五千多次散在几百个地址上。再看它们敲的是谁的账号:
zgrep -h "Failed password" /var/log/secure* | awk '{print $9}' | sort | uniq -c | sort -rn | head -5
54303 invalid
45993 root
141 mysql
108 www
94 ftp
五万四千次在赌 admin、user、ubuntu、test、oracle 这类可能不存在的用户名,四万六千次直接怼 root。没人需要知道我的用户名,脚本自己会一个一个试。
一句实话:封 IP 不是底线,关认证才是
动手之前我先想清楚了封 IP 能换来什么。
头号选手 172.239.20.36 从 10 月 11 号早上 6 点开始敲,到 12 号晚上 8 点半收工,38 个小时里塞进 9,321 次,之后再没露面。今天还在活跃的是 45.148.10.46,单日 449 次;另外一批地址每个只试 20 次就走,比如 147.90.182.25、14.103.213.90 这几个。
封 IP 封掉的是这一波人,明天换一批 IP 照样来。让十万次尝试变得毫无意义的动作只有一个:把密码认证关掉,脚本再敲一百万次,也永远拿不到那道门。
两件事都值得做,顺序别反。关认证是底线,封 IP 的收益是日志干净、少几万次无谓的 fork,附赠一点心理安慰。
动手前:确认密钥能登,再挂一颗后悔药
这一步比后面所有步骤都重要。要求两个前提:/root/.ssh/authorized_keys 里至少有一把你能用的私钥,而且云控制台还能兜底(阿里云 ECS 的 VNC 远程连接不需要 SSH 通)。
wc -l /root/.ssh/authorized_keys
cut -d' ' -f1,3 /root/.ssh/authorized_keys
本站三把,其中一把是日常在用的:
3 /root/.ssh/authorized_keys
ssh-rsa 1503255237@qq.com
ssh-ed25519 root@LittlesheepUAU
ssh-ed25519 hermes-automation
然后挂一条 15 分钟后自动还原的保险。改坏了它会自己把配置还回去,你还有机会重新连上:
STAMP=$(date +%Y%m%d-%H%M)
cp -a /etc/ssh/sshd_config /root/sshd_config.bak-$STAMP
setsid nohup bash -c "sleep 900; cp -a /root/sshd_config.bak-$STAMP /etc/ssh/sshd_config; systemctl reload sshd" </dev/null >/dev/null 2>&1 &
echo $! > /root/ssh-rollback.pid
新配置验证通过之后,kill $(cat /root/ssh-rollback.pid) 把它撤掉。十秒钟的事,能救回一整个下午,别省。
改哪一行:sshd_config 是先出现的赢
OpenSSH 的配置不是后面覆盖前面,而是第一条生效。所以往文件末尾追加一行 PasswordAuthentication no 完全没用,上面那行 yes 照样赢。必须把原来的行改掉,或者把旧行注释掉。
用编辑器手工改没问题,想照抄就用这段 python:
import re, pathlib
p = pathlib.Path('/etc/ssh/sshd_config')
s = p.read_text()
def setkv(s, key, val):
pat = re.compile(r'^[ \t]*' + key + r'[ \t]+.*$', re.M) # 只匹配未注释行
if pat.search(s):
return pat.sub('%s %s' % (key, val), s, count=1), 'replaced'
return s.rstrip('\n') + '\n%s %s\n' % (key, val), 'appended'
for k, v in [('PasswordAuthentication', 'no'),
('PermitRootLogin', 'prohibit-password'),
('LoginGraceTime', '30'),
('MaxAuthTries', '3'),
('AllowUsers', 'root')]:
s, how = setkv(s, k, v)
print('%-24s -> %-18s (%s)' % (k, v, how))
p.write_text(s)
改完文件里这几行长这样:
PermitRootLogin prohibit-password
PasswordAuthentication no
AuthorizedKeysCommand /usr/bin/timeout 5s /usr/local/share/aliyun-assist/plugin/config_ecs_instance_connect/ecs_config_instance_connect %u %f
LoginGraceTime 30
MaxAuthTries 3
AllowUsers root
PermitRootLogin prohibit-password 保留 root 用密钥登录,只是不许密码。MaxAuthTries 3 把一次连接能试的密码数从默认 6 次压到 3 次,LoginGraceTime 30 把等待时间从 60 秒压到 30 秒,公网机器上这两项比装什么工具都实在。AllowUsers root 会让五万多次 invalid user 在进认证之前就被拒,代价是以后新建用户要先把名字加进这一行,忘了就是你自己连不上。
阿里云那行 AuthorizedKeysCommand 一个字符都别动,控制台的远程连接和会话管理靠它注入临时公钥,删了就等于把自己的逃生通道拆了。
验证:三条命令
sshd -t # 先验语法,不通过就别 reload
systemctl reload sshd # reload 不踢已连上的会话,你现在这条 ssh 不会断
sshd -T | grep -Ei "^(passwordauthentication|permitrootlogin|allowusers|maxauthtries)$"
本站 reload 之后:
logingracetime 30
maxauthtries 3
permitrootlogin without-password
passwordauthentication no
allowusers root
prohibit-password 在 sshd -T 里会显示成 without-password,两者是同一个意思,别以为没生效。
最后换一台机器,用密码方式硬试一次:
ssh -p 19122 -o PreferredAuthentications=password -o PubkeyAuthentication=no root@你的服务器IP true
本站现在的返回:
root@47.115.212.51: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
服务端连 password 这个方法都不再提了,这才叫关干净。如果它还在问你密码,说明你改的那行不是文件里第一条,回去检查有没有重复项。
用 ipset 把高频 IP 挡在 sshd 之前
iptables 一条规则写一个地址也行,但十万次攻击背后是几百个 IP,逐条写会把自己写烦。ipset 一个集合搞定匹配,宝塔面板自己也在用这套(这台机器上有 bt_ip_filter 和 monitor_filter 两个集合)。
ipset create ssh_ban hash:ip hashsize 4096 maxelem 262144 timeout 604800 -exist
zgrep -h "Failed password" /var/log/secure* | grep -oE "from [0-9.]+" \
| awk '{print $2}' | sort | uniq -c | sort -rn | awk '$1>=2000{print $2}' > /root/ssh_ban_ips.txt
while read -r ip; do ipset add ssh_ban "$ip" -exist; done < /root/ssh_ban_ips.txt
iptables -I INPUT 1 -p tcp -m set --match-set ssh_ban src -m tcp --dport 19122 -j DROP
阈值我取的是 2000 次,本站命中 10 个地址。地址进集合之后连 TCP 握手都完不成,sshd 里连一条日志都不会留下。
验证别拿自己的公网 IP 试,那是自杀。把回环地址临时塞进去就行:
ipset add ssh_ban 127.0.0.1 -exist
timeout 12 ssh -p 19122 -o ConnectTimeout=5 127.0.0.1 true; echo "退出码 $?"
ipset del ssh_ban 127.0.0.1
timeout 15 ssh -p 19122 -o ConnectTimeout=8 -o BatchMode=yes 127.0.0.1 true; echo "退出码 $?"
本站被封时卡在 connect to host 127.0.0.1 port 19122: Connection timed out,退出码 255,规则计数器同时在涨,说明确实有包撞在规则上;把地址移出集合后同一条命令立刻能连上,退出码 0。
让封禁活过重启:别指望 rc.local
ipset 和 iptables 都不写盘,重启就没了。网上教程都让你改 /etc/rc.d/rc.local,先跑这两条再说:
systemctl is-enabled rc-local
systemctl is-active rc-local
本站返回 static 和 inactive,/etc/systemd/system/multi-user.target.wants/ 里也没有它,意思是这个文件根本不会被执行。crontab 反而是 active,所以恢复脚本挂 cron 更稳:
# /root/ssh-ban-restore.sh 里就是上面那三条命令,幂等,跑几遍都没事
( crontab -l 2>/dev/null | grep -v ssh-ban-restore; echo "@reboot /root/ssh-ban-restore.sh" ) | crontab -
脚本自己会先 ipset create ... -exist 再灌地址、再补规则,重启后跑一遍就恢复原样。删掉名单里某个地址用 ipset del ssh_ban <IP>,整条规则下线用 iptables -D INPUT -p tcp -m set --match-set ssh_ban src -m tcp --dport 19122 -j DROP。
回滚与两个附带发现
配置回滚就一句:cp -a /root/sshd_config.bak-* /etc/ssh/sshd_config && systemctl reload sshd,加上那颗十五分钟的自动保险,理论上你不会失联。
顺带说一个隐形大文件:这台机器的 /var/log/btmp 已经 38M、102,867 条失败登录记录,它是 binary 格式,lastb 才能读。日志轮转没管它的话会一直长,磁盘告急的时候值得翻一眼,清理思路在服务器磁盘被谁吃掉了那篇里写过。另外那种「每个 IP 只试 20 次就换地址」的慢速访问,恰好是各类自动封禁工具的盲区,别指望一条阈值规则能全兜住。
SSH 这一层收完之后,还有半边在 WordPress 里,登录页同样天天被人踹,动作都写在WordPress 安全加固实操那篇,抓异常请求的姿势可以看Nginx 日志排查。两年前我改了端口就觉得安全了,今天才知道门一直开着。你要是也从没看过自己的 /var/log/secure,花两分钟跑第一条命令,数字大概会让你今晚想动手。



![[教程][服务器]在阿里云服务器上卸载AliYunDunMonito(阿里盾)-小绵羊的小窝](https://www.littlesheep.cc/wp-content/uploads/2025/12/114612929920251229005735.png)

![[免费][教程]子比主题美化,优化教程-小绵羊的小窝](https://www.littlesheep.cc/wp-content/uploads/2026/09/3b06fd6a0520260920160148.webp)
![[免费][插件]将WordPress图片上传到LskyPro蓝空图床的插件-小绵羊的小窝](https://www.littlesheep.cc/wp-content/uploads/2026/09/b9e1998d8420260920160152.webp)

![[教程][笔记]Centos7宝塔下安装ImageMagick,无法安装webp扩展解决办法-小绵羊的小窝](https://www.littlesheep.cc/wp-content/uploads/2026/09/528d41b05d20260920155615.webp)
![[资源][教程]AI SEO - WordPress插件-小绵羊的小窝](https://www.littlesheep.cc/wp-content/uploads/2026/01/9f396016b220260112151830.png)



暂无评论内容