对 OpenSSH 用户名枚举的可重现再调查,基于统计分析。
本项目重新调查了 CVE-2016-6210,一个已知的 OpenSSH 定时侧信道漏洞,旨在确定其在现代 Ubuntu Server 使用默认 PAM 配置时是否仍然可观测。
项目并未假设已公开的行为仍然适用,而是通过手动探测、Hydra 和 Metasploit 收集认证计时测量值,并运用 韦尔奇的 t 检验 和 科恩的 d 值 来区分真实的计时信号与测量噪声。
研究发现,在测试的默认配置下 没有统计上显著的计时差异,这证明了可重现实验与基于证据的安全声明验证的价值。
⚠️ 法律声明
本项目完全在自有的隔离实验室环境中进行。所有发现仅适用于所测试的配置。切勿测试不属于你或未经明确书面授权的系统。
用户名枚举 —— 即无需有效凭据即可判断远程系统上是否存在特定用户名的能力。它是攻击链中迈向账户沦陷的关键第一步:``` Reconnaissance → [User Enumeration] → Password Attack → Access ↑ This project investigates here
如果攻击者能够通过分析服务器响应区分“此用户存在”与“此用户不存在”,他们就能显著缩小后续暴力破解或凭证填充攻击的密钥空间。
SSH 是常见目标,因为它几乎普遍暴露在外、处理密码认证,且旧版本在有效与无效用户名之间存在可测量的时间差异(CVE-2016-6210)。
**本调查提出两个问题:**
1. 默认配置下的 Ubuntu 22.04.5 LTS 上的现代 OpenSSH 是否通过响应消息、时间或工具报告的信号泄露用户名存在性?
2. 如果攻击者无论如何尝试,它会留下哪些痕迹?这些痕迹的检测可靠性如何?
---
## 🖥️ 实验室设置
所有测试均在完全隔离的仅主机虚拟网络中进行,无互联网暴露。
| 机器 | 操作系统 | 角色 | IP | SSH 版本 |
|---------|-----------------------------|-------------------------------------------|------------------|------------------|
| 攻击机 | Kali Linux 2024.1 | 攻击工具、分析脚本 | 192.168.56.5 | — |
| 目标机 | Ubuntu Server 22.04.5 LTS | 运行 **默认** 配置的 OpenSSH | 192.168.56.10 | OpenSSH 8.9p1 |
**目标 SSH 配置(`/etc/ssh/sshd_config` 默认值):**```
PasswordAuthentication yes
UsePAM yes # Key setting — normalises timing via dummy hash
PermitRootLogin prohibit-password
MaxAuthTries 6
LogLevel INFO
UsePAM yes 是关键的安全加固设置。它迫使 OpenSSH 为不存在的用户执行一次虚拟的 bcrypt 计算,以匹配真实密码检查的耗时。
此项设置是专门为应对 CVE-2016-6210 而引入的。
每种攻击方法均作为独立试验运行,且日志状态为干净状态:```bash
sudo truncate -s 0 /var/log/auth.log
sudo cp /var/log/auth.log ~/evidence/trial-N-auth.log
每次试验收集的证据:
- 工具 stdout/stderr(逐字保存)
- 目标上的 `/var/log/auth.log`
- 通过 `manual_ssh.py` 中的 `time.perf_counter()` 获取的响应定时样本
- 在任何认证尝试之前获取的 SSH 横幅
### 攻击方法
| 方法 | 工具 | 词表 | 目的 |
|-----------------------|--------------------------------------|-------------------------|-------------------------------------------------|
| 手动 SSH | `ssh` CLI + Paramiko | 50 个常见用户名 | 基线;检查原始响应 |
| Hydra 暴力破解 | `hydra` | 相同 50 个 | 自动化;利用 Hydra 内置的枚举模式 |
| Metasploit 模块 | `auxiliary/scanner/ssh/ssh_enumuser` | 相同 50 个 | 框架的专用枚举模块 |
| 横幅指纹识别 | 自定义 `BannerFingerprinter` | N/A | 无认证版本泄露,CVE 检查 |
| 定时分析 | 自定义 `ResponseAnalyzer` | 有效与无效子集 | 统计侧信道检查 |
---
## 构建了什么
本项目不仅仅运行工具——它将所有攻击和检测逻辑封装在一个结构化的 Python 代码库中,并提供了一个编排器,用于端到端运行整个流水线。
### 攻击工具 (`src/attack_tools/`)
**`ManualSSHEnumerator`** — 使用 `paramiko` 对每个用户名测试 N 次,记录精确的定时、结果类型和 SSH 横幅。计算每个用户名的均值/标准差。关键的是,它**不**在尝试之间重用连接,确保每个样本捕获完整的服务器端处理时间。
**`BannerFingerprinter`** — 通过原始 TCP 套接字抓取 SSH 横幅(无需凭证)。解析实现名称、版本字符串和操作系统提示。与本地 CVE 注册表交叉引用。像 `OpenSSH_8.9p1 Ubuntu-3ubuntu0.6` 这样的版本揭示了确切的服务器软件——足以在尝试任何认证之前识别已知漏洞。
**`HydraAutomation`** — 围绕 Hydra 的子进程包装器。解析 stdout 以提取成功登录、错误消息和 Hydra 自身的枚举判定(`does not support user enumeration`)。
**`MetasploitScanner`** — 写入临时资源脚本并通过子进程驱动 `msfconsole`。解析输出以进行加固检测和任何找到的用户名。
### 检测工具 (`src/detection_tools/`)
**`LogParser`** — 基于正则表达式的 auth.log 解析器,支持五种 SSH 事件类型:`failed_invalid_user`、`failed_valid_user`、`pre_auth_reject`、`accepted`、`disconnected`。返回带有时间戳、事件类型、用户名、源 IP 和端口的结构化事件字典。
**`ResponseAnalyzer`** — 对来自有效和无效用户名的定时分布执行韦尔奇 t 检验。计算定时差值(毫秒)、p 值、Cohen's d 效应量和通俗结论。阈值:delta ≥ 5ms 且 p < 0.05 触发侧信道警告。
**`EnumerationDetector`** — 四种检测模式:
- **快速用户名探测**:滑动窗口 - 同一 IP,60 秒内 ≥10 个不同用户名
- **词表关联**:尝试的用户名与已知攻击列表之间的匹配率
- **顺序定时**:尝试间隔的变异系数(低 CoV 表示工具)
- **分布式探测**:来自多个 IP 的相同用户名(凭证填充侦察)
**`AlertingSystem`** — 轻量级告警发射器。生成带时间戳的 JSON 告警到 stdout。可根据需要扩展电子邮件/SIEM/Webhook 集成。
### 编排器
**`run_investigation.py`** — CLI 驱动程序,按顺序运行所有四个阶段并将结果写入 `data/results/`。使用 `--help` 获取完整用法。```bash
python run_investigation.py \
-target 192.168.xx.xxxx \
-usernames data/wordlists/common-usernames-50.txt \
-log data/sample-logs/auth.log \
-known-valid root ubuntu \
-samples 10
Hypothesis tested: Does OpenSSH return a different error message for a non-existent user than for an existing user with a wrong password?```bash
$ ssh [email protected] Permission denied (publickey,password).
$ ssh [email protected] Permission denied (publickey,password).
**Result:** 响应字节级完全一致。该协议未泄露任何信息。
**Why:** 自 OpenSSH 7.3 起,`UsePAM yes` 会强制服务器对不存在的用户执行一次傀儡 `crypt()` 操作,从而在时间上和错误路径上与真实认证失败保持一致。此修复是直接针对 CVE-2016-6210 的回应。
---
### 发现 2:未检测到计时侧信道
**测试假设:** 即使错误消息匹配,是否存在可被统计利用的可测量的有效用户名与无效用户名之间的计时差异?
对 50 个用户名分别采集了 10 个计时样本。将系统确认的已知有效用户与无效用户池进行比较。
| 指标 | 值 |
|---------------------------|-------------------------------------|
| 无效用户平均计时 | ~312 ms |
| 有效用户平均计时 | ~311 ms |
| 差异 | **~1 ms** |
| Welch t 检验 p 值 | > 0.40 |
| 结论 | **无法区分侧信道** |
~1ms 的差异远低于 5ms 的噪声阈值,且不具有统计显著性(p >> 0.05)。OpenSSH 的虚拟哈希计算是有效的。
---
### 发现 3:Hydra 报告不支持枚举
Hydra 的 SSH 枚举模式依赖于三种信号之一:不同的错误消息、不同的计时或不同的连接行为。在这三者均被归一化后,Hydra 明确报告:```
[ERROR] target ssh://192.168.56.10:22/ does not support user enumeration
[STATUS] 50/50 tries completed, 0 valid logins found
观察到的副作用: 尽管枚举失败,所有 50 次尝试都会被记录在 /var/log/auth.log 中,包含源 IP、时间戳和尝试的用户名。攻击者的存在完全可见。
auxiliary/scanner/ssh/ssh_enumuser 模块在尝试枚举前会从横幅中检查 OpenSSH 版本。版本 ≥ 7.3 且设置了 UsePAM yes 的会被标记为已加固,模块会提前退出:```
[] 192.168.xx.xxxx:22 - SSH - Checking for vulnerability
[] 192.168.xx.xxxx:22 - SSH - Target is not vulnerable: OpenSSH 8.9p1 (hardened)
### 发现5:即使枚举失败,检测也是可靠的
从防御者的角度来看,关键见解是:**攻击即使未成功也会产生噪音**。所有四种检测模式都正确触发了对收集到的auth.log的分析:
| 检测 | 触发条件 | 严重程度 |
|----------------------|-----------------------------------------------|----------|
| 快速用户探测 | Kali IP在60秒内探测了50个用户名 | 高 |
| 词库关联 | 48/50个尝试的用户名匹配词库 | 高 |
| 时序分析 | 尝试间变异系数=0.04(工具签名) | 中 |
| 仅横幅探测 | 在发送任何用户名之前进行预认证断开 | 低 |
---
## 思考过程
### 为什么首先进行手动SSH枚举?
从手动测试开始的直觉在方法论上是合理的:在信任工具输出之前,你需要了解原始协议实际说了什么。运行`ssh ghost@target`并观察确切的错误消息会告诉你是否存在*任何*可枚举的内容,然后再投入时间进行自动化。
第一个观察是,无论用户是否存在,`Permission denied (publickey,password)`看起来都是相同的,这是核心发现。后续所有内容都是对该结果的验证。
### 所做的假设(以及重新审视)
最初的假设是Hydra和Metasploit会比手动测试*更*有能力,因此如果手动失败,工具可能仍然成功。这在预期方向上结果是错误的,但在*原因*上是正确的:工具在这里并不会增加能力,因为协议本身并不泄露信号。工具只是对同一协议的自动化。
第二个值得审视的假设:第一次手动尝试明显比后续尝试慢,而成功密码的尝试很快。这最初被解释为潜在的时间信号。经过反思,减速是由于新网络状态下的TCP连接建立开销(ARP解析、连接设置),而不是服务器端处理时间。控制这一点——通过在TCP握手后从`time.perf_counter()`开始测量,或丢弃第一个样本——会更加严谨。`ManualSSHEnumerator`实现通过收集每个用户名10个样本并报告均值/标准差来解决这个问题,这稀释了第一个样本的噪音。
### 项目期间发生了什么变化
最初的范围很窄:运行三个工具,记录它们是否有效。项目向两个方向发展:
**向内(更深入的分析):** 当最初的结果是负面的时,自然的问题变成了*为什么*——这导致阅读OpenSSH更新日志、CVE-2016-6210和`UsePAM`实现。理解机制比仅仅记录结果更有价值。
**向外(检测转向):** 负面的攻击结果仍然是有用的防御数据点。转向“尽管枚举失败,服务器看到了什么?”导致了日志分析和检测工程组件,将一维的工具运行练习变成了双面调查。
### 哪些会做得不同
时序测量是在主机仅虚拟网络上进行的,这比真实网络引入的抖动更少,但也意味着结果是乐观的。在具有TCP延迟、抖动和重传的真实环境中,噪声基底会更高,并且每个用户名的时序分析需要更多样本。更稳健的方法论将在模拟WAN链路上进行测试(使用`tc netem`引入受控延迟和抖动),以查看结论在现实条件下的表现。
---
## 安全风险
尽管在此实验环境中枚举未成功,但攻击面和相关风险如下:
**如果枚举*是*可能的(例如,较旧的OpenSSH、`UsePAM no`、自定义PAM栈):**
- 攻击者可以将暴力破解活动缩小到仅确认有效的用户名,从而大大降低检测风险并提高效率。
- 结合密码喷洒(一个通用密码用于所有有效用户名),这绕过了每个用户的`MaxAuthTries`锁定机制。
**即使有枚举抵抗措施也适用的风险:**
- SSH横幅泄露了确切的OpenSSH版本和操作系统。看到`OpenSSH_8.9p1`的攻击者可以在发送任何认证数据包之前立即检查CVE-2024-6387(regreSSHion)是否适用。
- 所有枚举尝试都会记录来源IP。如果缺少日志监控,慢速攻击(每小时一次尝试)可以探测数千个用户名而不会触发基于速率的警报。
- `PasswordAuthentication yes`即使枚举不可行,也保持基于密码的攻击面开放。后OSINT凭据攻击(使用泄露的密码列表针对OSINT获取的用户名)根本不需要服务器端枚举。
---
## 缓解策略
| 威胁 | 缓解措施 | 需要配置更改 |
|---------------------------|------------------------------------------------------------------|-------------------------------------------|
| 时序侧信道 | 确保`UsePAM yes`(Ubuntu默认) | 无需更改 — 已是默认 |
| 横幅版本泄露 | 在`sshd_config`中设置`VersionAddendum none`和`DebianBanner no` | 是 |
| 基于密码的攻击 | `PasswordAuthentication no` — 仅密钥认证 | 是 |
| OSINT后的暴力破解 | 使用`fail2ban`和`sshd`监禁 | 安装并配置 |
| 慢速枚举 | 将日志发送到SIEM;当每小时超过N个不同用户名/IP时触发警报 | 需要SIEM |
| Root登录 | `PermitRootLogin no` | 是(默认为`prohibit-password`) |
| 预认证断开 | `MaxStartups 10:30:60` 来限流未认证连接 | 是 |
**建议强化部署的最小`sshd_config`添加项:**```
PasswordAuthentication no
PermitRootLogin no
MaxAuthTries 3
MaxStartups 10:30:60
VersionAddendum none
LogLevel VERBOSE
当前项目仅测试一种协议在一种配置下的情况。自然扩展包括:
枚举同一主机上的其他服务。 SMTP(VRFY/EXPN)、LDAP(属性查询)、HTTP 登录表单和 SNMP 都是同一台机器上常见的用户名泄露向量,这些机器在 SSH 方面进行了强化。跨服务进行对比研究将提供更全面的风险图景。
测试非默认的 SSH 配置。 UsePAM no、自定义 PAM 模块、Kerberos 认证后端以及较旧的 OpenSSH 版本(部署在嵌入式系统、网络设备中)仍可能表现出时序泄漏。针对故意存在漏洞的配置测试相同的管线将展示出对比效果。
使用 inotify 进行实时检测。 当前的检测是针对复制的日志文件进行事后分析。生产级版本将使用 inotify(或 tail -f + 解析器线程)在枚举尝试开始后的几秒内进行近实时检测和告警。
网络级检测。 Auth.log 是基于主机的产物。通过包级检测进行互补:到 22 端口的短寿命 TCP 连接在完成认证握手前关闭,这是仅进行横幅指纹识别的特征。这可作为 Zeek 或 Suricata 规则来实现。
用于统计报告的 Jupyter 笔记本。 收集到的时序数据适合可视化——每个用户名的时序分布箱线图、随时间变化的尝试频率散点图、源 IP 的热力图。笔记本将使分析具有可重现性和可共享性。
ssh-enumeration-analysis/ │ ├── README.md ├── run_investigation.py ← Full pipeline orchestrator (start here) ├── requirements.txt │ ├── src/ │ ├── attack_tools/ │ │ ├── init.py │ │ ├── manual_ssh.py ← Paramiko-based timing probe │ │ ├── banner_fingerprinter.py ← No-auth banner grab + CVE lookup │ │ ├── hydra_automation.py ← Hydra subprocess wrapper │ │ └── metasploit_scanner.py ← MSF console automation │ │ │ └── detection_tools/ │ ├── init.py │ ├── log_parser.py ← auth.log regex parser │ ├── response_analyzer.py ← Welch t-test + Cohen's d │ ├── pattern_detector.py ← 4 detection patterns │ └── alerting_system.py ← JSON alert emitter │ ├── data/ │ ├── sample-logs/ │ │ ├── auth-baseline.log ← Normal SSH activity (no attacks) │ │ ├── auth-hydra-run.log ← Captured during Hydra trial │ │ └── auth-msf-run.log ← Captured during Metasploit trial │ │ │ ├── wordlists/ │ │ ├── common-usernames-50.txt │ │ └── common-usernames-100.txt │ │ │ └── results/ ← Auto-generated (gitignored) │ ├── investigation-summary.json │ ├── manual-enumeration-results.json │ ├── timing-analysis.json │ └── detection-report.json │ ├── tests/ │ ├── test_enumeration.py │ ├── test_detection.py │ └── test_log_parser.py │ ├── case-study/ │ └── User_Enumeration_attempt_using_manual_SSH.docx │ └── screenshots/ ├── manual-ssh-same-response.png ├── hydra-no-enumeration-support.png ├── metasploit-hardened-detected.png ├── auth-log-hydra-evidence.png └── detection-alerts-output.png
## ⚡ 快速开始```bash
# 1. Clone and install dependencies
git clone https://github.com/Alisha-chaudhary/ssh-enum
cd ssh-enum
pip install -r requirements.txt
# 2. Run just the banner fingerprint (no credentials needed)
python -c "
from src.attack_tools.banner_fingerprinter import BannerFingerprinter
r = BannerFingerprinter().grab('192.168.xx.xxxx')
print(r.raw_banner, r.cves)
"
# 3. Run the full investigation pipeline
python run_investigation.py \
-target 192.168.xx.xxxx \
-usernames data/wordlists/common-usernames-50.txt \
-log data/sample-logs/auth-hydra-run.log \
-known-valid root ubuntu \
-samples 10
# 4. Analyse a log file only (no live target needed)
python -c "
from src.detection_tools.pattern_detector import EnumerationDetector
d = EnumerationDetector('data/sample-logs/auth-hydra-run.log')
import json; print(json.dumps(d.run_all(), indent=2))
"
要求:``` paramiko>=3.3.1 scipy>=1.11.0 Hydra and Metasploit must be installed separately (pre-installed on Kali Linux).
---
## 📚 参考资料
| 资源 | 相关性 |
|-----------------------------------------------------------------------------------------|--------------------------------------------------|
| [CVE-2016-6210](https://nvd.nist.gov/vuln/detail/CVE-2016-6210) | 本项目测试的计时侧信道 |
| [CVE-2024-6387 (regreSSHion)](https://nvd.nist.gov/vuln/detail/CVE-2024-6387) | OpenSSH ≤ 9.7 中未经认证的远程代码执行漏洞 |
| [OpenSSH sshd_config 手册](https://man.openbsd.org/sshd_config) | `UsePAM`、`MaxAuthTries`、`VersionAddendum` |
| [OWASP — 用户枚举](https://owasp.org/www-community/attacks/User_Enumeration) | 通用枚举攻击模式 |
| [RFC 4252 — SSH 认证协议](https://tools.ietf.org/html/rfc4252) | 协议规范;定义错误消息行为 |
| [fail2ban 文档](https://www.fail2ban.org/wiki/index.php/Main_Page) | 速率限制与 IP 封禁 |
---
## 🎓 所展示的技能
| 领域 | 证据说明 |
|----------------------------|----------------------------------------------------------------------------------------------------|
| SSH 协议内部原理 | 理解 `UsePAM` 计时标准化、CVE-2016-6210 修复、横幅信息暴露 |
| 攻击性安全 | 在结构化方法论中实际使用 Hydra、Metasploit 和自定义 Paramiko 探测 |
| 统计分析 | 对计时数据应用 Welch t 检验、Cohen's d、变异系数 |
| 检测工程 | 基于日志的 IOC 检测覆盖四种模式类型;结构化的告警输出 |
| Python 工程 | 数据类、类型提示、子流程自动化、正则解析、统计库 |
| 安全研究 | 假设 → 受控实验 → 证据收集 → 记录发现 → 可操作的结论 |
---
**状态:** 已完成 | **测试环境:** Ubuntu Server 22.04 LTS + OpenSSH 8.9p1 | Kali Linux 2024.1