🛡️ 第四章 系统漏洞的防范
📄 一句话概括

安全编程(安全编程)的核心思路是把"安全"从"个人意识"变成"工程约束":不指望每个开发者每次都记得做对,而是让做错的事情在流程里根本走不通——判断标准很朴素:一条安全要求,只有在它能"让构建失败"的时候,才算真的被落实了。

📝 本节和前面几节的关系

4-4 到 4-10 讲的都是"具体某个漏洞怎么防",这一节讲的是它们背后的共同做法:怎么让这些防法"自动发生"。

这一节也是本章里唯一一个"面向开发者"的立场——前面几节你是防守方(要打补丁、要看日志),这一节你是产出方(要写出不需要别人打补丁的东西)。 教材把它放在第四章接近结尾的位置,意思很清楚:与其一个一个修,不如从一开始就不产生。

一、核心思路:从"意识"到"闸门"

"大家要注意安全"这句话是最没用的安全措施——因为它的效果完全依赖于"每个人每次都不忘",而人一定会忘。

工程化的做法是把安全要求变成"闸门":在流程里设一个检查点,不通过就过不去。 这样做的效果是:安全不再依赖个人的记性,而变成系统属性的一部分。

2026 年最成体系的一套目标来自 CISA 的 Secure by Design 承诺(到 2026 年初,签署方已经超过 350 家)。它列了七个目标,下面把它们逐个翻译成"能被检查、能让发布失败"的工程闸门:

目标 可测的指标 不达标怎么办
多因子认证 默认开启率 新增租户不开 MFA 不放行
默认口令 首发即须改口令的产品数 该项不归零就不发版
整类漏洞消除 某一类弱点的存量计数 引入该类新问题的合入请求直接拒
补丁安装率 严重漏洞中位修复天数 互联网产品超过 14 天即告警
漏洞披露政策 是否发布 VDP 与 security.txt 没有就不能过合规审
CVE 完整性 已修漏洞是否都发了编号 漏发编号算未完成
入侵证据 基础包是否含安全日志 默认不带日志不予发布

这张表的关键在最后一列:每一个目标都对应一个"能让构建失败"的条件。这就是"闸门"和"口号"的区别——口号是"我们提倡",闸门是"你不做,代码就合不进去"。

二、为什么要"消除整类",而不是"一个一个修"

七个目标里最需要解释的是"整类漏洞消除"——它和"修漏洞"的区别在哪?

"修漏洞"是:发现一个、修一个——问题是漏洞会不断被找到,你永远在追。 "消除整类"是:某一种写法在你们的代码库里被禁止——比如"所有 SQL 必须参数化""所有密码必须用特定算法哈希""所有输出必须经过模板转义",于是这一类漏洞从"逐个修"变成"不再产生"。

这个思路的实际效果,在 CISA 2026 年的复盘里有直接的证据:

这三条合起来的意思很直接: 问题不在"防不住新攻击",而在"同一类错误反复犯"——二十年前就已经有定论的事情,今天还在漏洞榜上。

三、动手 Demo:把七个目标变成七条闸门

下面这段脚本把七个目标、它们对应的可测指标、以及"不达标怎么处理"排成一张表:

gates = [
    ("多因子认证",   "默认开启率",              "新增租户不开 MFA 不放行"),
    ("默认口令",     "首发即须改口令的产品数",  "该项不归零就不发版"),
    ("整类漏洞消除", "某一类弱点的存量计数",    "引入该类新问题的合入请求直接拒"),
    ("补丁安装率",   "严重漏洞中位修复天数",    "互联网产品超过 14 天即告警"),
    ("漏洞披露政策", "是否发布 VDP 与 security.txt", "没有就不能过合规审"),
    ("CVE 完整性",   "已修漏洞是否都发了编号",  "漏发编号算未完成"),
    ("入侵证据",     "基础包是否含安全日志",    "默认不带日志不予发布"),
]
for goal, metric, action in gates:
    print(goal, "|", metric, "|", action)

实跑输出:

  CISA 的 Secure by Design 承诺有七个目标(2026 年初已超 350 家签署方)
  下面把每个目标翻译成一条【能被检查、能让发布失败】的工程闸门。

一、七个目标,翻成七条闸门
  目标              可测的指标                      不达标怎么办
  ----------------------------------------------------------------------------------------------
  多因子认证        默认开启率                      新增租户不开 MFA 不放行
  默认口令          首发即须改口令的产品数          该项不归零就不发版
  整类漏洞消除      某一类弱点的存量计数            引入该类新问题的合入请求直接拒
  补丁安装率        严重漏洞中位修复天数            互联网产品超过 14 天即告警
  漏洞披露政策      是否发布 VDP 与 security.txt    没有就不能过合规审
  CVE 完整性        已修漏洞是否都发了编号          漏发编号算未完成
  入侵证据          基础包是否含安全日志            默认不带日志不予发布

  → 一个承诺只有在能【让构建失败】时,才不只是一份新闻稿。

二、CISA 2026 年的复盘说明了什么
  ① 大多数入侵不是国家级零日,而是犯罪分子扫描暴露的已知漏洞
  ② KEV 里有 41.5% 的条目属于【顽固弱点】——
     也就是 MITRE CWE Top 25 里几乎每年都出现的同一批老毛病
  ③ 按 2007 年的标准,今天最常见的 10 个弱点里有 3 个
     当年就算【不可原谅】(写法有定论、攻击路径明确、利用极简单)
  → 问题不在防不住,而在【同一类错反复犯】。

三、买家这一侧的动作(Secure by Demand)
  采购时值得问的四个问题:
    ① 你们已经【消除】了哪些漏洞类别?
    ② 是否提供机读的软件物料清单(SBOM)?
    ③ 默认是否提供抗钓鱼的认证方式?
    ④ 安全日志是否包含在基础产品里(不用另外买)?

读这个输出:

四、⚠️ 2026 现状

30 秒实验

今天做两个动作,各 15 秒:

  1. 打开你常用的任何一个软件或服务,看它的"安全"设置页,只问一个问题:那个最关键的开关(比如两步验证)现在是"默认开着"还是"默认关着"。如果默认是关的,说明这个产品把"安全"当成了一个可选项——而"默认安全"才是这一节讲的七个目标的方向。 这一步的意义是让你有一个判断产品的尺子: 一个真正重视安全的产品,不会把最重要的开关默认关掉。
  2. 如果你参与过任何软件的开发或验收,今天回想一个"某次忘了做某件安全的事"的例子。然后问自己:那件事有没有可能做成一条"做不过去"的检查(比如提交前自动检查、发布流水线里加一步、模板里默认开启)。把这件"靠记性"的事变成"靠流程"的事——这就是安全编程最小、也最可复制的动作。

这一节要带走的一句是:安全编程不是"让每个人都记住更多规则",而是"让错误的做法在流程里走不通"——判断一条安全要求有没有被真正落实,就看它能不能让构建失败。

常见误区

  1. 错误:安全是安全团队的事,开发者只管把功能写出来。——纠正:这个分工在 2026 年已经越来越不成立了。证据来自几个方向:① OWASP Top 10:2025 里排最前的"访问控制失效""安全配置错误"都是纯粹的实现问题(不是"安全团队配置错了",而是"代码写错了");② "安全左移"这个词的流行本身就说明业界共识:在开发阶段修一个漏洞的成本,远低于上线之后再修;③ 而"消除整类漏洞"这个目标,只有写代码的人才能做到——安全团队能发现和推动,但不能替研发改掉那一行拼接的 SQL。 所以准确的分工是: 安全团队负责"定标准、做检测、追踪进度",开发者负责"让代码本身不出这类问题"。
  2. 错误:我们做的是内部系统,不需要这么讲究。——纠正:"内部"这个前提经常不成立,而且它的后果往往更大。原因有两层: ① 攻击者的路径通常是"先拿到一个不重要的入口,再横向移动到重要的地方"(2026 年复盘里"大多数入侵来自扫描已知漏洞"讲的就是这种打法的起点);② 内部系统往往权限更大、日志更少、补丁更滞后——它们通常是"防守最松但价值最高"的那一类。 这也是本节把"入侵证据"(默认带日志)列为七个目标之一的原因: 内部系统最需要的恰恰是"出事时能被看见"。
  3. 错误:第三方组件是别人维护的,出了问题不怪我们。——纠正:站在用户的角度,责任在你这里。你的产品把它打进了包、你的更新机制把它推给了用户,那么"你用了什么"就是你的责任。2026 年的插件投毒事故(4-12:25 个插件被投毒、约 80 万以上站点受影响)里,受影响的站点并没有"做错什么"——它们只是"安装了插件"。所以正确的做法是两条: ① 用软件物料清单(SBOM)知道自己用了什么;② 把"依赖的更新"也纳入审查流程(不是所有自动更新都该无条件放行)。
  4. 错误:我们把安全写进了制度文件,就算落实了。——纠正:制度文件是"口号"的书面形式,它和"闸门"的区别就是本节的判断标准:能不能让构建失败。一份写着"所有 SQL 必须参数化"的规范,如果没有任何检查手段去执行它,它的实际效果等于零。把它变成闸门的做法很具体:把静态检查工具接进流水线(发现拼接 SQL 就报错)、把依赖扫描接进流水线(发现高危组件就拦住发布)、把"新增租户必须开启 MFA"写成代码里的默认值。所以这一节最后那句判断标准值得反复用: "这条要求能不能让构建失败?"——不能的话,它还只是一个愿望。
自测三问(点开对答案) 1. "闸门"和"口号"的区别,用一句话怎么说? —— **闸门有后果,口号没有**。**具体到操作上:闸门是"这条检查不通过,代码就合不进去、版本就发不出去";口号是"我们提倡大家注意安全"。** **这个区别之所以重要,是因为它决定了这件事"依赖什么"**:**口号依赖每个人的记性和责任心(一定会失效),闸门依赖流程本身(不通过就是过不去)。** **本节那张表之所以要专门列出"不达标怎么办"这一列,就是在把每个目标从口号改造成闸门**——**这也是判断一份安全方案是否认真的最快方式:看它有没有"不达标怎么办"。** 2. 为什么"KEV 里 41.5% 属于顽固弱点"这个数字,支持了"消除整类漏洞"这个思路? —— **因为它说明被实际利用的漏洞里,将近一半不是"新奇的攻击",而是"早就知道怎么防的老毛病"**。**"顽固弱点"的定义就是"MITRE CWE Top 25 里几乎每年都会出现的同一批问题"**——**换句话说:这些漏洞已经被记录了十几年,防御方法早有定论,但它们仍然在被写出来、被利用。** **这就说明"一个一个修"是不够的**:**你今年修掉的这个问题,明年会在另一段代码里再出现一次**(**因为根本原因是"那种写法被允许"**)。**所以更有效的做法是从源头禁止那种写法**(**换语言、用参数化、让框架默认转义、把检查接进流水线**)——**这就是"消除整类",也是这七个目标里技术含量最高的一条。** 3. 为什么把"安全日志默认包含在基础产品里"也算一条安全目标? —— **因为它决定了"出了事之后你能不能知道、能不能追查"**。**安全的完整链条是"预防 → 检测 → 响应 → 复盘",日志是后面三环的前提**。**没有日志:** **你无法回答"我被入侵了吗"(检测不了)、"攻击者是从哪里进来的"(响应不了)、"这个漏洞影响了多少客户"(复盘不了)**。**这也是 CISA 2026 年复盘能得出"大多数入侵来自扫描已知漏洞"这个结论的前提**——**没有日志,这种统计根本做不出来。** **更实际的一点是:** **日志常常被厂商当成"高级版功能"来卖**,**而这一条要求正是要把它拉回基础包里**——**因为"看不见"比"挡不住"更危险:挡不住至少还有机会被检测到,看不见则连机会都没有。**

相关笔记