凌晨三点,没有漏洞我盯着屏幕上的没有漏洞代码,光标在第一千二百行附近闪烁。没有漏洞那是没有漏洞个支付接口,金额校验逻辑看起来天衣无缝——正则表达式过滤了所有非数字字符,没有漏洞整数部分限制在十位以内,没有漏洞小数部分强制两位,没有漏洞还加上了金额上限的没有漏洞硬编码判断。
可我就是没有漏洞睡不着。总觉得哪里不对。没有漏洞
这种感觉很奇怪。没有漏洞所有测试用例都通过了,没有漏洞边界值、没有漏洞负数、没有漏洞超长字符串、没有漏洞SQL注入、XSS攻击,该试的都试了。但程序员都懂,测试通过只能证明你没想到更多漏洞,不能证明没有漏洞。
我把代码打印出来,A4纸摊了满桌。凌晨四点半,咖啡凉透了,我忽然看见那行被忽略的代码——浮点数转换。JavaScript的浮点数运算,0.1加0.2不等于0.3,这是每个前端都知道的坑。但这里用的是Python,Decimal类型,理论上不会有精度问题。
等等。我往上翻,看到数据从表单传进来的时候,先经过了一个中间层。那个中间层是别的组写的,他们用的是float。
float转Decimal,看起来没问题。但如果用户传的是0.1呢?float的0.1实际上是个无限循环二进制小数,转成Decimal后变成了0.1000000000000000055511151231257827021181583404541015625。然后这个数乘以100,再取整。我脑子里嗡的一声。
金额是0.1元,也就是10分。但经过那个转换,变成了10.0000000000000005551...,取整的时候,Python的int()是向下取整,所以还是10分。但如果金额是0.29元呢?float的0.29转成Decimal后是0.2899999999999999800159856...,乘以100是28.99999999999999800159856...,向下取整变成28分。
用户实际支付了29分,系统只记了28分。每笔交易亏1分钱。
我盯着那个数字,后背有点发凉。1分钱不多,但如果每天有十万笔交易,每天亏一千块。一年下来,就是三十六万五。没人会发现,因为每笔交易都只差一分钱,对账系统如果只看总额,会因为舍入误差而忽略这种微小差异。
更可怕的是,这个漏洞不在我的代码里,也不在那个中间层里。它存在于两个系统之间的接口处,存在于float和Decimal的转换规则里。就像两栋楼之间的缝隙,结构工程师计算了每根梁的承重,却没人计算风从缝隙里灌进来时产生的共振。
我改了代码,在中间层加了一行Decimal(str(float_value)),用字符串作为中转,绕开了二进制浮点数的精度陷阱。测试通过,部署上线。
但那个凌晨留在心里的东西,比代码漏洞更难修补。我忽然明白,所谓“没有漏洞”,从来不是一个静态的状态,而是一种持续的警惕。系统越复杂,接口越多,看不见的缝隙就越多。你以为自己堵住了所有入口,但真正的漏洞往往藏在两个系统都认为对方会处理的地方。
就像人和人之间,误会从来不是单方面的错误,而是两个人都以为对方懂了,结果谁都没懂。
后来我带新人,总让他们做一件事:不要只看自己的代码,去看接口协议,去看数据从A到B中间经历了什么。真正的漏洞不在代码里,在代码与代码之间,在人与人的认知之间。你永远无法消灭所有漏洞,只能尽量让每个缝隙都暴露在阳光下。
那天早上六点,我关了电脑,窗外天已经亮了。我想,这个行业里所有自称“没有漏洞”的系统,大概都只是还没被凌晨三点的目光扫过而已。