基于功能的威胁建模
某学生门户网站新增了一个上传功能。学生可以提交作业,老师可以留下反馈。
这功能听起来小到很容易开发。一个表单、一个文件输入框、一个提交按钮,再加上一个老师查看的页面。
它同样小到可以被认真地做一次威胁建模。
**基于功能的威胁建模**会拿出一个功能,从头到尾走一遍流程。每走一步,你都要问:这里可能出什么问题?代码需要检查什么?
梳理功能流程
先从正常流程说起:
- 学生登录。
- 学生上传一个文件。
- 服务器存储该文件。
- 老师打开这份提交。
- 老师留下反馈。
- 学生阅读反馈。
这份清单就是你的地图。一开始不需要多复杂的图表。
现在标出每一个值跨越信任边界的地方:
| 步骤 | 跨越边界的值 | 该问的问题 |
|---|---|---|
| 上传 | 文件名、文件类型、文件大小 | 服务器有没有检查文件本身? |
| 存储 | 路径和元数据 | 一个学生能不能覆盖另一个学生的文件? |
| 老师查看 | 提交 id | 这位老师是否被分配给这个学生? |
| 反馈 | 反馈文本 | 显示时会不会变成脚本? |
| 学生查看 | 反馈 id | 一个学生能不能读到另一个学生的反馈? |
这样一来,这个功能就有了清晰的轮廓,你也能看出安全决策该放在哪里。
先讲故事,再补威胁
如果一上来就列威胁名称,练习会显得很抽象。
如果先从功能的故事讲起,每个威胁都能落到具体的地方。
然后标出你的应用接收到"自己没有主动选择的东西"的那些时刻。那些地方就是服务器需要多问几个问题的地方。
把威胁变成检查项
功能模型不应该只停留在一堆担忧上。
对于上传功能来说,一个担忧可能是:
学生可能上传不安全的东西。
这话没错,但太模糊了,没法据此开发。把它变成检查项:
- 存储前先限制文件大小。
- 只允许预期的文件类型。
- 存储的文件名由服务器生成。
- 文件存放在公开应用目录之外。
- 显示文件前先检查这位老师是否被分配给该学生。
现在这个模型就能变成验收标准了。
当下一位开发者能把某个威胁直接转化为代码或测试时,这个威胁才真正有用。
"上传不安全"是个担忧。"限制文件大小,并由服务器生成存储的文件名"才是一个可以被开发出来的检查项。
在老师反馈功能上试一试
现在轮到你自己用同样的方法来练习了。
这个功能是这样的:老师可以对学生的提交留下反馈。
在往下读之前,先回答这五个基于功能的问题。
- 谁发送这个值?
- 之后谁会读到它?
- 哪个决策很重要?
- 可能出什么问题?
- 代码应该检查什么?
对照你的答案
| 问题 | 一个合理的答案 |
|---|---|
| 谁发送这个值? | 老师发送反馈文本。 |
| 之后谁会读到它? | 学生,也可能是另一位老师。 |
| 哪个决策很重要? | 只有被分配的老师才能留下反馈。 |
| 可能出什么问题? | 反馈里可能藏有脚本,或者关联到错误的提交上。 |
| 代码应该检查什么? | 在服务器端检查分配关系、文本长度,以及显示时的输出安全。 |
第2个问题分量最重,却最容易被忽略。说清楚"之后谁会读到这个值",才能让你意识到它会被渲染进页面里,也正因为如此,"可能藏有脚本"才是一个值得问的问题。
只回答第1个问题,你得到的是输入校验。把第2个问题也一起回答,你还能得到输出安全。
具体的修复方法会在手册后面讲到。现在重要的是养成习惯:梳理功能、标出边界、写出检查项。这就是这套方法的全部内容。
前端可以引导,但决定权在服务器
页面可以对没有被分配的老师隐藏反馈操作控件。
但路由仍然必须检查分配关系,因为任何人都可以直接调用这个路由。
谁可以写反馈?它属于哪份提交?之后谁可以读到它?安全思维就是在代码悄悄替你做出这些决定之前,先自己把它们找出来。
接下来往哪走
基于功能的威胁建模,是 STRIDE 的日常简化版。你可以在规划阶段直接用它,把检查项写进工单,并在功能上线前完成测试。
接下来,OWASP 与真实漏洞会把时间线倒过来。它不再是让你在发布前预想可能出什么问题,而是给那些已经在真实应用里出过问题的东西一一命名。

