一个电商平台的普通用户,突然能看见后台订单的漏洞完整列表,包括收货地址、产生手机号、越权因支付流水。漏洞这不是产生黑客入侵,只是越权因他把URL里的`user_id=1001`改成了`user_id=1`。越权漏洞就这么简单——服务器太信任你给它的漏洞身份标识,忘了核对这标识是产生不是属于当前会话。
根子上,越权因是漏洞开发者的思维惯性在作祟。写代码时,产生大脑默认"登录了就是越权因自己人",于是漏洞从请求参数里拿个ID就去查数据库,压根不查这个ID跟当前登录用户有没有关联。产生SQL语句写得再漂亮,索引优化得再完美,这层身份校验没做,全白搭。
我见过最典型的案例,是某企业内部系统。员工登录后能修改自己的工资卡号,接口长这样:POST /api/updateCard,body里塞`{ "userId":123, "cardNumber":"新卡号"}`。后端拿到userId就更新,完全不看session里的实际用户是谁。结果有员工把userId改成老板的,顺手把老板的工资卡换成自己的。这种漏洞不是技术门槛低,是压根没设门槛——连个简单的`if(currentUser.getId() != request.getUserId())`都没写。
另一种常见成因是"功能设计时的粗心"。比如文件下载功能,文件名直接拼在路径里:/download?file=report_2024.pdf。开发者只想着"登录用户都能下载报表",却忘了检查这个报表是不是属于该用户所在部门。横向越权就这么来了——同级的销售能下载市场部的客户名单,只要他猜得到文件名规律。
更隐蔽的是"间接越权"。有些系统在页面层做了按钮隐藏,没权限的用户看不到"删除"按钮。可接口层完全开放,攻击者直接构造`DELETE /api/items/88`的请求,照样能删。前端隐藏≠后端拦截,这个道理老生常谈,但翻车现场永远存在。原因很现实:前端改动快,后端权限模块写起来麻烦,工期紧的时候,产品经理说"先这样吧,反正按钮藏起来了"。
还有个容易被忽视的坑——缓存与CDN。如果响应头没设`Cache-Control: private`,共享缓存服务器可能把A用户的敏感数据存下来,B用户用同一台设备访问时直接命中缓存。这不是传统意义的越权,但效果一样,甚至更糟:受害者连主动请求都没发,数据就泄露了。
说到底,越权漏洞是"信任边界"没划清楚。代码里默认了"请求带了凭证就信任一切参数",可现实是,参数可以被篡改,凭证可以被伪造,只有服务端会话里的身份信息是相对可靠的。偏偏很多系统把身份标识放在请求体里,跟业务数据混在一起——这不是技术难题,是设计时的懒惰。
修复方法其实不复杂:每个涉及资源操作的接口,先取session里的用户ID,再拿这个ID去查权限,最后才允许操作。但为什么漏洞层出不穷?因为补丁要改的代码太多,而业务方总说"这功能上线半年了没人搞事,先忙新需求吧"。直到某天有人手贱改了那个`user_id`,才追悔莫及。