结论:TOCTOU(time-of-check to time-of-use)指检查与使用之间存在时间窗,攻击者在窗内改变状态使检查结果失效。OS 层的经典是"检查文件权限后再打开"被符号链接替换攻击;Web 业务里同样的模式无处不在:先查余额/库存/优惠券是否可用,再扣减——并发请求同时通过检查,余额被扣成负数、一张券用多次。

展开:Web 例子与修复:

-- 漏洞流程:两个请求同时读到 balance=100,都通过检查,各扣 80
SELECT balance FROM account WHERE id=1;   -- 检查
UPDATE account SET balance = balance - 80 WHERE id=1;  -- 使用

修复手段按强度:1)让检查和修改原子化——UPDATE account SET balance = balance - 80 WHERE id=1 AND balance >= 80,影响行数为 0 即失败,条件写进同一语句;2)数据库唯一约束兜底——优惠券使用记录表对 (coupon_id, user_id) 建唯一索引,重复插入直接失败,业务层捕获冲突;3)行锁/乐观锁——SELECT ... FOR UPDATE 或版本号 CAS;4)单资源热点走串行化(Redis Lua 脚本原子执行 check-and-set、或按用户 ID 哈希到固定队列)。易错点:1)依赖应用层 synchronized/单机锁,多实例部署即失效——锁必须在数据层;2)以为事务隔离级别 Repeatable Read 能自动解决——快照读看不到并发修改,照样双花,需要显式加锁读或原子写;3)HTTP 层的重复提交(双击、重试)与并发攻击同根,幂等键一并解决。

追问方向:limit racing(Turbo Intruder 单包多请求技术 bypass 速率限制)、文件系统 TOCTOU 的现代缓解(openat2 的 RESOLVE_NO_SYMLINKS)。