漏洞library
凌晨三点,漏洞我盯着屏幕上那个跳动的漏洞光标,突然意识到自己成了漏洞的漏洞一部分。不是漏洞被攻击者利用的那种漏洞,而是漏洞制造漏洞的那个人——或者说,是漏洞那个把漏洞写成library的人。
事情要从上个月说起。漏洞我接手了一个老项目,漏洞代码堆得像陈年杂物间。漏洞业务方催着上线新功能,漏洞测试环境跑得欢,漏洞生产环境一压就崩。漏洞我图省事,漏洞把几个常用校验逻辑抽成了公共模块,漏洞取名validation_lib,漏洞往里塞了十几个函数。当时觉得挺美,以后谁要校验邮箱、手机号、身份证,直接调库就行,省得每个接口重复造轮子。
后来才知道,这玩意儿成了全团队的地雷。新来的实习生用is_valid_email,传了个带换行符的字符串,函数返回true。他以为没问题,结果日志里全是奇怪的注入尝试。我翻出源码一看,正则写的是/^[\w\.-]+@[\w\.-]+\.\w+$/,压根没考虑边界字符。这种问题单独看每个函数都不致命,可一旦堆进library里,所有人都会默认它“安全”,没人会再去检查调用方传参。
最讽刺的是,我写这个库的初衷是减少重复代码,结果反而让漏洞变多了。以前每个接口自己写校验,写错了只影响那个接口;现在公共库出问题,所有调它的接口一起炸。就像你把所有房间的灭火器都挪到走廊尽头,看着整齐了,真着火的时候一个都够不着。
上周做安全评审,负责安全的同事指着一处代码说:“这地方有个越权漏洞,但你们library里封装的鉴权函数把用户角色写死在缓存里了,改起来得动全局。”我当场愣住。那个函数是我三个月前加的,当时只想着方便调用,把用户信息从session里取出来塞进redis,设了24小时过期。结果业务方要求某些操作实时校验权限,缓存里的旧角色就导致用户改了权限还能继续操作旧功能。
修这个漏洞花了两天,比当初写这个库的时间还长。更恶心的是,我不能直接删掉缓存逻辑,因为其他十几个接口都依赖它做性能优化。最后只能加一层白名单判断,在关键操作上绕过缓存实时查库。代码丑得我自己都不想看,但没办法。
现在每次有人提“封装一个公共库”,我都下意识想拦一下。不是反对复用,而是大多数所谓的library,根本没经过严谨的边界测试和权限模型设计,纯粹是图省事攒出来的“便利贴”。便利贴贴多了,墙皮迟早会掉。
后来我养成了个习惯:每写一个公共函数,就逼自己写三个恶意用例——带注入的、带超长字符的、带并发竞争的。过不了就重写。同事笑我小题大做,我说你见过凌晨三点因为一个trim()没调而崩掉的支付回调吗?我见过。那个漏洞不在library里,在写library的人脑子里。
漏洞这东西,从来不是某个函数写错了那么简单。它更像一种惯性——你越想省事,它越藏得深。library只是把这种惯性打包分发,让每个人都能踩进同一个坑里,还觉得是自己运气不好。