1. 概述
安全编码总则定义了与编程语言无关的通用安全编码原则。这些原则构成了所有语言特定安全编码规范的基础, 适用于任何技术栈的软件开发。开发人员在编写代码时应将这些原则内化为编码习惯, 而非仅作为检查清单使用。
2. 输入验证 (Input Validation)
2.1 核心原则: 不信任任何外部输入
所有来自系统边界外的数据都应视为不可信, 包括但不限于: HTTP 请求参数、请求头、Cookie、上传文件、API 调用参数、数据库查询结果 (如果数据来源不可控)、环境变量和配置文件 (如果可被外部修改)。
2.2 白名单优于黑名单
验证策略应基于白名单 (允许已知安全的输入) 而非黑名单 (尝试过滤已知危险的输入)。黑名单方法存在固有缺陷: 攻击者总能发现未被列入黑名单的新攻击向量, 而绕过技术 (如编码变换、Unicode规范化) 使得完备的黑名单几乎不可能实现。
2.3 验证时机与位置
输入验证必须在服务端执行。客户端验证可以改善用户体验, 但绝不能作为安全控制手段, 因为攻击者可以轻易绕过客户端逻辑。验证应在数据进入业务逻辑层之前完成, 建议在框架层面统一实施。
3. 输出编码 (Output Encoding)
输入验证解决"进入系统的数据是否安全", 输出编码则解决"离开系统的数据是否会造成危害"。根据输出目标的不同, 需要采用不同的编码策略:
- HTML Context: HTML Entity Encoding (如
<>&) - JavaScript Context: JavaScript Unicode Escaping
- URL Context: Percent Encoding
- CSS Context: CSS Hex Encoding
- SQL Context: 使用参数化查询而非编码 (编码不是SQL注入的可靠防御)
上下文感知编码是防御 XSS 的关键。许多框架 (如 React, Angular) 提供自动输出编码, 但开发人员应理解其工作原理和边界条件, 特别是在使用 dangerouslySetInnerHTML 等绕过机制时。
4. 最小权限原则 (Principle of Least Privilege)
每个模块、进程和用户应仅拥有完成其功能所需的最小权限集。该原则应在多个层面实施:
- 数据库层: 应用账号仅授予必要的表和操作权限, 避免使用 root/sa 账号
- 文件系统层: 进程仅拥有必要目录的读写权限
- 网络层: 服务仅开放必要的端口, 使用网络策略限制出入流量
- API层: 每个 Token/Key 仅关联必要的 Scope
- 容器层: 以非 root 用户运行容器, 使用 seccomp/AppArmor 限制系统调用
5. 纵深防御 (Defense in Depth)
不依赖单一安全控制, 而是在多个层面部署互补的安全措施。即使某一层防御被攻破, 其他层仍能提供保护。典型的纵深防御架构包括:
- 网络层: WAF + DDoS防护 + 网络分段
- 应用层: 输入验证 + 输出编码 + CSP
- 数据层: 加密存储 + 访问控制 + 数据脱敏
- 运维层: 日志监控 + 入侵检测 + 自动告警
6. 安全失败 (Fail Securely)
当系统出现异常时, 应默认进入安全状态, 而非开放状态。这一原则体现在:
- 认证失败: 默认拒绝访问, 而非默认放行
- 权限检查异常: 返回403/401, 而非跳过权限检查继续执行
- 配置缺失: 使用安全的默认值, 而非最宽松的配置
- 异常处理: 捕获异常后进行安全处理, 避免信息泄露
一个常见的反模式是在 try-catch 块中捕获异常后静默忽略, 导致安全检查被绕过。所有安全相关异常都应被正确记录和处理。
7. 不要自己造轮子 (Don't Roll Your Own)
在密码学、认证、会话管理等安全关键领域, 应使用经过广泛审查的标准库和框架, 而非自行实现。历史反复证明, 自研加密算法、自研认证协议和自研随机数生成器几乎总是存在缺陷。
- 使用框架内置的会话管理, 不要自行实现基于 Cookie 的会话
- 使用标准加密库 (如 OpenSSL, libsodium, BouncyCastle), 不要实现自定义加密
- 使用成熟的 JWT 库, 不要手动解析和验证 Token
8. 安全默认配置 (Secure by Default)
系统的默认配置应是安全的。用户需要主动操作才能降低安全级别, 而非需要主动操作才能提升安全级别。具体体现在:
- Cookie 默认设置
Secure、HttpOnly、SameSite属性 - CORS 默认不允许跨域, 需显式配置白名单
- API 默认需要认证, 公开接口需显式声明
- 日志默认不记录敏感字段
9. 小结
安全编码总则的核心思想可以概括为: 不信任外部输入、最小化攻击面、纵深防御、安全失败、使用经过验证的方案。这些原则应贯穿于日常编码的每一个决策中。后续的语言特定编码规范将在这些通用原则的基础上, 提供更具操作性的编码指导。