做了一次安全周审,查出六个跨租户漏洞。六个漏洞的共同点只有一个:系统在前端做了隔离——用户只能看到自己企业的入口、自己的数据、自己的页面——但后端没有做对应的校验。前端把按钮藏了,后端的接口却对所有人敞着门。
其中最典型的一个:一个费用查询接口,请求体里带着租户标识,后端直接拿来用,不做任何校验。也就是说,任何人只要在请求里把租户标识换成别人的,就能看到别家企业的财务数据。前端当然不会让你换——界面锁死了——但绕过前端直接调接口,五分钟的事。
还有两个更隐蔽的:一个推送脚本完全不区分租户,另一个的数据查询没有按租户过滤。日常使用看不出来,因为前端只会发"自己企业"的请求。但一旦有人开始"探索"API,这三道门全是虚掩的。
前端隔离给了一个"看起来安全"的错觉:用户登录后只看到自己的企业、自己的数据、自己的功能入口。产品验收时,一切正常——A 企业看不到 B 企业的任何东西。
但前端的隔离只是"不让你看到入口",不是"不让你访问接口"。前端隐藏按钮,不等于后端关了门。一个稍微懂技术的人,打开浏览器开发者工具,把请求里的租户标识改掉,或者直接用接口测试工具调你的 API——前端那层"隔离"在他面前等于不存在。
这不是 hypothetical(假设性场景)。我们查出的六个漏洞,全是这种模式:前端做了、后端没做。而六个漏洞分布在六个不同的文件里——说明这不是某个开发者的疏忽,是系统性的设计缺陷:整个团队默认"前端管安全"。
多租户系统的安全不能只靠一层,必须做三层。
UI层隔离——用户只能看到自己企业的入口、自己的数据、自己的功能。这是体验层的设计,让用户不至于混乱。但它不是安全措施,是体验措施。
API层鉴权——每个接口被调用时,必须校验请求方的令牌与请求的租户标识是否一致。令牌是 A 企业的,请求的数据也是 A 企业的,放行;令牌是 A 企业的,请求的数据是 B 企业的,直接拒绝。这一层是真正的安全门。
数据层隔离——数据库查询本身必须带租户过滤。即使前两层都失守,攻击者拿到了合法的令牌、绕过了接口校验,到了数据库层,查询仍然只返回该租户的数据。这一层是最后的兜底。
三层的设计逻辑是:任意一层失效时,剩下两层仍然能拦住。UI 层被人绕过了,API 层校验拦住;API 层有漏洞,数据层过滤兜底。只有三层同时失效,数据才会真正泄露——而这个概率,远低于单层失效。
六个漏洞修完后,我们做了一次实测:用一个企业的令牌,请求另一个企业的数据,全部返回 403。十八个测试用例,十八个全过,跨租户请求一个都进不去。
这次修复的关键不在补了六个洞,而在把"安全靠前端"的思维换成了"安全靠后端"——前端隔离是为了用户体验,后端鉴权才是真正的安全边界。以后任何新接口上线,都必须过三层校验:前端做了隔离没有、API 做了令牌绑定没有、数据库做了租户过滤没有。三层缺一层,不上线。
这个原则不只适用于多租户。任何有"角色"概念的系统——管理员和普通用户、内部和外部、付费和免费——都面临同样的问题:前端根据角色显示不同界面,不等于后端根据角色做了不同校验。按钮藏了不等于接口关了,页面不给看不等于数据查不到。
判断你的系统有没有这个问题,方法很简单:假设前端完全不存在——没有任何界面、没有任何按钮——你的后端接口直接暴露在公网上,每个接口自己能不能拦住非法请求?如果答案是"不能",那你的安全就建立在"没人绕过前端"这个假设上。而这个假设,迟早会被打破。
什么是跨租户漏洞? 多租户系统里,A企业的用户通过修改请求参数或直接调接口,访问到B企业的数据。根因是后端没有校验"请求方身份"与"请求数据归属"是否一致。
为什么"前端隔离"不够? 前端隐藏入口只是体验设计,不是安全措施。任何人打开开发者工具修改请求参数,或直接调接口,就能绕过前端。安全边界必须在后端。
三层防御分别是什么? UI层隔离(用户只看到自己企业的入口)、API层鉴权(每个接口校验令牌与租户一致)、数据层隔离(数据库查询带租户过滤)。任意一层失效,剩下两层兜住。
怎么判断自己的系统有没有这个问题? 假设前端完全不存在,后端接口直接暴露在公网上——每个接口自己能不能拦住非法请求?如果答案是不能,安全就建立在"没人绕过前端"这个假设上。