凌晨两点,一台跑着内部测试业务的Linux服务器突然对外流量飙升。负责运维的同事第二天一早收到云平台告警,登上去一看,CPU满载、内存被吃干净、一堆奇怪的进程在/usr/bin下安了家。再查SSH登录日志,发现前一天的深夜两点到现在,来自一个陌生IP的登录记录多达几百次,最终在某一次尝试后成功建立会话。这就是今天要聊的主题——SSH暴力破解与弱口令攻击。起因并不复杂:测试服开了公网SSH端口,密码是root/admin这类“图省事”的组合,于是成了攻击者手里最顺手的一枚棋子。
这篇文章不是纯理论分析,而是结合这次应急响应的全过程,把SSH暴力破解的攻击路径、弱口令为什么这么容易得手、失陷之后如何排查清除、以及加固方案逐一讲清楚。内容适合所有运维、研发和偶尔兼职管服务器的朋友,尤其是手上有“环境不重要、密码随便设”的测试机的人。
1. 事故复盘:一台测试服的失陷始末
1.1 失陷前的“安全配置”到底有多脆弱
这是一套用于接口联调的测试环境,部署在一台云服务器上,外网IP直接暴露,没有安全组白名单,22端口开着。系统是Linux,发行版比较老,sshd服务多年没升级过,密码认证打开,root可直接登录。密码设置属于“好记”那一类,长度8位,完全在常用弱口令字典的攻击范围内。
这类环境在很多团队里都有:上线时图快,设计上默认“内网才访问”,实际上为了方便联调,防火墙规则早就放开了。跑在里面的业务虽然是测试数据,但服务器本身是生产网的邻居——和正式环境的数据库、消息队列、配置中心在同一个VPC内网里。这种“看起来不值钱、实际很关键”的定位,正是它沦陷后让人头大的主要原因。
1.2 从首次爆破到成功登录的时间线梳理
攻击不是一天完成的。翻日志可以看到清晰的节奏:
- 攻击持续近12个小时,前6个小时以周期性尝试为主,每几分钟来一轮,每轮尝试几十个密码,大多集中在root/admin/123456这类组合,速度不算快,频率也不算高,在日志里嵌在一堆正常扫描里非常不起眼。
- 第6小时之后尝试速度明显加快,目标用户从root扩展到多个系统用户名,把常见弱密码组合加用户名组合都过了一遍。
- 深夜时段,某一次尝试以root账号配一个8位弱口令成功登录。和之前被拒绝的几百条记录相比,这条的校验结果从failed变成了accepted。
成功登录后的行为也很典型:先关闭了命令历史记录功能,随后检查了系统用户列表、CPU信息和网络连接,紧接着下载了解压执行了一个挖矿程序,并把矿池地址写进了一个看似正常的系统定时任务里。整个过程不到10分钟,动作熟练得就像套模板。
1.3 当时为什么没被拦下来
回头审视,至少有四道防线是缺失的。第一,没有启用fail2ban或等价机制,暴力尝试期间系统层面完全没有触发自动封禁。第二,SSH端口直接暴露在公网,没有任何来源IP白名单。第三,安全组和主机防火墙虽然允许22端口访问,但并未设置任何访问控制。第四,也没有做登录成功后的告警通知,攻击者半夜进去的时候,团队里没有收到任何信号,等发现时已过去好几个小时。
测试环境缺监控,是内部常见的认知盲区。大家默认“这机器丢了也不影响线上”,却忘了它一旦被控制,就可能成为横向渗透进入内网的跳板。这次事件没造成更大损失,纯粹是运气好——攻击者的目标只是挖矿,没有进一步对内网做针对性操作。
2. SSH暴力破解的技术逻辑与攻击路径拆解
2.1 SSH认证机制里能被攻击者利用的薄弱点
SSH登录支持多种认证方式,暴力破解针对的是密码认证。原理不复杂:客户端把用户名和密码通过SSH协议交给服务端校验,服务端查询shadow文件中的密码哈希,比对后决定是否放行。攻击者的思路就是反复提交候选密码,直到某一次命中。
这里面值得注意的一个技术细节是,密码在网络传输中是加密的,攻击者无法通过抓包直接拿到明文密码,但他们不需要拿密码,他们只需要猜中密码。只要服务端允许无限次尝试、没有锁定机制、没有速率限制,暴力破解在数学上就一定会在某个时间点成功——时间长短取决于密码复杂度和字典大小。
另一个常被忽略的角度是,攻击者手握海量“撞库”字典。所谓撞库,就是拿其他网站的泄露账号数据来试当前系统的账号。测试环境的运维同学很容易在多个平台复用同一个密码,这进一步放大了风险。
2.2 三种常见的暴力破解形态与判别特征
暴力破解不是只有一种打法,不同形态对应不同日志特征,也给运维同学提供了不同的判别信号。
第一类是快速字典爆破。攻击者用工具对单一来源IP发起高频尝试,常见工具包括hydra、medusa等,在短时间内把常用弱密码字典整体过一遍,每秒钟可以尝试数次到数十次。这类攻击特征最明显:日志里同一IP短时间内出现大量failed password记录,频率远高于正常人类操作,fail2ban这类防护工具最容易拦截。
第二类是分布式慢速爆破。攻击者租用一个IP池或僵尸网络,把尝试次数分摊到几百上千个IP上,每个IP每分钟只试一两次。这类攻击最大的迷惑性在于,从任何一个单一IP看,频率都不足以触发频率控制,但整体尝试节奏从未停止。日志看起来是“全球各地各种IP来了一圈”,短时间不汇总分析很难发现规律。
第三类是撞库式尝试。攻击者拿到其他平台泄露的用户名密码组合后,在SSH登录上做批量验证。特征是用户名比较“真实”,不是admin这种通用名,甚至能匹配系统里实际存在的账号,密码也符合用户个人习惯,这种最危险,因为它不再依赖密码本身弱,而是依赖用户密码复用。
日志特征对照表:
| 攻击形态 | 日志特征 | 易防御程度 |
|---|---|---|
| 快速字典爆破 | 单一IP短时段高频failed记录 | 容易,频率拦截即可 |
| 分布式慢速爆破 | 多IP低频尝试、总量大 | 较难,需聚合分析 |
| 撞库式尝试 | 真实用户名+真实密码泄露组合 | 较难,需多因素认证 |
2.3 攻击者拿到口令后的标准动作
这次事件里攻击者拿到root权限后做的事非常有代表性:关闭历史记录、查看系统信息、部署挖矿程序、设置定时任务持久化。这套动作在真实攻击中几乎是“开箱即用”的流程。
关掉历史记录,是为了避免后续登录者通过history看到攻击者的操作痕迹;查看系统信息,是为了确认CPU核数、内存大小和带宽,判断这台机器值得投入多少算力去挖矿;部署挖矿程序后,往往还会替换sshd配置文件或植入公钥,保证即使改了密码也能重新进来。定时任务则负责维持持久性,确保挖矿进程被清理后能自动拉回来。
如果攻击者的目标不是挖矿而是定向破坏或内网渗透,后续动作会更危险:下载代理工具作为跳板,扫描内网其他机器,尝试用同一套口令登录数据库或运维管理后台。这也是我一直强调“测试服被攻陷不等于只损失一台机器”的原因——它真正的价值在于帮你拆掉生产网的第一道防线。
3. 从应急响应到溯源分析:如何确认失陷与清除后门
3.1 第一反应:隔离网络而不是先杀进程
发现服务器被攻陷后,我的第一反应永远是先断网,而不是急着去杀进程、删文件。原因很简单,挖矿程序往往具备自动恢复能力,你在这台机器上执行的操作越多,越可能被攻击者预埋的后门再次接管。先把安全组出入方向全部关闭,或直接停掉这台机器的外网访问,确保攻击者无法继续连接,再从本机做分析。
断网之后,第二步是冻结现场,而不是直接清理。应该先保留下证据,再做后续处理。这次事件中,第一步就是对/var/log/secure做了完整备份,并保存了当时的进程列表、网络连接列表以及内存转储。这些是分析攻击来源和攻击手法的核心依据。
3.2 核心检查点:登录记录、异常进程、持久化任务
检查登录记录是最直观的一项。在RedHat系系统上,SSH日志通常位于/var/log/secure,在Debian系系统上位于/var/log/auth.log。用grep过滤Accepted和Failed关键字,就能还原完整的登录情况。尤其要关注Accepted的出现时间、来源IP和登录用户,这些是定位“钥匙是哪把门打开”的关键线索。
进程排查也很有必要。挖矿进程通常占用高CPU,但有些木马会做伪装,把进程名改成看起来很正常的名字。所以不能只看表面同名进程,还要结合路径判断。也就是说,光看进程名不可靠,要看进程的可执行文件路径、启动时间和启动参数。这次排查时发现进程名是“-bash”加一串随机后缀,但路径在/usr/bin下一个新建目录里,伪装迹象非常明显。
持久化机制是攻击者“留后路”的主要方式,必须逐一排查:crontab -l和/etc/cron.*里的定时任务、systemd的service/timer单元、/etc/rc.local启动项、以及SSH的authorized_keys文件。尤其要仔细看authorized_keys——攻击者一旦把公钥写入这里,无需密码就能再次登录,这是创伤最大的后门方式。
3.3 清除后门的边界:哪些情况该重装
清除后门的操作边界,是应急响应里经验值含量最高的部分。如果攻击者只拿到了普通用户权限,且确认没有提权能力,删除恶意文件和修复安全配置后,还有保留系统的价值。但如果攻击者拿到了root权限,我会直接建议重装系统,而不是在原系统上“清洗干净”。
这里面的原因不在于清不干净,而在于“你不知道你不知道什么”。拿到root权限意味着攻击者可能改过内核模块、替换过系统库、插入过日志清理程序、修改过二进制文件。你检查完所有已知后门,也未必能发现一个精心隐藏的rootkit。我第一次处理这类事件时,花了整整一天清理、修补、确认,最后第二天攻击者还是通过一个我完全没有预料到的伪装系统服务回来了。
拒绝重装还有一个现实代价:你永远不敢确认这台机器是干净的,后续部署什么都有隐患。而重装系统加上只恢复必要的数据,才是确定性最高的处理方式。测试服的数据本来就不需要长期保留,重装的成本很低,这次事件最终也是走重装路线解决的。
4. 防御加固方案:让暴力破解无处下口
4.1 SSH配置层加固:从允许密码到只认密钥
配置层的加固是性价比最高的第一道防线。核心思路是关闭密码认证、拒绝root直接登录、限制可登录的用户列表。具体到sshd_config文件里,这样修改:
# 禁止密码认证,只允许密钥认证 PasswordAuthentication no PubkeyAuthentication yes # 禁止root直接登录,改用普通用户登录后su切换 PermitRootLogin no # 只允许指定用户登录,避免用户名枚举 AllowUsers deploy admin # 限制单次会话内最大认证尝试次数 MaxAuthTries 3 # 缩短无操作断连时间,减少挂死会话残留 ClientAliveInterval 300 ClientAliveCountMax 2改完配置后最好先做个语法检查再重启,避免一时手误把远程访问弄断:
sshd -t systemctl reload sshd这套配置的背后逻辑很明确:密钥认证的安全性不在于“密钥太难猜”,而在于私有密钥是一份长度随机的随机数文件,暴力破解的空间比字母数字组合的8位密码大无数个量级。关闭密码认证等于把暴力破解的大门彻底关上。禁止root直接登录同样很重要,因为攻击者首要目标就是root账号,你让他面对一个需要先破解普通用户、再提权或恰好拿到sudo权限的多步骤路径,攻击难度能上一个台阶。
很多团队会担心“切换密钥登录后新同事入职不方便”,这是意识问题:ssh-keygen生成密钥、把公钥添加到服务器的authorized_keys文件,整个过程一分钟搞定,比记住一个弱密码再被爆破后的应急成本低得多。
4.2 fail2ban的配置要点与常见误区
如果由于业务限制确实无法立刻关闭密码认证,fail2ban可以作为过渡阶段的兜底方案。它的原理是监控sshd日志,检测到某个IP在指定时间内连续多次认证失败后,动态在防火墙层加入封禁规则。
配置重点在jail.local中覆盖默认参数:
[sshd] enabled = true maxretry = 3 findtime = 10m bantime = 1h这三个参数的语义和取舍值得展开说明。findtime是统计窗口,即多长时间内的尝试会被累计;maxretry是窗口内允许的最大失败次数;bantime是封禁时长。maxretry=3意味着在10分钟内,同一个IP只要错3次就被拉黑1小时。这个阈值既能挡掉快速爆破,又能避免正常用户输错几次密码就被封掉。如果担心分布式慢速爆破,可以适当增加findtime到30分钟或1小时,代价是日志监控内存稍有上升,收益是更长周期的低频尝试也能被识别。
实际部署时有两个高频坑。第一个坑是某些系统上fail2ban默认只拦截IPv4,不拦截IPv6,攻击者若同时拥有IPv6地址,可以直接绕过封禁——很多渲染“fail2ban完全没用”的文章,其实只是在IPv6上漏了。
第二个坑是日志路径不匹配。不同发行版、不同sshd版本、甚至不同容器场景下,日志路径和日志格式都可能不同,filter规则匹配不到就统计不到失败次数。部署完成后一定要用fail2ban-regen命令或手动模拟错误登录,确认能否看到ban动作生效,不能装完就以为万事大吉。
4.3 网络层与审计层:不把门暴露在外面才是最终解法
网络层面的思路和SSH配置逻辑完全不同:SSH配置提升了攻击难度,而网络层直接降低了被攻击概率。如果22端口根本不对公网开放,暴力破解的工具再强也打不到你身上。最直接的做法是设置来源IP白名单——云平台的安全组里把22端口的来源限制为办公网出口IP或指定的跳板机IP,只审计自己的固定IP能访问。
对于需要多方远程联调的场景,推荐用堡垒机或跳板机架构,替代直接暴露SSH。团队成员统一通过堡垒机登录,堡垒机再转发到目标服务器。这样既保留了远程访问能力,又让目标机器的22端口只对堡垒机开放,外部暴力破解的空间被压缩到只有一个入口,而且入口由堡垒机统一管控。审计层的价值同样不可替代:登录成功通知、SSH公钥变更监控、系统定时任务检查,这些配合起来,即便攻击真的发生,也能把发现时间从“几天后”缩短到“几分钟内”。这次事件如果当时有登录成功通知,一台测试服在深夜登录了root账号的提醒,足以让运维同事当晚就处置掉挖矿程序。
5. 常见问题与排查技巧实录
5.1 失陷后常见的五个疑问
问题一:改一下SSH端口,是不是就不用担心暴力破解了?
改端口只能减少被普通扫描器随机命中概率,不能阻挡针对性攻击。互联网上每天有大量端口扫描器在跑全端口扫描,22、222、2222、高端口一样会被探测到。端口号是一种弱防护,可以配合其他手段使用,但不能作为唯一防线。
问题二:为什么我配置了fail2ban,还是被爆破了?
检查顺序依次是:日志路径是否正确匹配、filter正则是否能命中failed关键字、IPv6封禁是否启用、ban时间是否太短。特别要注意有些攻击是分布式的,每个IP的失败次数都低于maxretry,从单机看确实无法触发封禁,这时就需要密钥登录+IP白名单来兜底。
问题三:服务器被攻陷后,直接杀死进程就行吗?
不行。杀进程只是治标,攻击者可能已经写入了持久化任务。需要有步骤地排查定时任务、启动项、SSH公钥后再清理。最稳妥方案是重装系统,而不是杀进程删文件。
问题四:普通用户权限被拿到,需要重装吗?
如果确认攻击者没有提权成功、没有替换系统文件,可以不用重装,但要立即修改密码、清理authorized_keys、检查sudo权限。不过实际操作中你很难确认“没有提权成功”,安全起见,还是重装最省心。
问题五:测试服可以不管吗?
从这次事件应该能看出,测试服的沦陷不止一台机器的问题。测试环境和生产环境通常共享VPC、共享账号体系、甚至共享数据库白名单,测试服被控制的后果,完全可能与直接攻击生产环境等效。
5.2 日志排查的高效命令模板
处理事件时下面这套命令路径效率最高,都是直接在Linux终端下执行的,粘贴时改成自己的环境变量即可:
# 查看SSH登录成功记录 grep Accepted /var/log/secure # 查看SSH登录失败记录,统计来源IP次数 grep Failed /var/log/secure | awk '{print $(NF-3)}' | sort | uniq -c | sort -nr # 查找异常登录用户 lastlog | grep -v 'Never logged in' # 检查当前所有SSH连接 ss -anutp | grep :22 # 查看系统所有监听端口 ss -anutpl # 检查有无新建普通用户 tail -n 5 /etc/passwd # 列出所有uid=0的账号(root提权后门) awk -F: '$3==0 {print $1}' /etc/passwd # 检查SSH信任公钥 cat /root/.ssh/authorized_keys # 查看定时任务 crontab -l cat /etc/cron.d/*每次应急响应我都建议先做日志备份再执行检查命令,把当时的状态固定下来,再去操作。不然边查边改,最后连自己都分不清哪些是攻击者的行为,哪些是自己排查动作产生的痕迹。
不过这里要特别说明一个经验:判断失陷不能只看单一指标,比如光看CPU高、光看某个异常文件都不够。攻击者也很聪明,会把挖矿进程绑在多核CPU的某些核上,伪装成系统服务。要把进程树、启动时间、SSH登录记录、定时任务等多个信号放在一起比对,才不至于误判或漏判。
6. 事件收尾阶段容易忽视的三件事
应急响应其实没有在“系统重装完成”那一刻就结束,后面还有几件事特别容易被忽视,但重要性不亚于前面的处理。
第一件是密码体系的全面刷新。攻击者可能已经拿到了这台机器上所有账户的密码哈希,也可能通过这台机器已经摸到了内网其他服务的口令。凡是这台服务器能访问到的服务、所有复用过的密码,都应该考虑更新一轮。尤其是root密码、运维平台密码、数据库账号密码,必须全部换掉。不能只改被攻陷机器的密码,其他环境里的同一密码继续用,那就是给攻击者留了条后路。
第二件是攻击入口的复盘审视。这台机器到底是怎么暴露的?是云平台安全组规则太宽,还是运维为了联调临时放开的,还是因为绑定了弹性公网IP根本没人记得?找到这个入口,比单纯改个密码更值得记录。我处理过不少后续又被攻破的案例,几乎都是因为“改了密码,但忘记修复原来的漏洞”,这是最可惜的场景。可以在复盘报告里单独列一条规则:任何暴露在公网的SSH端口都必须有白名单,不允许裸奔。
第三件是监控告警的落地配置。如果不想下次凌晨再被挖矿程序吵醒,最好的方式就是让告警替你值班。登录成功通知、CPU超过阈值告警、外联可疑端口告警,这三条都值得设置上。自动化程度不必高,能第一时间发条告警消息出来就行,人不需要24小时盯着,系统帮你盯。配合上一条,至少可以保证下一次攻击发生时,发现时间是以分钟计,而不是以天计。
按我看,测试环境的安全策略完全可以直接复制生产环境的等级去要求。测试服没有生产数据并不能天然免疫攻击者的恶意,它反而因为“没人关注”成为最容易踩进去的那块泥地。暴力破解技术门槛不高,弱口令问题也在很多团队里普遍存在,两者一旦碰到一起,就是文章标题写的那件事——一次由弱口令引发的测试服沦陷。希望大家看完这篇之后,都回去看一眼自己手里那台“不重要的”服务器,看看它的SSH端口还对着公网裸奔着没有。