当前位置:首页 > 知识

漏洞代码

```html 漏洞代码

漏洞代码

去年秋天,我在一个电商后台里翻出一行代码。漏洞代码它长这样:

if (user.isAdmin = true) { grantAccess(); }

少了一个等号。漏洞代码赋值变成了比较。漏洞代码任何登录用户,漏洞代码只要走到这个分支,漏洞代码都会变成管理员。漏洞代码我盯着屏幕愣了几秒——这玩意儿已经在生产环境跑了七个月,漏洞代码没人发现。漏洞代码没人发现的漏洞代码原因是,它藏在一个“仅限内部测试”的漏洞代码开关后面,而那个开关从来没被关掉。漏洞代码

这就是漏洞代码漏洞代码的典型长相:它不一定是恶意写的,甚至不一定是漏洞代码错的。它只是漏洞代码恰好在你最不希望的地方,做了你最不希望的事。

我后来养成了一个习惯,每隔几周就翻一遍项目里的 git log,专门看那些“紧急修复”“临时方案”“先这样吧”的提交。十次里有六次,这些提交会在三个月内变成事故报告里的第一行。有一次我找到一段处理优惠券的代码,逻辑是:如果用户输入了优惠码,就减免;如果没输入,就按原价。但作者把“没输入”的判断写成了 if (coupon == null || coupon == ""),而前端传过来的是一个空字符串 ""。这没问题。问题在于,另一个同事后来加了一个“自动填充最近使用优惠券”的功能,填充失败时传的是字符串 "null"。于是 coupon == "" 不成立,coupon == null 也不成立,程序继续往下走,把 "null" 当成有效优惠码去查数据库,查不到,报错,整个结算流程卡死。那天的客诉量涨了三倍。

你可能会说,加个类型检查不就完了。对,但加类型检查的人往往不是写这行代码的人。写这行代码的人当时想的是“反正前端会传空串”,加类型检查的人想的是“反正后端会处理异常”。两个“反正”撞在一起,就是漏洞。

我见过最离谱的一个案例,是一家创业公司的登录接口。他们用 JWT 做鉴权,密钥是硬编码的 "secret"。这已经够糟了。更糟的是,他们在验证 token 的时候,把 verify 的第三个参数——也就是算法白名单——直接传了 undefined。攻击者可以把算法改成 none,伪造任意用户。这个漏洞被一个安全研究员发现的时候,公司的用户量刚过五十万。创始人后来在复盘会上说:“我们以为 secret 够用了。” secret 这个词本身,就是最大的讽刺。

漏洞代码有个特点:它往往出现在你最自信的地方。你刚重构完一个模块,觉得逻辑清晰、边界明确,结果漏了一个 else。你刚加完一个功能,测试全绿,上线后才发现并发场景下两个请求会同时读到旧值。你刚写完一段正则,觉得能匹配所有邮箱格式,结果遇到一个带 + 号的地址就崩了。这些都不是什么高深的安全问题,就是普通的、日常的、让人想砸键盘的疏忽。

我现在的做法是:每次提交代码前,强迫自己把新增的 if 和 while 读三遍。第一遍读逻辑,第二遍读边界,第三遍读“如果这里传进来一个我没想过的东西会怎样”。这个方法很笨,但管用。上个月我拦下了一个 for 循环里的 i <= list.size(),那个 = 差点让一个定时任务在凌晨三点把整个数据库的订单状态刷成“已取消”。

漏洞代码不会消失。只要还有人写代码,就会有人写错。区别只在于,你是那个在凌晨三点被电话叫醒的人,还是那个在提交前多读了一遍的人。

—— 写于一个刚修完 null 判断的深夜

```

分享到: