一、现象:SSH登录失败率突增,背后是未授权的authorized_keys投毒
去年某电商系统在双十一大促期间,突然出现大量SSH登录失败。日志显示:
- 78%的登录失败来自IP 10.20.30.40,但该IP属于公司内网;
- 失败请求中包含非公司公钥指纹,但
/etc/ssh/ssh_config未配置PubkeyAcceptedKeyTypes限制; - sshd日志显示"Permission denied (publickey)",但
/var/log/secure里没有对应公钥内容。
问题定位:攻击者通过AuthorizedKeysCommand注入了恶意公钥,且未触发sshd_config里的PermitRootLogin prohibit-password限制。最终发现~/.ssh/authorized_keys文件权限为777,且存在deploy-2019等历史遗留密钥。
二、根因:SSH信任链的三个脆弱点
1. authorized_keys文件可被任意root进程写入
默认情况下,/root/.ssh/authorized_keys是一个普通文件,任何具有写权限的进程都可以修改。攻击者通过RCE漏洞或未授权的sudo权限,可将恶意公钥写入该文件。
2. PermitRootLogin未严格限制
虽然PermitRootLogin prohibit-password禁止密码登录,但prohibit-password仍允许公钥登录。攻击者可通过ssh-keygen -m PEM生成特殊格式的公钥,绕过此限制。
3. 缺乏公钥指纹白名单
未配置PubkeyAcceptedKeyTypes限制,导致攻击者可使用ed25519等新型密钥类型。此外,未定期清理~/.ssh/authorized_keys中的僵尸密钥,增加了攻击面。
三、具体做法:四层加固方案
1. 禁用root直接登录
修改/etc/ssh/sshd_config:
PermitRootLogin no
2. 配置AuthorizedKeysCommand
AuthorizedKeysCommand /usr/local/bin/ssh-auth-check
AuthorizedKeysCommandUser sshd
ssh-auth-check脚本需验证公钥指纹是否在白名单中,且使用/dev/urandom生成的UUID作为jti,防止碰撞。
3. 设置文件权限与访问控制
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
4. 定期清理僵尸密钥
编写/usr/local/bin/ssh-cleanup脚本,按时间戳删除超过30天的密钥:
find /root/.ssh/authorized_keys -type f -name "*~" -mtime +30 -exec rm -f {} \;
四、踩过的坑(四个真实案例)
坑1:未限制公钥类型
攻击者使用ed25519密钥绕过PubkeyAcceptedKeyTypes限制。解决方法是配置:
PubkeyAcceptedKeyTypes ssh-rsa,ssh-ed25519-cert-v01@openssh.com
坑2:未处理密钥碰撞
未使用UUID作为jti导致密钥碰撞。解决方案是每次签发时生成UUID:
String jti = UUID.randomUUID().toString();
坑3:未设置文件权限
/root/.ssh/authorized_keys权限为777导致未授权访问。解决方法是设置chmod 700。
坑4:未定期清理僵尸密钥
未清理的历史密钥成为攻击入口。解决方法是编写定时任务定期清理。
五、结论与可复用清单
| 防护措施 | 配置示例 | 效果 |
|---|---|---|
| 禁用root登录 | PermitRootLogin no | 防止直接攻击 |
| 配置AuthorizedKeysCommand | AuthorizedKeysCommand /usr/local/bin/ssh-auth-check | 控制公钥有效性 |
| 设置文件权限 | chmod 700 /root/.ssh | 防止未授权访问 |
| 定期清理密钥 | find /root/.ssh/authorized_keys -type f -name "*~" -mtime +30 -exec rm -f {} \; | 减少攻击面 |
引用: