capitan0n/sshd-lint
GitHub: capitan0n/sshd-lint
一款零依赖的 OpenSSH sshd_config 静态分析工具,基于 CIS、Mozilla 和 NIST 安全基线离线审计 SSH 服务器配置并支持 CI/CD 集成。
Stars: 2 | Forks: 0
# sshd_lint
一个零依赖、快速且健壮的 OpenSSH `sshd_config` 文件静态分析工具。
`sshd_lint` 根据行业标准安全基线评估您的 SSH 服务器配置,包括 **CIS Benchmark for Linux**、**Mozilla OpenSSH Guidelines** 和 **NIST SP 800-53**。
## 💡 动机
这个项目源于一个反复出现的实际需求:每当设置新的测试 VM 或管理生产服务器时,需要快速审计 SSH 配置和状态。
为了高效地解决这个问题,我构建了这个轻量级程序来提供即时结果。它是完全独立的,运行时不需要互联网连接,并且绝对零依赖。
## 🤖 AI 辅助项目
该工具的构思和开发均借助了人工智能的辅助。
在人类的指导和审查下,AI 被用于代码生成、逻辑优化以及边缘情况的处理(例如累积指令和 `Match` 块作用域)。
## ✨ 功能
- **零依赖** — 完全基于 Python 标准库构建。无需 `pip install`。
- **上下文感知解析** — 理解 OpenSSH 语义:`Match` 块、`Match All` 重置、指令遮蔽(directive shadowing)和 `Include` 通配符扩展。
- **作用域限定的 Match 块发现** — 区分全局配置错误与仅适用于特定用户、地址或组的风险。
- **重复指令检测** — 当某个指令在全局中出现多次时发出警告,因为 sshd 只会静默使用第一次出现的配置。
- **详细且具可操作性的报告** — 解释问题所在、其重要性,并引用相关标准。
- **支持 CI/CD** — 结构化的 JSON 输出和有意义的退出码,便于集成到 pipeline 中。
- **版本感知规则** — 根据目标 OpenSSH 版本调整预期。
## 📦 环境要求
- Python **3.9+**
- 无需外部包
## ⚙️ 安装
由于没有外部依赖,您可以直接运行它:
```
wget https://raw.githubusercontent.com/capitan0n/sshd-lint/main/sshd_lint.py -O sshd_lint
chmod +x sshd_lint
./sshd_lint
```
或者克隆代码仓库:
```
git clone https://github.com/capitan0n/sshd-lint.git
cd YOUR_REPO
python sshd_lint.py
```
## 🚀 使用方法
分析默认的系统 SSH 配置:
```
python sshd_lint.py
```
分析特定文件:
```
python sshd_lint.py /path/to/sshd_config
```
按严重性过滤(仅限 HIGH 及以上):
```
python sshd_lint.py --severity high
```
紧凑输出(隐藏解释信息 — 适用于快速扫描):
```
python sshd_lint.py --compact
```
用于 pipeline 集成的 JSON 输出:
```
python sshd_lint.py --format json
```
JSON 输出是自包含的 — `exit_code` 和每个严重级别的 `summary` 都包含在顶层,因此使用者无需单独捕获 `$?`:
```
{
"exit_code": 2,
"summary": {
"CRITICAL": 0,
"HIGH": 1,
"MEDIUM": 3,
"LOW": 4,
"INFO": 2
},
"findings": [
{
"severity": "HIGH",
"directive": "PasswordAuthentication",
"value": "yes",
"line": 42,
"scope": "global",
"message": "Password authentication is enabled.",
"detail": "...",
"references": ["CIS Benchmark for Linux", "Mozilla OpenSSH Guidelines"]
}
]
}
```
使用 `jq` 过滤:
```
# 仅 CRITICAL 发现
python sshd_lint.py --format json | jq '.findings[] | select(.severity == "CRITICAL")'
# 仅摘要
python sshd_lint.py --format json | jq '.summary'
# 从 JSON 而非 $? 读取 exit code
python sshd_lint.py --format json | jq '.exit_code'
```
审计从远程服务器复制的配置,并从当前运行的系统解析 Include:
```
scp user@server:/etc/ssh/sshd_config /tmp/audit/sshd_config
python sshd_lint.py /tmp/audit/sshd_config --base-dir /etc/ssh
```
## 🧰 CLI 标志
| 标志 | 缩写 | 描述 |
|-----------------------|-------|-------------|
| `config` | — | sshd_config 的路径(默认:`/etc/ssh/sshd_config`) |
| `--severity` | `-s` | 最低严重性:`critical`、`high`、`medium`、`low`、`info`(默认:`info`) |
| `--format` | `-f` | 输出格式:`text` 或 `json`(默认:`text`) |
| `--compact` | `-c` | 隐藏解释和参考以获得更清晰的输出 |
| `--no-color` | — | 禁用 ANSI 颜色 |
| `--openssh-version` | — | 目标 OpenSSH 版本(例如 `8.9`),用于进行感知版本的规则调整 |
| `--base-dir` | — | 用于解析 `Include` 的基准目录。默认:与配置文件相同的目录 |
| `--version` | `-v` | 显示版本并退出 |
## 🚦 退出码
专为 CI/CD pipeline 设计:
| 代码 | 含义 |
|------|---------|
| `0` | 没有达到请求严重性阈值的发现 |
| `1` | 存在发现,但没有 HIGH 或 CRITICAL 级别的问题 |
| `2` | 至少有一个 HIGH 或 CRITICAL 级别的发现 — pipeline 应该失败 |
使用 `--format json` 时,退出码也会作为 `exit_code` 嵌入在 JSON 输出中,因此报告是完全自包含的,可供下游工具读取,而无需捕获 `$?`。
GitHub Actions 步骤示例:
```
- name: Lint SSH config
run: python sshd_lint.py /etc/ssh/sshd_config --severity high --format json
```
## 🧪 已评估的规则
### 解析与文件处理
- 规则 00:Include 解析问题 — 无法读取的文件、匹配超过 500 个文件的 Include 通配符(直接拒绝)、匹配 0 个文件的 Include 通配符,以及无法解析为指令的行
### 身份验证
- 规则 01:`PermitRootLogin`
- 规则 02:`PasswordAuthentication`
- 规则 03:`PermitEmptyPasswords`
- 规则 04:`ChallengeResponseAuthentication`
- 规则 05:`PubkeyAuthentication`
- 规则 06:`HostbasedAuthentication` / `IgnoreRhosts`
### 访问控制
- 规则 10:`LoginGraceTime`
- 规则 11:`MaxAuthTries`
- 规则 12:`MaxSessions`
- 规则 13:`MaxStartups`
- 规则 14:`AllowUsers` / `AllowGroups`
### 转发与隧道
- 规则 20:`X11Forwarding`
- 规则 21:`AllowTcpForwarding`
- 规则 22:`AllowAgentForwarding`
- 规则 23:`GatewayPorts`
- 规则 24:`PermitTunnel`
### 日志与审计
- 规则 30:`LogLevel`
- 规则 31:`PrintLastLog`
### 密码学
- 规则 40:弱或已弃用的 Ciphers
- 规则 41:弱或已弃用的 MACs
- 规则 42:弱 KexAlgorithms(密钥交换)
- 规则 43:已弃用的 HostKeyAlgorithms
- 规则 44:已弃用的 `PubkeyAcceptedAlgorithms`
规则 40-44 理解 OpenSSH 的 `+`/`-`/`^` 默认集合语法(例如 `Ciphers +arcfour`
会追加到编译时的默认值中,而不是替换它)—— 无论是完全替换列表还是仅仅追加到其中,弱算法都会被标记出来。
### 其他
- 规则 50:`Banner`
- 规则 51:`StrictModes`
- 规则 52:`Port`(默认端口 22)
- 规则 53:`ClientAliveInterval` / 空闲会话超时
- 规则 54:`UseDNS`
- 规则 55:`Match` 块内的不安全指令(受作用域限制的风险)
- 规则 56:重复的全局指令(被 sshd 遮蔽)
- 规则 57:`PermitUserEnvironment`
## 🔍 sshd_lint 与同类工具的区别
| 工具 | 工作原理 | 需要 root 权限 / 运行中的系统 |
|------|--------------|-----------------------------|
| **Lynis** | 在系统上实时运行,审计许多方面 | 是 |
| **ssh-audit** | 连接到正在运行的 SSH 服务器,测试其响应 | 是(需要网络访问) |
| **sshd_lint** | 离线静态读取配置文件 | 否 |
`sshd_lint` 专为离线审计、CI/CD pipeline 以及审查来自远程系统的配置而设计,无需访问正在运行的服务器。
## ⚠️ 限制
- **仅限静态分析** — 不连接到正在运行的服务器或测试实际行为。
- **不评估 Match 块条件** — 条件字符串(例如 `User anoncvs`、`Address 10.0.0.0/8`)会被记录并报告,但 sshd_lint 无法确定它是否适用于给定的连接。
- **Include 解析需要文件系统访问权限** — 无法读取的文件和匹配超过 500 个文件的 Include
通配符会被报告为 CRITICAL;匹配 0 个文件的通配符被报告为
INFO(通常是良性的 — 例如一个空的 `sshd_config.d/` — 但值得看一眼)。
- **相对 `Include` 路径会根据 `--base-dir` / 配置文件自身的
目录进行解析,而不是真实 sshd 硬编码的 `/etc/ssh`。** 对于审计
正在运行的 `/etc/ssh/sshd_config` 这种常见情况,两者是相同的。当仅审计
主文件的副本(旁边没有其片段)时,请传入 `--base-dir /etc/ssh`,以按照 sshd 本身的方式解析 include。
- **感知版本的规则目前很少** — `--openssh-version` 标志仅调整了少量已知的默认值;在未来的版本中可能会添加更多特定于版本的规则。
- **编译时的默认值适用于 OpenSSH 8.x** — 在明显更旧或更新版本上的行为可能会有所不同。
## 作者
* **capitan0n** - [capitan0n](https://github.com/capitan0n)
## 📜 许可证
MIT License
标签:DevSecOps, Python, SSH, 上游代理, 云安全监控, 关系图谱, 基线检查, 安全合规, 无后门, 网络代理, 逆向工具, 静态分析