跳转到内容

安全、开源与技术交付

安全不是上线前加一层“检查”,技术交付也不是把代码推上去。系统需要从设计阶段就明确资产、信任边界、攻击面和失败后的影响;交付则需要版本、文档、许可、迁移和回滚信息。

威胁模型不是列出所有攻击,而是回答四个问题:

  1. 我们在保护什么:账号、资金、隐私、内部文档、密钥、可用性。
  2. 谁可能攻击:外部匿名用户、登录用户、恶意员工、第三方服务、供应链。
  3. 哪里是信任边界:浏览器到服务端、服务到数据库、租户之间、内部网到公网。
  4. 资产被破坏会怎样:泄露、篡改、丢失、拒绝服务、越权操作。
浏览器 → CDN/WAF → API → 业务服务
├→ 数据库
├→ 对象存储
├→ 第三方 API
└→ 消息队列 → Worker

在每条箭头标出身份、数据、加密和权限检查。没有明确身份和边界的路径,通常是风险来源。

威胁 含义 例子
Spoofing 冒充身份 偷取会话、伪造回调
Tampering 篡改数据 修改价格、请求参数
Repudiation 否认操作 缺少审计日志
Information disclosure 信息泄露 越权读取、公开存储桶
Denial of service 拒绝服务 资源耗尽、超大请求
Elevation of privilege 权限提升 普通用户执行管理员操作

每个威胁配一个可执行控制:认证、签名、审计、加密、限流、权限检查。

  • 认证回答“你是谁”,授权回答“你能做什么”。
  • 前端隐藏按钮不是授权;后端必须检查每个受保护资源。
  • 会话 Cookie 应设置 HttpOnly、Secure 和合适的 SameSite。
  • JWT 要验证签名、算法、过期、签发者和受众,不把敏感信息只做 base64 编码。
  • OAuth/OIDC 使用授权码 + PKCE、state 和严格回调白名单。
  • API Key 要可轮换、可撤销、可限权,不能放在前端。

常见越权是修改 URL 中的 ID:

GET /documents/123

如果系统只检查“用户已登录”,攻击者可以枚举其他文档。每次访问都要验证:

  • 用户是否属于该租户。
  • 用户是否拥有该资源的读取/编辑权限。
  • 资源当前状态是否允许该操作。
  • 共享链接是否有有效期和最小范围。

登录成功后应轮换会话 ID,防止会话固定。退出登录要撤销服务端会话或刷新令牌。敏感操作可以要求二次认证。密码存储使用专门密码哈希算法和每个用户独立盐,不使用普通 SHA/MD5。

把不可信输入拼进 SQL、命令、模板或 ORM 原始片段,可能改变程序语义。

// 错误:拼接输入
db.query(`SELECT * FROM users WHERE email = '${email}'`);
// 正确:参数化查询
db.query("SELECT * FROM users WHERE email = $1", [email]);

参数化查询适用于数据值;表名、列名和排序方向不能随便参数化,应使用白名单。Shell 命令尽量使用参数数组,避免 shell: true 和字符串拼接。

未转义的用户内容进入 HTML,脚本可能在其他人浏览器中执行。风险包括窃取会话、冒充操作和读取页面数据。

防护:

  • 默认输出编码。
  • 少用 innerHTML,必须用时经过可信净化。
  • CSP 限制脚本来源,但 CSP 不是唯一防线。
  • 富文本使用严格白名单。
  • Cookie 使用 HttpOnly 降低令牌被脚本读取的风险。

浏览器会自动携带 Cookie,恶意站点可能诱导用户发出已认证请求。

防护:

  • 使用 SameSite Cookie。
  • 写操作要求 CSRF token 或自定义头部。
  • 不用 GET 执行状态变更。
  • 关键操作二次确认。

服务端根据用户提供的 URL 发起请求时,可能被诱导访问内网、元数据服务或本地管理端口。

防护:

  • 目标域名白名单。
  • 解析后检查 IP,阻止私有地址和回环地址。
  • 禁止任意协议和重定向跳转。
  • 使用独立出站代理与最小网络权限。
  • 限制大小、类型和数量。
  • 校验实际内容,不只信扩展名和 Content-Type。
  • 文件名重新生成,避免路径穿越和控制字符。
  • 上传后扫描恶意内容。
  • 存储与执行环境隔离,使用私有访问或短期签名 URL。

3.6 反序列化与外部数据

跳转到“3.6 反序列化与外部数据”

反序列化不可信对象可能触发代码执行、资源耗尽或类型混淆。优先使用纯数据格式和 schema 校验,限制嵌套深度、字段数量和总大小。第三方 webhook 要验证签名和时间戳,防止伪造与重放。

  • 对外通信使用 HTTPS,内部敏感链路也应加密或隔离。
  • 数据库、备份、对象存储和密钥应有静态加密。
  • 加密不能替代访问控制;有密钥的人仍能读取明文。
  • 密码使用不可逆哈希,不用可逆加密。

密钥不能写进源码、镜像、前端包、日志或聊天记录。使用密钥管理服务或环境注入,按用途和环境隔离,定期轮换。访问密钥要有审计,泄露时要能快速撤销和轮换。

项目依赖的库、构建工具、容器镜像和 CI 插件都可能成为攻击入口。

  • 锁定依赖版本,保留 lockfile。
  • 只从可信源安装,注意包名仿冒。
  • 审查安装脚本和构建权限。
  • 定期扫描已知漏洞,但不能只看扫描数量。
  • 生成 SBOM 或至少记录直接依赖与版本。
  • 构建产物签名、校验和来源证明能提高可追溯性。

依赖漏洞是否可利用,要结合调用路径、输入边界和运行权限判断。升级修复前先读变更说明,避免自动升级引入破坏。

需求与威胁模型
→ 设计与权限评审
→ 编码与静态检查
→ 依赖/镜像扫描
→ 测试(含权限和注入)
→ 灰度发布
→ 监控与应急

安全检查应尽量自动化,但不能用工具代替设计评审。一个通过扫描的系统仍可能允许任意用户读取别人的订单。

  • 服务账号只拥有所需资源权限。
  • CI/CD 部署凭据按环境隔离。
  • 数据库用户不要共用管理员账号。
  • 容器不以 root 运行。
  • 云存储默认私有。
  • 内部工具也要认证和审计。

重要操作记录谁在何时对什么资源做了什么,结果如何。审计日志应防篡改并限制访问,不能和普通调试日志一样随意删除。隐私数据写入审计前要脱敏。

使用开源项目要理解许可证,不只看“GitHub 上能下载”。

常见许可证大致分为:

  • 宽松型:MIT、Apache-2.0、BSD,通常允许修改和商业使用,但保留版权/许可声明。
  • 弱 copyleft:LGPL 等,对链接方式和分发有特定要求。
  • 强 copyleft:GPL/AGPL,分发或提供网络服务时可能有源代码开放义务。
  • 非商业/共享相同方式:CC BY-NC-SA 等,限制商业用途并要求署名与相同许可。

许可证条款需要阅读原文,特别是:

  • 是否允许商业使用。
  • 是否要求公开修改后的源代码。
  • 是否要求相同方式共享。
  • 是否保留 NOTICE、版权和出处。
  • 数据库、字体、图片、模型权重是否另有许可。
  • 阅读贡献指南和行为准则。
  • 先搜索已有 issue,再提问题。
  • 一个变更解决一个清晰问题。
  • 不提交来源不明或与项目许可冲突的代码。
  • 尊重维护者时间和项目边界。

选型不是寻找“最强技术”,而是寻找匹配当前约束的最简单方案。

要比较:

  • 需求与性能目标。
  • 团队经验与维护能力。
  • 生态、文档、社区和长期支持。
  • 部署、运维、监控和迁移成本。
  • 安全、许可证和供应商风险。
  • 简历驱动:为了使用新技术而引入不必要复杂度。
  • 盲目追新:忽略稳定性、迁移和团队学习成本。
  • 只看基准:测试环境与真实数据分布不同。
  • 忽略退出成本:数据锁定、专有 API 和迁移难度。
  • 过度设计:为不存在的规模预先拆分系统。

架构决策记录应写明:

背景与约束
备选方案
决定
理由
代价与风险
验证指标
回滚/退出条件

ADR 让后来者知道当时的假设,而不是只看到结果。

文档是交付物的一部分。最需要的不是长篇教程,而是能回答实际问题:

  • 项目解决什么问题,不解决什么。
  • 如何安装、配置、启动和验证。
  • 关键概念与架构图。
  • 数据模型、接口和错误码。
  • 运维、备份、故障与回滚。
  • 已知限制和许可。
  • 先写读者要完成的任务。
  • 示例要能运行,命令和路径要一致。
  • 明确版本、前置条件和预期结果。
  • 用短段落、表格和检查清单降低扫描成本。
  • 不把敏感信息、内部地址和个人数据写进公开文档。
  • 文档随代码更新,过期内容比没有更危险。
  1. 威胁模型、资产和信任边界是否明确。
  2. 身份、会话、对象级权限是否在服务端验证。
  3. 输入、上传、回调和外部 URL 是否按不可信处理。
  4. 密钥是否与代码和镜像分离,是否可轮换。
  5. 依赖和镜像是否锁定、扫描和可追溯。
  6. 是否检查许可证、版权和第三方资产来源。
  7. 是否有测试、灰度、监控和回滚。
  8. 文档是否说明安装、配置、接口、限制和故障处理。
  9. 选型是否记录取舍、风险和退出条件。
  10. 对外发布是否保护隐私、密钥和内部拓扑。