TOCTOU(time-of-check to time-of-use)是指某个事物的状态在被检查的时刻与被使用的时刻之间发生变化的问题。本页介绍其工作原理及预防方法(不包含攻击步骤)。
什么是 TOCTOU
程序常以“先检查,若无问题再使用”的方式工作,例如“检查此文件是否可写,然后打开并写入”。但检查与使用之间存在一段很短的时间间隔。如果另一个程序或请求在这段间隔中改变了目标,检查的结果就不再成立。
MITRE 的 CWE-367 将其描述为:产品在使用资源前检查其状态,而该状态可能在检查与使用之间发生变化,从而使检查失效。CWE(Common Weakness Enumeration,通用缺陷枚举)是漏洞背后缺陷的共享目录。TOCTOU 是竞争条件的一种,其结果取决于同时运行的各项操作的时序。
为什么会发生
根据 CWE-367,其后果可能包括:访问本应禁止访问的资源、资源被以非预期方式更改,以及日志缺失。这段间隔可能很短,但在同时运行大量任务的系统中,它会被反复触发,而不仅仅是偶然发生。
常见出现的场景
| 场景 | 检查 | 使用 | 出现的问题 |
|---|---|---|---|
| 文件处理 | 存在性或权限检查 | 打开或写入 | 检查之后文件被替换 |
| Web 应用库存或余额 | SELECT 剩余数量 | UPDATE 减少数量 | 两个并发请求都通过检查并超出限制 |
| 支付与注册 | 是否仍未处理? | 执行操作 | 重复提交导致执行两次 |
| 文件上传 | 检查内容 | 存储或发布 | 检查之后内容被更改 |
关于上传检查,另请参阅 文件上传漏洞。
防御:不要将检查与使用分开
不要在使用前进行检查
CWE-367 最基本的建议是不要在使用之前进行检查。直接使用资源,并在失败时处理错误。这样就消除了间隔本身。
使用文件描述符而非路径
先打开文件,然后通过已打开的文件描述符检查状态并写入。反复查找同一路径名可能每次指向不同的目标。O_NOFOLLOW(不跟随符号链接)等标志,以及 openat(相对于目录打开)等调用也有帮助;可用性取决于操作系统。
数据库使用条件更新和事务
不要采用“先 SELECT 检查,再 UPDATE”,而是使用包含条件的单条 UPDATE(例如,仅当数量为 1 或以上时才减少)。当涉及多条语句时,使用具有合适隔离级别的事务和行锁。
在检查之前加锁
如果使用锁,应如 CWE-367 所述,在检查之前获取锁。在检查之后加锁无法保证你检查的目标就是你使用的目标。
使用幂等键防止重复执行
对于支付和注册,为每个请求分配唯一的键(幂等键),并确保每个键只执行一次操作。
如何检查自己的代码
请在你自己开发或管理的代码中进行。
- 查找检查代码与使用代码分属不同调用的位置(先检查存在性再打开;先 SELECT 再 UPDATE)
- 对每处位置,判断能否去掉检查,并将该步骤合并为单个操作
- 编写测试,同时发送同一请求多次,确认只有一次被执行
- 切勿对他人的服务进行此类测试;请使用你自己的环境
本站观点:将检查置于与使用相同的操作之中
TOCTOU 并非“有人忘记了检查”。检查确实存在,只是与使用分离了。添加更多检查代码无法解决问题。正确的修复方法是将检查置于与使用相同的原子操作之中,而不是将其作为额外步骤添加。在学习 IDOR 的同时理解它,有助于代码审查:两者都是“检查存在但未发挥作用”的缺陷。
主要来源
- MITRE CWE-367:Time-of-check Time-of-use (TOCTOU) Race Condition — cwe.mitre.org(定义、后果、缓解措施)
- 具体技术(O_NOFOLLOW、openat、幂等键、事务)作为通用实现实践进行描述;可用性因操作系统和数据库而异,请查阅各产品的官方文档
延伸阅读
- 术语表:什么是路径遍历 · 什么是 IDOR · 什么是 OWASP Top 10
- 指南:文件上传漏洞
- 术语表:什么是 CVE