安全基线
概述
完整参考见 references/baseline.md(章节 §0–§18,通过 ## N. 搜索)。本文件是操作流程。核心原则:将严谨程度与威胁等级匹配,然后用证据证明每个发现。 三条法则:永远不要信任客户端,永远不要信任模型的代码,永远不要信任默认配置。
选择模式
| 请求 | 模式 | 在 baseline.md 中阅读 |
|---|---|---|
| “安全地规划/启动一个项目”,编写智能体规则 | 计划 | §0, §2, §3, §16 |
| “审查/审计这个应用/仓库/PR” | 代码审查 | §1.3, §4–§11, §15.2 |
| “审计我的服务器/家庭实验室/VPS”,“是否是最新的” | 主机审计 | §3.3, §8.3, §12, §13 |
| “我们被黑了/密钥泄露了” | 事件响应 | §17, §8.2 |
主机审计包括对每个面向互联网、持有账户或个人数据的应用进行简短的代码审查。
步骤 1 — 确定等级(始终优先进行,并在报告中说明)
根据可观察到的事实判定,而非所有者对它的称呼:
| 等级 | 判断条件 | 适用 |
|---|---|---|
| T0 玩具 | 没有真实用户或数据,且无法从互联网访问(仅局域网,或位于 SSO 之后) | §2, §8 |
| T1 公开,无账户 | 可从互联网访问,无登录功能,不存储个人数据 | T0 + §9, §10 |
| T2 账户/用户数据 | 任何登录功能,任何个人数据(姓名、邮箱、访客列表),或它持有的任何 API 密钥/令牌 | 全部 |
| T3 规模化的资金/PII/受监管 | 支付、健康、凭据库、金融、>1k 人的数据、客户合同 | T2 + 外部审查 + §14 |
家庭实验室主机的等级按照其上运行的任何内容的最高等级来评定。凭据聚合器(密码管理器、n8n、Home Assistant、Portainer、docker.sock 持有者)因其爆炸半径被定为 T3。
步骤 2 — 收集证据(只读)
通过 bash 运行捆绑脚本。在远程主机上,通过 SSH 传输它们:ssh host 'bash -s -- --remote-digests' < scripts/audit-docker-host.sh 和 ssh host 'bash -s -- <project-dir>' < scripts/scan-code.sh。
bash scripts/audit-docker-host.sh --remote-digests— 操作系统补丁、重启标志、监听套接字、防火墙、sshd、每个容器的用户/权限/暴露端口/docker.sock、类似密钥的环境变量名称、镜像新鲜度(UP-TO-DATE / OUTDATED / local-build)。bash scripts/scan-code.sh <project-dir>— 使用 grep 查找密钥、注入、XSS、批量赋值、客户端提供的租户 ID、fail-open 捕获、弱加密、锁定文件、Dockerfile/compose 风险、路由清单的线索。
然后做脚本无法完成的事:
- 反向代理盘点:列出每个公共主机名、其目标地址以及前端是否有认证(NPM DB / Caddy / Traefik 标签)。位于公共主机名上且没有 SSO/访问列表的管理界面(§3.3 列表)= 发现项。
- 外部视图:从局域网路径外部,
curl -sI https://<host>获取头部信息(§10.2),并仅使用 GET 探测/.env、/.git/config、/admin、/api。 - 每个 T2+ 应用的授权追踪:对于每个路由,指明授权检查方式以及主体/租户 ID 的来源(§4.3)。应用 §1.3 的问题。
- 代理信任:如果应用读取
X-Forwarded-For,检查它信任哪个跃点($proxy_add_x_forwarded_for会追加;第一个值可能由攻击者控制)。 - 每个角色的凭据强度:对于每个角色(管理员、员工、志愿者、API/MCP 令牌),找出其密钥是如何设置的,包括种子、旧版和迁移路径(它们可能跳过常规长度检查)。使用
awk -F= '{print $1, length($2)}'检查.env值的实际长度,切勿打印它们。弱密钥加上可绕过的速率限制算作一个发现项,而不是两个。 - 版本与安全公告对比:对于每个面向互联网的应用,将运行版本与当前发布版本和已知 CVE 进行比较。使用网络搜索该产品的安全公告。
主机检查清单(§16 检查清单是针对每个项目的;这里用于主机)
- 每个公共主机名都已列出,并且每个管理界面(§3.3)都有 SSO、访问列表或 VPN 保护,或不在公共网络中
- 虚拟机监控程序、Docker、NPM/代理管理、密码库管理:绝不公开
- 每个不需要开放注册的自托管应用(Vaultwarden、Open WebUI、Gitea、Immich 等)上的开放注册已禁用
- WAN 可达端口 = 仅 80/443(+ VPN)。Docker 发布端口会绕过 ufw:检查路由器转发和
DOCKER-USER链 - 代理传递的客户端 IP 应用无法被欺骗(§4.5, X-Forwarded-For)
- 已列出 docker 组成员;每个成员都等同于 root 权限
- 密钥文件权限为 600;没有全局可读的
.env/密钥文件 - 操作系统已打补丁,没有超过 7 天未重启;镜像为最新(新鲜度表)
- 最近运行了备份,并且测试过恢复(§13.4)
参与规则
- 默认只读。不要进行任何更改数据的写入、重启、更新、POST 操作、暴力破解或扫描所有者不拥有的主机。
- 切勿打印密钥值。仅报告变量名和文件:行号。
- 检查配置存储(数据库配置表、JSON 设置、
docker inspect环境变量)时,仅通过键和长度:jq 'paths'、SELECT key FROM …、awk -F= '{print $1, length($2)}'。切勿倾泻整个配置行或文件来查看:其中会包含 API 密钥。 - 更改(更新、chmod、配置修复)仅在报告之后且请求者要求时才进行。对于更新,请遵循下面的“安全更新”。
步骤 3 — 报告(精确格式)
- 范围和等级:检查的内容、等级以及一句话的原因。
- 结论:一句话 — 可以上线 / 先修护 / 停止。
- 发现项,按严重性从高到低排序。每个发现项包括:标题、
CONFIRMED(你已复现或读取了确切的代码/配置路径)或LEAD(看似合理,但未证实)、证据(文件:行号,命令 + 输出摘录)、一句话影响、修复方案。 - 新鲜度表(主机审计):组件、运行版本、最新版本、更新风险。
- 覆盖范围:你检查过的 §16 检查清单部分,以及你未检查的部分及原因。沉默不代表通过。
- 后续行动,按编号排列,以最小工作量-最高风险优先。
安全更新(“确保一切都是最新的”)
- 确认存在备份且是近期的(§13.4)。记录每个容器的当前镜像 ID,以便通过
image: <仓库>@<旧摘要>回滚。 - 无状态 = 应用没有写入的命名卷或绑定挂载,也没有数据库。静态 nginx 站点、gotenberg、flaresolverr 是无状态的。如果不确定,视为有数据。
- 分类:有数据的或有架构迁移(数据库、Immich、Paperless、Vaultwarden、Firefly、n8n、Home Assistant) → 一次一个,先阅读发布说明中的重大更改。无状态(nginx 静态站点、工具) → 批量处理。
- 切勿通过
docker pull跨越大版本升级数据库(例如 postgres 15→17)。那需要 dump/restore。 - 每个项目执行
docker compose pull && docker compose up -d,然后检查健康状态:容器状态、日志中的错误,以及公共 URL 的 HTTP 检查。 - 本地构建(
local-build)通过使用--pull重新构建来更新。操作系统包需要 sudo;如果没有 sudo,列出包列表和重启标志。 - 如果拉取失败并显示
429 toomanyrequests,请停止。要求在主机上运行docker login,或 6 小时后重试。不要循环。
常见错误
| 错误 | 正确做法 |
|---|---|
| 一切报告为“高危” | 严重性 = 可利用性 × 等级 × 爆炸半径 |
| 将脚本命中视为发现项 | 脚本输出是 LEAD,直到你阅读了代码 |
| 不检查就报告“容器以 root 运行” | 许多镜像会在入口点中降低权限;检查容器内的 ps |
| 在局域网主机上报告“端口 9000 已打开” | 说明谁可以访问它:仅局域网、通过路由器转发暴露于 WAN、或通过代理 |
| 跳过等级,像审计银行一样审计静态站点 | 等级优先;T1 需要头部信息和依赖项,而不是 RLS |
一次性用 :latest 更新一切 | 一次一个数据承载服务,并记录回滚摘要 |
通过禁用控制来修复(关闭 RLS,设置 CORS *) | 修复策略,切勿移除它(§7.2) |