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

STRIDE 威胁建模

QuickBite 要上线在线点餐功能。顾客挑选食物、付款,然后等餐厅确认订单。

正常流程很清晰:顾客提交订单,厨房收到订单,应用发送状态更新。

但安全考虑要问一个不一样的问题:在我们把它做出来之前,会出什么问题?

这个问题有个名字,叫**威胁建模**。你审视一个设计,想象它可能被怎样滥用,然后决定代码需要做哪些防范。

威胁建模发生在漏洞报告之前

代码审查看的是已经存在的代码。

威胁建模看的是还处在改动成本很低阶段的功能。

STRIDE 清单

STRIDE 是一个助记词,代表攻击者可能捣乱系统的六种方式。这几个字母分别代表:

字母威胁通俗提问
S仿冒(Spoofing)有人能冒充别人吗?
T篡改(Tampering)有人能更改不该由他们更改的数据吗?
R抵赖(Repudiation)有人能因为没有记录而否认自己做过某事吗?
I信息泄露(Information disclosure)有人能看到不该看到的数据吗?
D拒绝服务(Denial of service)有人能让应用对其他人无法正常工作吗?
E权限提升(Elevation of privilege)有人能做超出自己角色权限的事吗?

课程里有一句话说得很实用:STRIDE 给你六种思路,让你去想攻击者可能怎么捣乱你的系统。

我们把这六个问题拿来问一问 QuickBite。

JunoSTRIDE 清单 STRIDE 就是给你的想象力列一张清单。它能防止你只想到脑海里冒出来的第一个安全问题就停下。

一旦把这几个字母换成通俗的问题,它们就没那么吓人了:有人能冒充、更改、否认、读取、阻断,或者做超出权限的事吗?

JunoSTRIDE 清单 用 STRIDE 能让审查不再取决于恰好在场的是谁。六个提问,同样的顺序,每个功能都过一遍。

真正有用的产出不是这个缩写本身,而是你能转化成具体工作的那一行:服务端在展示订单前先检查是谁拥有这个订单、支付金额在服务端计算、订单变更要记录账号和时间。

JunoSTRIDE 清单 STRIDE 源自微软 20 世纪 90 年代末的威胁建模工作,这提醒我们一件事:真正留存下来的不是当年的工具,而是这套可以反复使用的提问清单。

这几个字母并不是万能保证。它们漏掉了这样一种滥用:用户完全按照自己角色允许的权限行事,只是规模大到造成了危害;它们也不会替你判断你一开始是否就不该收集这些数据。

把 STRIDE 当作一次严谨的初步排查,而不是安全之神颁发的合格证书。安全之神目前联系不上,而项目周五就要交付。

用 STRIDE 走一遍 QuickBite

先从一个功能流程开始:

  1. 顾客创建订单。
  2. 服务端计算总价。
  3. 顾客付款。
  4. 餐厅接受或拒绝订单。
  5. 顾客收到状态更新。

现在把 STRIDE 的六个问题拿来对照这个流程逐一发问。

威胁QuickBite 示例应对决定
仿冒某个请求冒充成另一位顾客。使用已登录的会话身份,而不是请求体里的用户 id。
篡改浏览器发送了一个更便宜的价格。由服务端根据菜单数据计算价格。
抵赖餐厅声称自己从未拒绝过某个订单。记录谁在什么时候修改了订单状态。
信息泄露一位顾客能读到另一位顾客的订单。返回订单详情前先检查归属权。
拒绝服务某个用户发送了成千上万个订单。加上速率限制和请求体大小限制。
权限提升顾客调用了本应仅限餐厅使用的接口。在服务端检查角色和餐厅所属关系。

这张表本身就已经很有用了。它告诉团队,在这个功能上线前,哪些检查是必须存在的。

威胁模型只有在每个威胁都落实成一个具体决定时才有意义。

Juno用 STRIDE 走一遍 QuickBite 选一个功能,像讲故事一样把它走一遍。每一步都问一问:有人能冒充、更改、否认、读取、阻断,或者做没有权限做的事吗?

然后在旁边写下应对决定。一个担忧只有在有人能把答案做出来或测出来时,才真正有用。

Juno用 STRIDE 走一遍 QuickBite 这张表就是最终产出。把它放在工单旁边,而不是埋进一份计划会开完之后没人再打开的文档里。

尽量把每一行写成可验收的标准。"服务端根据菜单数据计算总价"是可以直接开发的;"价格可能被篡改"只是一句大家点头认可、然后就忘掉的备注。

Juno用 STRIDE 走一遍 QuickBite 最容易被忽视的失败方式,是写下一堆威胁却没有指定负责人。每一行最终都应该归为三种结果之一:立刻修复、说明理由后接受、或者指定负责人延后处理。

这听起来有点官僚,直到事故复盘时有人问:为什么订单总价当初信任了浏览器传来的值。一个标注了日期、写明理由的"已接受风险"是一个决定;一个空白的决定栏只是一堆考古材料,而且光线还更差。

潜在威胁建模

潜在威胁建模从更宽的范围入手。你审视的不是单个功能,而是整个系统或系统中很大的一部分。

对 QuickBite 来说,这可能意味着:

  • 顾客登录后能做的所有事情
  • 餐厅老板能做的所有事情
  • 整条支付链路
  • 订单数据离开应用的每一个出口

当你接手一个已有应用、启动一个新系统,或者想找出老设计里的问题时,这种方式很有用。

风险在于范围失控。"整个应用"很容易变成一场冗长的会议和一屋子疲惫的人。要选一个大家能明确指出边界的范围。

开始前先划定范围

潜在威胁建模需要一个固定的范围和一个固定的时长。

少了任何一项,这个过程就会一直膨胀,直到没人再能做出任何决定。

Juno潜在威胁建模 潜在威胁建模是"放宽视野"的版本。你审视整个区域,比如支付链路,然后问那里可能出什么问题。

把边界控制在能看得清的范围内。"支付链路"是有用的范围,"整个应用"通常只会变成一团迷雾。

Juno潜在威胁建模 当系统的整体形态比单个功能更重要时,就用潜在威胁建模。顾客操作、餐厅老板操作、支付、上传和管理工具,都是不错的范围选择。

在会议开始前先写清楚边界。如果某个威胁落在边界之外,就先搁置。不然这场会就会变成把产品里所有令人担心的地方都过一遍的巡礼。

Juno潜在威胁建模 大范围的排查很少能直接产出一个迭代周期能完成的任务,它更多暴露的是结构性问题:一个支付流程被太多调用方共用、一个管理后台和顾客共用同一套会话机制、一个没人认领的 webhook 路径。

这依然很有价值,但要让产出指向规划环节。按放着不管的代价给发现的问题排优先级,并把论证写清楚,清楚到以后有人想推动解决时,不需要凭记忆重建当初那场会议。风险决策一旦只存在于记忆里,迟早会变成传说。

动手试一试

QuickBite 新增了一个功能:顾客可以对已完成的订单留下评价,餐厅可以回复。

用 STRIDE 把它走一遍。针对每个字母,说出一个可能出错的地方,以及对应的检查手段。

对照你的答案
字母可能出错的地方检查手段
S 仿冒有人冒充另一位顾客发布评价服务端从会话中获取作者身份,绝不从请求体中获取
T 篡改餐厅事后修改了某条评价的内容或评分只有作者本人能编辑自己的评价,回复是独立的记录
R 抵赖餐厅否认自己发布过一条辱骂性回复记录谁在什么时候写了什么内容,带上账号 id
I 信息泄露评价里暴露了顾客的全名或地址在存储数据前先确定哪些字段是公开的,只返回这些字段
D 拒绝服务有人发布了成千上万条评价,或者一条长达一兆字节的评价对文本长度做限制,对接口做速率限制
E 权限提升顾客以餐厅的身份进行了回复回复接口检查该账号是否确实拥有那家餐厅

有两点值得注意。R(抵赖)是大家最容易跳过的一项,因为在有人提出争议之前一切看起来都好好的,而到了那时候,记录要么存在,要么不存在,没有补救的余地。

S(仿冒)有一个能一劳永逸解决问题的办法:身份信息只从会话中取,绝不取自调用方发来的字段。如果一条评价自带一个 authorId 字段,那任何人都能冒用别人的名字来签署这条评价。

接下来去哪儿

STRIDE 给了你这六个问题。下一章要改变的是审视的尺度。

不再是扫视整个系统,基于功能的威胁建模把这些问题一次只落到一个功能上。这是大多数开发者能在日常规划中直接用上的版本。