Skip to content
This page has been auto-translated and may contain errors.View in English

网络安全导论

Web 应用中大多数安全漏洞,本质上都是普通的编程失误,只不过后果比较特殊。

一条查询语句是靠拼接字符串拼出来的。一个模板把访客的名字原封不动地写进了页面。一个路由从 URL 里读出一个 id,不检查是谁在请求,就把记录返回了。

这些代码在评审时看起来都挺合理,直到你看到那个专门利用它的请求为止。

安全工作,说到底大部分时候就是养成一个习惯:问问自己,一个恶意版本的这个输入会做什么。

上面这些漏洞,无一例外都踩在一条**信任边界**上——这是这本手册里最重要的一个概念。

什么是信任边界?

信任边界,就是你系统里你能掌控的部分和你无法掌控的部分之间的那条线:浏览器、第三方 API、上传的文件,都在线的另一侧。任何跨越这条线进来的东西,都得在这一侧接受检查。

如何用安全的思维方式思考 讲的就是在一个典型的 Web 应用里,这条线该画在哪儿。

这本手册讲什么

这是一本面向开发者的应用安全手册:讲的是你自己写、评审、上线的代码的安全问题。

它不涉及网络安全、恶意软件分析、蓝队监控、红队行动或合规框架。这些都是各自有一套完整体系的大领域,如果你是奔着这些来的,去别处能找到更适合的资料。

书里的每个代码示例都用 Node 和 Express 写成,用 Zod 描述你期望的数据结构,所以示例看起来就像你自己可能正在维护的服务。

各章分为四个部分,第一次阅读时建议按顺序看,因为后面的章节要用到前面章节建立的概念和术语。

部分内容从这里开始
第一部分 · 像攻击者一样思考找出外部数据进入系统的位置,用 STRIDE 预测威胁,用 OWASP 给已知漏洞命名,以及如何写一份漏洞报告如何用安全的思维方式思考
第二部分 · 输入与数据安全那些著名的攻击手法,说穿了其实都是同一个错误换了个马甲,然后是能一次性堵住它们的校验方法永远不要相信用户输入
第三部分 · 身份验证与身份识别HTTP 在请求之间不会记住你,所以每个应用都得选一种方式来记住用户。三种方式,逐一对比身份验证与授权
第四部分 · 速率限制与流量控制在流量不友好的情况下,如何让服务保持正常响应。五种算法,两种应对方式速率限制基础
Juno这本手册讲什么 我们只聚焦安全领域里的一角:你自己写和评审的代码,用 Node 和 Express,加上 Zod 检查一切进来的数据。

四个部分,开篇讲思维方式,收尾讲怎么保住服务不倒。到现在我回头翻得最多的,还是第一部分。

Juno这本手册讲什么 范围是 Node、Express 和 Zod 上的应用安全。网络、恶意软件、蓝队和合规都不在内。

第一次阅读请按顺序来。第三部分要用到第二部分讲的输入相关词汇,第四部分则假设你已经能识别出一个客户端——这正是第三部分打下的基础。

Juno这本手册讲什么 范围划得这么窄是故意的:只讲你要负责的那部分代码,不涉及挂着同一个词、却完全是另一回事的其他领域。

值得知道这种窄范围的代价在哪。密码存储、访问控制模式、CSRF、响应头以及供应链风险,会在某一章需要用到时顺带提一句,但都没有独立成章。

真遇到这些问题时,去查对应的 OWASP 速查表。这本手册至少会让你具备读懂它的词汇储备。

你需要先具备什么基础

如果你能写一点 JavaScript,见过一个小型的 Node 和 Express 服务器,那你从这本手册里能学到的东西会最多。只要能一眼认出路由和中间件函数就够了。

有一个概念在这里比其他任何东西都重要。当有人访问你的网站时,他们的浏览器会发送一个请求:他们请求的地址、一些请求头,以及通常还带着他们输入内容的请求体。

这里面的每一项内容都是对方写的——包括你自己页面预先填好的那些字段。

没有任何东西能阻止一个人跳过你的页面,直接发送他们想发的任何内容。

有个 Express 的细节需要先说清楚,因为后面大多数修复方案都建立在它之上。中间件按你注册的顺序执行,所以一个检查必须放在信任它结果的路由之前。放在后面注册的检查,什么都保护不了。

JavaScript 手册 讲的是语言层面的内容,其中关于DOM 的那一章,为这里讲解如何转义写入页面的内容打下了基础。

Juno你需要先具备什么基础 会 JavaScript、懂一点 Node,就足够上手了。要记住的核心观念是:请求里的每一部分都是发送者自己输入的,包括你自己页面预先填好的字段。

我自己花了好一阵子才改掉把前端当成可信来源的习惯,一旦改过来,这里讲的几乎所有东西都会变得容易理解。这里也不假设你有任何安全方面的背景知识。

Juno你需要先具备什么基础req 上的每个字段都当成攻击者可控的。请求是通过好几个属性到达的,检查了其中一个不代表其他的也没问题:req.params 存放路径片段,req.query 存放查询字符串,req.body 存放解析后的请求体,req.headers 存放其余的请求头。

所以,只在 req.body 上加了校验规则,后面读取 req.query.sort 的处理函数就完全没受到保护。

Juno你需要先具备什么基础 威胁模型涵盖整个请求:请求头、cookie、请求体、路径,以及你自己客户端填充的每一个字段。

运行时环境的因素也得考虑进来。推理逻辑可以在不同技术栈之间迁移,但默认配置和并发模型很少能通用。Node 单一事件循环就是最典型的例子:一个写法上容易发生回溯的正则表达式,或者处理函数里一次同步的哈希调用,都会卡住当前所有其他正在处理的请求。

一个在“每请求一个线程”的服务器上只会体现为延迟增加的输入体积漏洞,在这里会直接变成服务不可用的故障。

我们如何讲解漏洞

每一章在讲解真实存在的安全弱点时,都遵循同样的四个步骤,所以你只要读懂一章,其他章节就能触类旁通。

  1. 有问题的代码,篇幅足够短,能一口气读完,而且和你在实际项目里可能见到的代码很接近。
  2. 攻击载荷(payload),也就是真正能让它出问题的输入或请求。
  3. 为什么会出问题,顺着代码逐步分析,因为一个你自己都解释不清的修复方案,早晚会被你自己撤掉。
  4. 修正方案,并说明它覆盖了哪些变种情况,又没覆盖哪些。

篇幅有限的入门介绍里,这四步大概长这样:

js
// 有漏洞: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 里、在下午三点犯困的时候,其实很难被一眼认出来。

有一条边界贯穿始终

展示这些攻击载荷,是为了帮你在自己负责的代码里找出这一类漏洞。请只在你拥有所有权、或者获得了明确测试授权的系统上运行它们,别的地方一概不要用。

这本手册自身也遵守这条边界:不会介绍扫描工具,不会针对第三方目标,不会讲检测规避,也不会演示如何把一个弱点串联成另一个更大的攻击。

Juno我们如何讲解漏洞 第一次看到一个真能用的攻击载荷,可能会觉得有点怪,好像被塞了什么不该看的东西。这跟食品安全课上会给你看变质食物长什么样是一个道理:认得出它,才是重点所在。

要记住的规则是:这些东西只能拿来对付你自己的项目。

Juno我们如何讲解漏洞 每次都是同样的四步:有问题的代码、攻击载荷、为什么会出问题、修复方案。每次都要仔细读攻击载荷,因为以后做代码评审时,你要靠模式匹配来识别的就是它。

大家最容易跳过的是第三步。一个你自己都没搞懂就照搬来的修复方案,等下一个觉得它碍事的人一来,就会被删掉。

Juno我们如何讲解漏洞 可利用性决定了一个问题是这周就得修的紧急事项,还是记在待办清单里慢慢来的小事,而这只有对照具体的攻击载荷才能判断。这就是为什么每一章都会附上一个。

第四步同样重要。一个没说清楚适用范围的修复方案,最后会变成以讹传讹的“土办法”:有人把它套用到它根本不覆盖的场景上,而这个漏洞会在一次看似很彻底的评审中安然无恙地溜过去。这里的每一个修复方案,都会明确说清楚它解决了什么、没解决什么。

认识你的向导

这本手册配备了三位向导,由你来选择让哪一位陪你走完全程。

所有人读到的基础内容都是一样的,用最直白的方式写给第一次接触安全领域的人。选择更深入的向导,会在基础之上加料:讲更广泛的模式、第一版修复方案漏掉的情况、背后的取舍权衡。你选的向导,也决定了讲述的语气风格。

你可以随时在任意页面顶部的选择器里切换向导,也可以点击右下角的偏好设置来切换向导或调整字号。

难度级别就是你当天想要的讲解深度,所以哪一章感觉太浅或太难,随时都可以切换。看到一半往上调,多出来的内容会直接插入原地,之前读过的部分不会受影响。

Juno认识你的向导 又见面了,还是我。这里的“初学者”不是说编程新手,而是说安全领域的新手,大多数人其实都是这样。

如果某一章感觉内容偏重,可以把那一页的难度调低一档试试。不会漏掉什么内容,底层的基础都是一样的。

Juno认识你的向导 不管你选哪位向导,基础页面的内容都是一样的。变的是站在你身边的是谁,以及他们能带你走多深。

碰到一个你已经很熟的话题,值得切换着看看:这是判断接下来的课程里哪个难度最适合你的最快方式。

Juno认识你的向导 这个难度级别追求的是生产环境里的判断力,而不是为了钻研内部原理而钻研:一个修复方案要付出什么代价,它在什么情况下会失效,你接受的是哪种取舍。

选这个难度,是因为它是你今天想要的深度,而不是因为你干这行干了多久。不少经验丰富的开发者,遇到不熟悉的话题时也会回去读基础版,读完再回来。

接下来讲什么

四个部分,三十二章,一个贯穿始终、不断被打破又被修复的实例。

什么是网络安全? 先把这个词的范围界定清楚,然后才引入各种术语,接着如何用安全的思维方式思考 会开始培养这本手册后续都要用到的那个习惯。

更喜欢边做边学?Scrimba 的 Learn Cybersecurity 课程通过你在浏览器里就能动手解决的互动挑战,讲解同样的内容。