低代码平台权限绕过漏洞剖析:从路径解析差异到token鉴权缺失 最近圈子里又在传某开源低代码平台的权限绕过漏洞说是“最新版”也没能挡住。我花了两个晚上把整个鉴权链路翻了一遍除了社区里已经讨论的那些路径绕过手法之外还真让我挖到一条不太一样的绕过路径。这条路径不是简单的“拦截器没匹配上”而是出在“token有效但权限校验缺失”这个更隐蔽的位置。这篇东西我会从平台鉴权架构入手把漏洞根因拆开讲清楚再把我发现新绕过路径的完整过程、复现步骤、修复方案和验证方法都摆出来。适合正在做代码审计的安全工程师、用低代码平台搭内部系统的开发以及想搞明白“为什么修了拦截器还是被绕过”的运维同学。1. 先从平台架构和鉴权设计说起1.1 为什么低代码平台最容易出权限绕过低代码平台的核心卖点是快。表单配一配、接口拖一拖一套后台管理就出来了。这种快速交付模式在安全上天然会埋雷最典型的就是把权限模型做得很“薄”。多数这类平台的权限控制思路是这样的前端根据用户角色控制菜单显隐后端在接口层做URL和角色的匹配。听起来没什么问题但实际落地时后端的权限判断往往只做到“登录态校验”这一步也就是说只要你带着一个有效token后端就认为这个请求是合法的至于这个token对应的人到底能不能访问这个接口很多时候根本没查。为什么会出现这种情况因为低代码平台的接口数量太多、迭代太快如果每个接口都精细配置权限开发效率会急剧下降。于是平台方普遍选择“统一拦截器 少量放行名单”的方案拦截器负责校验token放行名单里的路径不用校验token。问题恰恰就出在这个“放行名单”和“统一拦截”之间的空档里。我审计的这个平台下称X-Platform就是典型代表。它是标准的Spring Boot架构鉴权用的是Shiro JWT核心逻辑写在一个全局拦截器里。所有请求进来先过拦截器检查请求头是否带token、token是否有效如果有效就放行到业务接口。看起来链路很完整但打开放行名单之后你会发现事情没那么简单。1.2 目标平台的权限拦截链路先模拟一下X-Platform的鉴权配置。登录接口、图形验证码接口、Swagger接口文档、Druid监控页、Actuator健康检查、静态资源、以及几个“内部使用”的报表导出接口全部被加进了放行名单。关键代码如下Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) .addPathPatterns(/**) .excludePathPatterns( /sys/login, /sys/captcha, /swagger-ui/**, /v2/api-docs, /druid/**, /actuator/**, /static/**, /report/export/**, /inner/token ); }这段配置单看似乎没什么大毛病放行的都是些常规路径。但真正的问题是Shiro从1.x升级到2.x之后默认的路径匹配规则变了而平台的业务代码里还残留着大量老式写法。更麻烦的是新版框架里有两个拦截器在同时工作——Shiro的FilterChain和SpringMVC的Interceptor它们对“同一个请求路径”的解析结果可能完全不一样。这个不一致就是所有绕过手法的温床。2. 漏洞根因从路径规范化到鉴权不一致2.1 同一请求两套解析结果先解释一下什么叫“两套解析结果”。你在浏览器里输入一个URL这个URL不是直接交给业务代码处理的要先经过Web容器比如Tomcat的解析再经过框架的路由匹配最后才能到Controller。问题是容器解析URL的规则和框架匹配路径的规则并不是100%一致的。Tomcat在解析请求路径时有一个比较特殊的机制它会把分号;后面的内容当作“路径参数”剥离开。举个例子请求/sys/user/list;foobar 容器解析后认为实际路径是/sys/user/list为什么容器要这么干因为Servlet规范里允许在路径段上附加参数最常见的场景就是/xxx;jsessionidabc123这种老式会话跟踪写法。Tomcat老老实实地执行了规范把分号后面的部分拆掉然后拿着/sys/user/list去匹配Controller。但框架这边呢Spring的AntPathMatcher在处理/sys/user/list;foobar这个字符串时它会把这个路径当成一个整体去跟/sys/user/list做匹配结果就是不匹配。于是拦截器认为这是一个“没有匹配到受保护路径”的请求直接放行了。而Tomcat已经把真实路径解析出来了请求最终打到了/sys/user/list对应的业务方法上。这就形成了经典的鉴权绕过拦截器看到的是一串“无害”的乱码路径Web层看到的却是真实的敏感接口。2.2 绕过手法分类我在实际测试中整理了一下这类路径解析差异导致的绕过手法大概有这几类手法类型请求路径示例拦截器视角Web层视角杀伤力分号参数/sys/user/list;foo1不匹配放行匹配 /sys/user/list高URL编码/sys/user/%6cist不匹配解码后匹配 /list高大小写变体/Sys/User/List不匹配区分大小写匹配不区分中双斜杠//sys//user/list不匹配归一化后匹配中路径穿越/sys/user/../user/list不匹配规范化后匹配高末尾斜杠/sys/user/list/不匹配匹配中注意不是每种手法在每个版本上都有效因为框架升级会影响匹配规则。但分号和URL编码这两类在我测的X-Platform多个版本上都稳定生效。2.3 为什么拦截器匹配不上而后端能处理这里再往深挖一层。AntPathMatcher是一个纯字符串匹配器它不会去理解URL的语义。它拿到原始请求路径之后就直接跟配置的模式做比对。而容器的路径处理流程里有一步叫做“路径规范化”这一步会做三件事解码百分号编码、移除.和..路径段、剥离分号参数。也就是说容器在“语义层”处理了URL而拦截器在“字符串层”处理URL。这两层之间出现认知偏差就给了攻击者操作空间。所以我在最初审计时一眼就看出了X-Platform的问题它把Shiro的FilterChain配置和SpringMVC的Interceptor混用了而且两边对路径的校验规则还不一样。Shiro那边已经升级了更严格匹配机制但SpringMVC这边还开着老式宽松匹配。一套鉴权体系里同时存在两套标准那么攻击者只需要找到“对不上”的位置就能钻进空档。3. 新绕过路径的发现过程3.1 翻代码时的怀疑点社区里已经公开的漏洞分析大多集中在路径规范化绕过上也就是上文说的那些手法。但我审计的时候发现X-Platform在最新版里做了一个改动他们把旧版/**统一拦截改成了“细化白名单”模式并且新增了一个叫“内部鉴权开关”的配置项。这个配置项的用途官方说法是“为了单元测试和内部服务间调用方便”默认是关闭的。但代码里有一条分支让我非常在意if (authSwitchConfig.isInnerEnabled()) { // 直接从请求头获取内部调用凭证不再走完整鉴权 String innerToken request.getHeader(X-Inner-Token); if (StringUtils.hasText(innerToken) validateInnerToken(innerToken)) { return true; // 直接放行 } }这段代码在/inner/token这个接口的调用链路上看起来是专门给“内部服务”用的捷径。如果配置项被打开那么只要请求头里带上一个内部token就能跳过完整鉴权。这里就出现了一个值得深挖的点获取内部token的接口/inner/token本身就在放行名单里。那我是不是可以用它先换一个合法的内部token然后带着这个token去访问任何敏感接口而平台后端根本不校验这个token的访问范围3.2 绕过链甲接口泄露token乙接口接受另一个来源我顺着这个思路继续追又发现了一个更隐蔽的问题。即便不考虑那个“内部鉴权开关”X-Platform的token校验逻辑也有个大毛病它在解析JWT之后只校验了签名和过期时间完全没有校验这个token的“使用场景”。说得再具体点X-Platform其实存在两种token。一种是用户登录后拿到的登录token权限范围是当前用户的角色权限另一种是从/inner/token获取的内部凭证本来只应该用于调用那几个报表导出接口。但在后端Controller里很多敏感接口比如用户列表、角色配置、字典管理的权限校验注解都只写了RequiresAuthentication意思就是“只要登录了就行”。内部token同样满足“已登录”的条件。于是绕过链就形成了未登录的用户先访问公开的/inner/token接口获取一个合法的内部token。拿着这个内部token去访问/sys/user/list等敏感接口。后端只校验token是否有效、是否过期不校验token的使用范围和角色的接口权限。请求返回200数据“顺利”流出。这一步做完之后我意识到这已经不是单纯的路径绕过了。它是一个“任意token即可访问任意接口”的越权漏洞危害等级比路径绕过要高得多因为攻击者甚至不需要去凑路径变体只需要一个合法凭证。3.3 具体触发流程下面是在本地授权测试环境复现的完整流程。为了不提供可直接打真实系统的完整利用链这里只描述关键触发逻辑接口名和参数都做了脱敏处理。第一步调用公开的获取内部凭证接口POST /inner/token HTTP/1.1 Host: target.local Content-Type: application/json {appId:x-inner-app,timestamp:1720000000000}服务端返回一个JWT格式的token{ code: 200, token: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJpbm5lcl9hcHAiLCJzY29wZSI6ImlubmVyIiw..., expire: 7200 }第二步携带这个token访问敏感接口GET /sys/user/list HTTP/1.1 Host: target.local Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...正常情况下这个请求应该被拒绝因为内部token根本没有访问用户列表的权限。但实际返回的是{ code: 200, data: { total: 128, records: [...] } }也就是说服务端只认了“token有效”这个事实完全没有去核对token的scope和接口的权限要求。拿到这个结果之后我又顺手测了角色配置、部门管理、操作日志等接口全部命中。单这一个点就能把平台内部的组织架构、账号列表、角色权限配置全量拖走。3.4 新绕过与传统绕过的差异为了让读者更好理解这条新路径“新”在哪里我把它和传统路径绕过放在一起对比对比维度传统路径绕过新发现的绕过触发点拦截器与容器路径解析不一致鉴权逻辑缺少token作用域校验利用难度需要构造路径变体部分场景还依赖中间件版本只需调用公开接口换一个合法token所需权限未登录未登录隐蔽性中日志里能看到畸形路径高所有请求看起来都“正常”走完鉴权影响面单个接口或单类接口被绕过所有“只校验登录态”的接口全部暴露修复难度需统一路径解析规则需在鉴权逻辑中补齐权限范围校验对比下来很直观传统绕过本质上是在打“鉴权系统认不出这个请求”的漏洞而新绕过是在打“鉴权系统认出了请求但没判断这个人有没有资格做这件事”的漏洞。很多团队在修复前者时会集中精力去调过滤规则、规范URL编码却忽略了后者——因为后者的代码位置更深藏在业务逻辑里不跟请求进去根本看不到。4. 修复方案与验证4.1 后端修复统一鉴权过滤器先说结论修复这类问题最有效的手段是把鉴权逻辑往前移放到Spring的Filter层完成并且对路径做统一的规范化处理不要在Interceptor和Shiro两边各做一套。推荐的做法是新增一个OncePerRequestFilter在这个过滤器里先做三件事拿到原始请求路径做一次规范化处理。统一处理分号参数、URL编码、路径穿越、大小写归一化。把规范化后的路径传给后续的权限校验逻辑。规范化路径的工具方法示例如下public static String normalizeUri(HttpServletRequest request) { String uri request.getRequestURI(); try { // 1. 解码URL编码 uri URLDecoder.decode(uri, StandardCharsets.UTF_8.name()); // 2. 移除分号及后面的路径参数 uri uri.replaceAll(;.*?(?/|$), ); // 3. 处理路径穿越 Path path Paths.get(uri).normalize(); uri path.toString().replace(\\, /); } catch (Exception e) { // 解析失败直接返回原始uri后续鉴权流程会拒绝 } return uri; }然后在同一个过滤器里统一做三块校验当前请求路径是否在白名单里。token是否存在、签名是否有效、是否过期。token携带的角色/scope是否覆盖当前接口的权限要求。只有三层校验全部通过请求才允许继续。这样就不会再出现“Shiro放行了、Interceptor没拦住”之类的空档因为路径规范化和权限判断都集中在了一个执行链路上。另外那个“内部鉴权开关”必须单独处理。我给出的修复建议是去掉“通过请求头传内部token直接放行”的分支改为必须从注册中心获取调用方身份并且调用方身份必须精确匹配目标接口的allow列表。任何情况下都不能因为开关打开就跳过权限范围校验。4.2 验证方式修复完之后我一般会拿之前所有的绕过样本做回归测试。验证清单大概长这样验证项预期结果实际结果未登录访问 /sys/user/list401通过分号绕过 /sys/user/list;foo1401通过URL编码绕过 /sys/user/%6cist401通过大小写绕过 /Sys/User/List401通过内部token直接访问用户列表403通过内部token访问报表导出接口200通过正常用户访问自己有权限的接口200通过正常用户访问无权限的接口403通过我把这组测试用例做成了一个自动化脚本用的就是常见的网络请求工具跑一遍大概两分钟能直接把所有已知绕过手法全部覆盖住。脚本逻辑并不复杂枚举路径变形、枚举放行名单、检查响应码如果发现预期要返回401/403的请求返回了200就说明有绕过。4.3 代码审计自检清单最后给出一份代码审计自检清单做完这些还不够但至少能把大部分同类漏洞排掉[ ] 全局搜索addInterceptors和FilterRegistrationBean检查所有放行路径。[ ] 对所有放行路径逐一验证是否真的不需要鉴权是否可能泄露敏感数据[ ] 搜索excludePathPatterns、permitAll、antMatchers().permitAll()全部列出并人工复核。[ ] 搜索RequiresAuthentication、RequiresUser等注解检查是否只校验了登录态没有校验具体权限。[ ] 检查JWT解析逻辑确认解析之后是否校验了token的使用场景、角色、scope。[ ] 检查路径解析相关代码确认是否存在分号参数、URL编码、大小写导致的两套解析结果。这份清单是我做Java Web代码审计时固定的起手式每次都能捞出点东西。X-Platform这次的问题就是清单第三项和第五项踩了雷。5. 审计方法论下次怎么快速找到这类漏洞5.1 从配置和放行名单入手很多人在代码审计时会犯一个错误一头扎进业务代码里跟Controller结果看了半天看不出问题。我的习惯是反着来先看配置再看过滤器和拦截器最后才看业务逻辑。为什么先看配置因为配置是最容易留后门的地方。低代码平台的作者往往会在配置里加一些“测试入口”或者“内部接口”初衷是方便自己调试但上了生产环境忘了删就成了最肥的突破口。放行名单里的每一个路径都应该视为潜在攻击面逐个过一遍“如果我不带token访问这个路径会发生什么”。5.2 关注“两套解析器”的差异这是这类漏洞的核心命门。只要发现一套系统里同时存在两套路径匹配逻辑就要立刻警觉。最常见的组合是Shiro/Spring Security的外层Filter加SpringMVC的Interceptor两套规则对URL编码、分号、双斜杠的理解不同就会产生认知偏差。我自己的排查方法是写一个简单的路径变形枚举脚本把Java Web常见的容器路径解析差异都跑一遍看哪些变形能突破拦截器。这个脚本我维护了好几年里面已经积累了三十多种变形方式基本覆盖Tomcat、Undertow、Jetty的常见差异。5.3 关注“内网白名单”和“临时凭证”从这次审计经验来看新式绕过特别喜欢藏在两个地方一个是“内部服务专用接口”另一个是“临时凭证”。它们有一个共同点都是为了图省事在完整的鉴权链路上开了一口“后门井”。审计时遇到这类代码要追问三个问题这个内部token是从哪个接口发出来的那个接口是不是任何人都能访问token的有效期是多长过期了之后是否有刷新机制某个接口校验了token但校验的是“token是谁签发的”还是“token对应的人有没有权限做这件事”这两者差别巨大。我在X-Platform上挖到的新绕过本质上就是在追第三个问题时发现的。代码里写了scope: inner的字段但校验逻辑里根本没有读取这个字段等于写了个装饰。5.4 工具加持手工测当然可以但效率太低。我一般会配合Burp Suite做两件事一是把常用绕过payload做成Intruder字典直接遍历所有关心的接口看哪些路径变体能绕过去。二是用响应对比功能把“正常访问”和“绕过访问”的响应长度、状态码做对比。如果两个响应一样就说明拦截器已经被打穿了。另外有一个小技巧如果平台用了JWT可以把token解出来看它的payload里有什么字段。很多开发会把角色、权限范围写进token里但后端解析出来之后完全没校验这种问题用肉眼一眼就能看出来。最后分享一点实际体会这次审计让我最有感触的一点是修复路径绕过神速但修复权限范围缺失很慢。原因在于路径绕过是“一个点”的问题改改过滤器、规范一下解析逻辑就行而权限范围缺失是“一片”的问题你得把所有标记了RequiresAuthentication的接口全部过一遍逐个补上真实的角色和scope校验。这个工程量在低代码平台上尤其大因为接口太多了。所以在跟开发团队沟通修复方案的时候我的建议是分两步走第一步立刻在全局过滤器里加上路径规范化逻辑把已知的绕过路径先堵死第二步梳理敏感接口清单分批把“只要登录就能访问”的接口改成“必须有指定角色才能访问”。第一步治标能快速止血第二步治本需要排期慢慢推。另外还有一个小建议审计低代码平台时别只盯着框架本身的漏洞公告一定要把平台自研的那层“二次封装”也翻一遍。很多平台为了方便业务方调用会在框架之上再包一层鉴权逻辑我最常遇到的问题就是框架层已经校验过了平台层又校验一次而且两层的校验标准不一样。多一层封装就多一层差异多一层差异就多一个绕过空间。X-Platform这个案例如果后续扩展我大概率会再去翻它暴露出来的接口文档看看还有多少接口是“声明了权限但没真正校验”的状态。低代码平台里这类“纸面权限”远比想象中多而且每个都可能是下一个安全公告的主角。