网络安全导论
Web 应用中大多数安全漏洞,本质上都是普通的编程失误,只不过后果比较特殊。
一条查询语句是靠拼接字符串拼出来的。一个模板把访客的名字原封不动地写进了页面。一个路由从 URL 里读出一个 id,不检查是谁在请求,就把记录返回了。
这些代码在评审时看起来都挺合理,直到你看到那个专门利用它的请求为止。
安全工作,说到底大部分时候就是养成一个习惯:问问自己,一个恶意版本的这个输入会做什么。
上面这些漏洞,无一例外都踩在一条**信任边界**上——这是这本手册里最重要的一个概念。
什么是信任边界?
信任边界,就是你系统里你能掌控的部分和你无法掌控的部分之间的那条线:浏览器、第三方 API、上传的文件,都在线的另一侧。任何跨越这条线进来的东西,都得在这一侧接受检查。
如何用安全的思维方式思考 讲的就是在一个典型的 Web 应用里,这条线该画在哪儿。
这本手册讲什么
这是一本面向开发者的应用安全手册:讲的是你自己写、评审、上线的代码的安全问题。
它不涉及网络安全、恶意软件分析、蓝队监控、红队行动或合规框架。这些都是各自有一套完整体系的大领域,如果你是奔着这些来的,去别处能找到更适合的资料。
书里的每个代码示例都用 Node 和 Express 写成,用 Zod 描述你期望的数据结构,所以示例看起来就像你自己可能正在维护的服务。
各章分为四个部分,第一次阅读时建议按顺序看,因为后面的章节要用到前面章节建立的概念和术语。
| 部分 | 内容 | 从这里开始 |
|---|---|---|
| 第一部分 · 像攻击者一样思考 | 找出外部数据进入系统的位置,用 STRIDE 预测威胁,用 OWASP 给已知漏洞命名,以及如何写一份漏洞报告 | 如何用安全的思维方式思考 |
| 第二部分 · 输入与数据安全 | 那些著名的攻击手法,说穿了其实都是同一个错误换了个马甲,然后是能一次性堵住它们的校验方法 | 永远不要相信用户输入 |
| 第三部分 · 身份验证与身份识别 | HTTP 在请求之间不会记住你,所以每个应用都得选一种方式来记住用户。三种方式,逐一对比 | 身份验证与授权 |
| 第四部分 · 速率限制与流量控制 | 在流量不友好的情况下,如何让服务保持正常响应。五种算法,两种应对方式 | 速率限制基础 |
四个部分,开篇讲思维方式,收尾讲怎么保住服务不倒。到现在我回头翻得最多的,还是第一部分。
你需要先具备什么基础
如果你能写一点 JavaScript,见过一个小型的 Node 和 Express 服务器,那你从这本手册里能学到的东西会最多。只要能一眼认出路由和中间件函数就够了。
有一个概念在这里比其他任何东西都重要。当有人访问你的网站时,他们的浏览器会发送一个请求:他们请求的地址、一些请求头,以及通常还带着他们输入内容的请求体。
这里面的每一项内容都是对方写的——包括你自己页面预先填好的那些字段。
没有任何东西能阻止一个人跳过你的页面,直接发送他们想发的任何内容。
有个 Express 的细节需要先说清楚,因为后面大多数修复方案都建立在它之上。中间件按你注册的顺序执行,所以一个检查必须放在信任它结果的路由之前。放在后面注册的检查,什么都保护不了。
JavaScript 手册 讲的是语言层面的内容,其中关于DOM 的那一章,为这里讲解如何转义写入页面的内容打下了基础。
我自己花了好一阵子才改掉把前端当成可信来源的习惯,一旦改过来,这里讲的几乎所有东西都会变得容易理解。这里也不假设你有任何安全方面的背景知识。
我们如何讲解漏洞
每一章在讲解真实存在的安全弱点时,都遵循同样的四个步骤,所以你只要读懂一章,其他章节就能触类旁通。
- 有问题的代码,篇幅足够短,能一口气读完,而且和你在实际项目里可能见到的代码很接近。
- 攻击载荷(payload),也就是真正能让它出问题的输入或请求。
- 为什么会出问题,顺着代码逐步分析,因为一个你自己都解释不清的修复方案,早晚会被你自己撤掉。
- 修正方案,并说明它覆盖了哪些变种情况,又没覆盖哪些。
篇幅有限的入门介绍里,这四步大概长这样:
// 有漏洞:name 被直接拼接进了查询文本
app.get('/users', (req, res) => {
const rows = db.query(`SELECT * FROM users WHERE name = '${req.query.name}'`)
res.json(rows)
})
// 攻击载荷:/users?name=' OR 1=1 --
// 这个引号提前把字符串闭合了,于是 OR 1=1 作为一个独立的
// 条件混入了查询语句,匹配了表里的每一行数据。
// 修复后:值和查询语句是分开传递的,永远不会被当作 SQL 解析
const rows = db.query('SELECT * FROM users WHERE name = ?', [req.query.name])展示攻击载荷是刻意为之的。抽象地描述注入攻击,你看过就忘,而这类漏洞在别人的 pull request 里、在下午三点犯困的时候,其实很难被一眼认出来。
有一条边界贯穿始终
展示这些攻击载荷,是为了帮你在自己负责的代码里找出这一类漏洞。请只在你拥有所有权、或者获得了明确测试授权的系统上运行它们,别的地方一概不要用。
这本手册自身也遵守这条边界:不会介绍扫描工具,不会针对第三方目标,不会讲检测规避,也不会演示如何把一个弱点串联成另一个更大的攻击。
要记住的规则是:这些东西只能拿来对付你自己的项目。
认识你的向导
这本手册配备了三位向导,由你来选择让哪一位陪你走完全程。
所有人读到的基础内容都是一样的,用最直白的方式写给第一次接触安全领域的人。选择更深入的向导,会在基础之上加料:讲更广泛的模式、第一版修复方案漏掉的情况、背后的取舍权衡。你选的向导,也决定了讲述的语气风格。
你可以随时在任意页面顶部的选择器里切换向导,也可以点击右下角的偏好设置来切换向导或调整字号。
难度级别就是你当天想要的讲解深度,所以哪一章感觉太浅或太难,随时都可以切换。看到一半往上调,多出来的内容会直接插入原地,之前读过的部分不会受影响。
如果某一章感觉内容偏重,可以把那一页的难度调低一档试试。不会漏掉什么内容,底层的基础都是一样的。
接下来讲什么
四个部分,三十二章,一个贯穿始终、不断被打破又被修复的实例。
什么是网络安全? 先把这个词的范围界定清楚,然后才引入各种术语,接着如何用安全的思维方式思考 会开始培养这本手册后续都要用到的那个习惯。
更喜欢边做边学?Scrimba 的 Learn Cybersecurity 课程通过你在浏览器里就能动手解决的互动挑战,讲解同样的内容。
