导语:漏洞的最好修复时间点是"写出来的那一刻"
同样的漏洞,在代码评审时发现成本最低,在测试时发现次之,上线后被发现就要救火,被攻击者发现就是事故。安全编码规范的目的是把漏洞拦截在"诞生之前",代码审计则是最后一道人工防线。
一句话总结: 安全编码 = 让每一行代码默认安全(输入必校验、输出必编码、权限必最小);代码审计 = 在合并前把"不合规的写法"揪出来。规范解决存量,审计拦截增量。
1. 安全编码三大铁律
1.1 铁律一:输入永不信任(Input Validation)
// ❌ 直接使用用户输入
String path = request.getParameter("file");
Files.readAllBytes(Paths.get("/data/" + path)); // 路径穿越
// ✅ 白名单 + 服务端校验
if (!path.matches("[a-zA-Z0-9_.-]{1,64}")) { throw new BadRequest(); }
// 且用规范化的绝对路径,再检查前缀是否仍在白名单目录内
输入校验原则:
· 白名单优先(能列举就不正则黑名单)
· 校验放在服务端(前端校验只是体验)
· 校验输入「语义」而非仅格式(长度、类型、取值范围、枚举值)
· 统一用框架的校验注解/中间件,不散落在业务代码
1.2 铁律二:输出必编码(Output Encoding)
凡是要渲染到 HTML / SQL / 系统命令 / JSON 的数据,
都必须按目标上下文做编码或参数化:
HTML: 转义 < > & " '(防 XSS)
SQL: 参数化/预编译(防注入)
命令: 禁止拼接,用参数数组(防命令注入)
JSON: 序列化时按框架默认安全转义
日志: 换行符/敏感字段要处理(防日志注入)
1.3 铁律三:权限最小 + 纵深
· 进程/服务用最小权限账号跑,不 root
· 数据库账号只给业务库最小授权(见「数据库安全加固」专题)
· 敏感操作二次校验 + 审计留痕
· 防御不止一层:即便输入校验被绕过,下层还有编码/权限兜底
一句话总结: 三大铁律是「不信任输入、安全地输出、最小化权力」——它们互相兜底,构成安全编码的底层假设。
2. 各语言高危模式清单
2.1 Java
| 模式 | 危险写法 | 安全写法 |
|---|---|---|
| SQL | "SELECT * FROM t WHERE id=" + id | PreparedStatement / MyBatis #{} |
| 反序列化 | ObjectInputStream.readObject() 处理不可信数据 | 白名单 ObjectInputFilter |
| 命令执行 | Runtime.exec("cmd " + input) | 参数数组,不 shell 拼接 |
| XML | DocumentBuilderFactory 未禁外部实体 | 禁用 external-general-entities |
| 反射 | 用反射动态加载用户可控类 | 类名白名单 + 拒绝加载任意类 |
2.2 Python
| 模式 | 危险写法 | 安全写法 |
|---|---|---|
| 命令 | os.system("ping " + host) | subprocess.run([...], shell=False) |
| SQL | f-string 拼 SQL | 参数化 ? / %s / ORM |
| 反序列化 | pickle.loads(untrusted) | 仅对可信数据用 pickle,否则 json |
| 模板 | 在模板里执行用户输入 | 模板渲染默认转义 |
| 文件 | open(user_path, 'w') | 校验路径 + 白名单目录 |
2.3 Go / Node.js
// Go:命令执行
cmd := exec.Command("sh", "-c", "rm "+arg) // ❌
cmd := exec.Command("rm", arg) // ✅ 参数数组
// Node:原型污染 / 命令注入
const opts = JSON.parse(userInput); // ❌ 深合并会污染 __proto__
const out = execSync(`ls ${arg}`); // ❌
const out = execFileSync('ls', [arg]); // ✅
一句话总结: 高危模式高度相似——命令拼接、SQL 拼接、反序列化不可信数据、未受限的文件/XML/反射。建立语言级危险函数清单,是审计的起点。
3. 代码审计方法论
3.1 审计的目标对象
优先审计的代码:
· 接收外部输入的第一层(HTTP handler / 消息消费 / 文件导入)
· 调用系统命令、操作文件、执行 SQL、发起 HTTP 的代码
· 处理反序列化 / XML / 模板渲染的代码
· 鉴权、支付、上传、导出等高风险业务
3.2 数据流与污点分析
核心思维:找「不可信数据(Source)」→ 流向「危险函数(Sink)」的路径
Source(不可信数据):
HTTP 参数 / 请求体 / Header / Cookie / 上传文件 / 外部 API 响应 / 消息队列
Sink(危险函数):
SQL 执行 / 系统命令 / 文件写 / 响应输出 / 反序列化 / 重定向 / 反射
审计动作:
对每个 Source→Sink 路径,检查路径上是否有「校验/编码/参数化」。
没有 → 漏洞;有但可绕过 → 漏洞。
3.3 审计流程
① 工具预筛:SAST/SCA 先跑一遍,定位可疑点(减少人工盲扫)
② 人工重点:聚焦「用户输入 → 危险函数」的真实路径(工具误报多、漏报也多)
③ 业务理解:看懂代码的业务意图,才能判断「这个校验是否真的生效」
④ 深挖绕过:对每个校验问「参数化了吗 / 编码覆盖所有输出点了吗 / 校验可否被绕过」
⑤ 修复建议:给出最小改动方案,不只报漏洞
一句话总结: 审计不是"读一遍代码",而是沿着数据流追"不可信数据是否无阻碍到达危险函数"——工具负责广度,人工负责深度与绕过。
4. 审计工具与人工的分工
| 层 | 工具/手段 | 覆盖 | 局限 |
|---|---|---|---|
| 静态分析 SAST | SonarQube / Semgrep / CodeQL | 危险函数、常见注入 | 误报多、不懂业务、跨函数追踪弱 |
| 污点分析 | CodeQL / 商业工具 | 数据流路径 | 配置门槛高 |
| 依赖扫描 SCA | Snyk / Trivy | 组件漏洞、许可证 | 只覆盖已知漏洞 |
| 人工审计 | 安全/资深工程师 | 业务逻辑、绕过、架构 | 成本高、依赖经验 |
推荐组合:
提交前:IDE 插件 + 提交钩子(快速拦截明显问题)
流水线:SAST + SCA 门禁(强制阻断高危)
重大变更:人工安全评审(merge 前)
周期:全量人工抽审 + 重点模块深度审计
一句话总结: 工具负责"抓普遍问题",人工负责"抓工具抓不到的"——两者是互补关系,用工具筛、用人审、用门禁挡。
5. 安全编码规范落地
5.1 规范的形态
一份好的安全编码规范应该具备:
· 按语言/框架给出「危险写法 ❌ / 安全写法 ✅」对照
· 直接对应可用的 lint 规则(能机检的不靠自觉)
· 与公司技术栈绑定(如 Spring + MyBatis 的具体写法)
· 随漏洞复盘持续补充(每次事故 → 新增一条规范)
5.2 评审清单(Merge 前必查)
□ 所有用户输入在服务端做了白名单校验?
□ SQL/命令/模板全部参数化或编码?
□ 没有反序列化不可信数据?有则白名单过滤?
□ 上传文件做了类型/大小/内容校验并隔离存储?
□ 按 ID 查询的接口校验了数据归属?
□ 敏感数据(密码/Token)没有落日志/返前端?
□ 依赖已扫描无高危漏洞?许可证合规?
□ 错误信息没有向用户泄露堆栈/内部路径?
一句话总结: 规范要落地,靠的是把规则变成 lint 规则、评审清单、门禁策略——不能只写文档等自觉。
6. 避坑清单
| 坑 | 后果 | 对策 |
|---|---|---|
| 只做 SAST 不人工 | 逻辑漏洞/绕过漏检 | 工具 + 人工审计结合 |
| 审计只看单文件 | 跨文件数据流漏掉 | 追踪 Source→Sink 路径 |
| 规范不配 lint 规则 | 文档形同虚设 | 规则机检化 |
| 只审新代码不审存量 | 老代码漏洞长存 | 存量抽审 + 重点模块深审 |
| 审计报告不闭环 | 漏洞反复出现 | 修复 + 复测 + 规范补充 |
| 拿扫描器当唯一门禁 | 扫描器绕过即上线 | 人工评审兜底高风险变更 |
7. 总结
安全编码与代码审计是"成本最低的安全投资":
| 环节 | 动作 |
|---|---|
| 编码 | 输入白名单校验、输出上下文编码、最小权限 |
| 审计 | 数据流追 Source→Sink,工具筛 + 人工深挖绕过 |
| 规范 | 危险/安全写法对照表 + lint 规则 + 评审清单 |
| 闭环 | 每次漏洞复盘补一条规范,让历史不再重演 |
一句话记住:安全编码是把"默认不安全"变成"默认安全",代码审计是给"默认安全"加一道人工确认。规范写得再漂亮,没有评审与门禁落地,都是纸上安全。
延伸阅读
- SAST/DAST/SCA 代码安全分析 — 工具化扫描与门禁
- DevSecOps 流水线实践 — 把安全编码规范嵌入 CI/CD
- OWASP Top 10 深度解析 — 各类编码缺陷的系统归类
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。