资讯动态

前端JS审计:普通用户如何一路提权到管理员

发布时间:2026/10/18 2:28:04 来源:尧图企业网站定制
前端JS向来是Web安全测试里最容易被忽略、又最容易出东西的一块地方。大部分时候我们盯的是登录框、上传点、SQL注入总觉得后端才是重点可前端那堆被webpack打包出来的JS文件里藏着的往往就是整套系统的“内部地图”。这次授权测试的某证书管理平台就是一个典型例子我们没打任何高危组件纯粹靠抠JS从普通用户一路摸到了管理员权限整个过程像剥洋葱一样。这篇文章就是这次审计的完整复盘分享给做安全测试的同行也建议后端和前端的开发同学看一看——你们眼中的“前端权限控制”到底在后端面前站不站得住脚。先说清楚这个证书平台是干什么的它面向企业员工提供数字证书的申请、签发、续期、撤销等全流程管理内部还划分了普通用户、审核员、管理员三种角色。普通用户只能提交申请、下载自己的证书管理员负责审核、签发、吊销证书还能管理其他用户账号。这权限边界本身就摆在那剩下的问题是谁来执行这个边界。我们花了两个多小时翻前端JS最后找到一条看起来人畜无害的“用户资料更新”接口用它直接把自己的角色改成管理员整站的管理功能瞬间全部解锁。下面拆开讲每一步。1. 为什么前端JS里藏着“管理员通道”1.1 现代Web应用等于“半透明”架构现在的Web应用普遍采用单页应用架构也就是SPA。Vue、React、Angular这三大框架把大量业务逻辑搬到了浏览器端而服务器只负责提供JSON数据接口。这种做法带来了很好的用户体验但也带来一个副作用前端的业务逻辑对用户是完全可见的。你会发现只要打开浏览器开发者工具切到Themes面板所有JS文件都在那里躺着。所谓“后端隐藏逻辑”在这个架构下根本不成立——路由怎么跳、调用哪些接口、传入什么参数、接口返回什么结构全部明明白白。这次审计的证书平台就是标准的Vue全家桶应用登录之后我们随手一看Network面板里就躺着几十个JS chunk。这意味着只要我花时间把这些JS看明白就能还原出整个平台的后端API全貌。1.2 前端鉴权只是“看起来安全”很多系统的权限控制做在了前端而不是后端。典型表现包括菜单根据用户角色动态渲染普通用户看不到管理员菜单Vue Router里有meta: { requiresAdmin: true }之类的路由守卫页面按钮通过v-ifuser.isAdmin控制显示或隐藏接口请求头里统一带上Authorization: Bearer token后端只校验token是否有效听起来挺像回事对吧但问题是这些全部是前端行为做给用户看的不是安全闸门。用户完全可以绕过界面直接用工具构造HTTP请求。很多人有一个思维误区觉得“前端都做了权限控制后端肯定也做了”。审计中我们发现不少系统真的是前端做了、后端没做。前端把菜单、路由、按钮都藏起来了普通用户“不知道怎么进管理员页面”但这不是安全只是增加了攻击门槛而且这个门槛对懂一点HTTP的人来说基本等于没有。所以第一个结论前端JS里藏着什么跟你的系统安不安全有直接关系。所有前端可见的接口都应该假设攻击者会全部枚举出来。2. 信息收集把打包产物变成接口清单2.1 拿到全部JS文件的三种途径审计的前置工作是拿到尽可能完整的JS文件集合。实际操作中我一般用三种方式浏览器开发者工具的Sources面板登录后刷新页面Sources里会按站点域名分类列出所有加载过的JS。优势是直接、无门槛缺点是只能看到当前页面加载的chunk懒加载的路由模块可能要切换页面才会加载。Network面板过滤请求在Network里按JS类型过滤能看到所有脚本请求。这里有个技巧——切换页面、触发不同路由会加载对应的异步chunk所以审计时要把所有业务功能页面都点一遍让全部路由模块暴露出来。爬取Sourcemap某些环境开启了sourcemapJS文件旁边会有一个.map文件。用工具解析sourcemap能把压缩混淆过的代码还原成接近源码的形式。对于大型应用来说这一步的价值极大变量名、注释、文件路径全回来了审计难度直接下降一个等级。这次的项目比较“良心”没有开sourcemap但webpack打包产物保留了非常清晰的chunk注释和模块边界变量名也被压缩过。我们就用最笨的办法把所有JS落地逐个格式化。2.2 快速定位高价值关键词拿到几十个JS文件之后不能一个一个从头读效率太低。我习惯先批量搜索高价值关键词把关键片段挖出来再看上下文。常用关键词包括权限相关admin、role、permission、authority、rbac、isAdmin接口相关/api/、/v1/、axios、baseURL、request、url:敏感操作upload、export、delete、update、password、token配置信息secret、key、appId、clientSecret以admin为例我们在某个chunk里搜到一长串接口路径比如/api/v1/admin/user/list、/api/v1/admin/cert/audit。这说明系统内置了整套管理员接口而且接口路径非常规范带有/admin/前缀。这个前缀给了我们很大的发挥空间——既可以直接访问也可以猜测其他类似接口。2.3 整理API地图的实操方法搜索到接口路径之后我建议立刻建一个API地图表格把收集到的接口按照类型归类。这一步看似繁琐实际是审计的地基后面所有验证都靠它。接口分类示例路径请求方法说明公开接口/api/v1/public/cert/listGET无需登录即可访问普通用户接口/api/v1/user/profileGET获取当前用户资料普通用户接口/api/v1/user/updatePOST更新当前用户资料管理接口/api/v1/admin/user/listGET管理员查看用户列表管理接口/api/v1/admin/cert/auditPOST管理员审核证书申请管理接口/api/v1/admin/cert/issuePOST管理员签发证书这里有一个实用技巧如果JS里出现了/api/v1/admin/user/list这种路径那么很有可能他旁边还有/api/v1/admin/user/add、/api/v1/admin/user/update、/api/v1/admin/user/delete。接口的RESTful风格是很规律的利用这种规律可以补全JS里可能没出现或没被搜索到的接口。整理完API地图整个攻击面就清晰了。接下来要回答一个关键问题这些接口真的只有管理员能调吗3. 从普通用户到管理员一次完整的越权链路3.1 第一个诱人的信号前端路由只有“伪拦截”在JS里定位到管理员接口后我们先跟踪了前端Router是怎么控制页面访问的。代码大致是这样的逻辑router.beforeEach((to, from, next) { if (to.meta.requiresAdmin store.state.user.role ! admin) { next(/unauthorized) } else { next() } })也就是说普通用户访问管理员页面时会被重定向到“未授权”页。这条守卫确实生效了——我们把普通用户账号的token放到浏览器里手动访问/admin/user页面确实被拦了页面也进不去。但请注意这个拦截只发生在“浏览器内部跳转”这一层。真正重要的是管理员页面对应的JS chunk文件其实在普通用户登录状态下也已经加载到了本地。浏览器需要执行这些脚本才能渲染页面所以服务器不可能把代码藏起来。路由守卫拦截的是“页面展示”而不是“接口调用”。这个信号非常重要它说明系统至少有一个共识普通用户不应该看到管理员页面。但接下来我们要验证的是管理员页面对应的后台接口是否也做了拦截。3.2 核心证据管理员接口在后端“裸奔”带着API地图我们直接用普通用户的token请求了一个管理员接口。整个过程用Burp Suite或者任何HTTP客户端都能做本质就是一次带鉴权的GET请求GET /api/v1/admin/user/list HTTP/1.1 Host: cert-admin.example.com Authorization: Bearer eyJhbGciOiJIUzI1NiIs...请求发出去的一瞬间我心里已经预感到要出问题了。返回结果是标准的HTTP 200响应体里是一页用户列表字段包含用户ID、姓名、手机号、邮箱、角色、最后登录时间。普通用户拿管理员接口200这就坐实了第一层越权后端接口完全没有做角色鉴权只校验了token是否有效。它的判断逻辑是“只要是登录用户就能访问”根本没问“这个用户是什么角色”。那种感觉就像进了办公楼门禁只查你有没有工牌不查你是一楼员工还是机房管理员。3.3 顺着开放接口“申请”管理员身份发现管理员列表接口开放之后提权路径并没有就此结束。列表接口泄露的角色字段和用户ID结构让下一步变得更加清晰——要修改自己的角色得找到修改用户资料的接口。在前面整理的API地图里有一个普通用户接口引起了我们的注意/api/v1/user/update。从命名上看这是登录用户修改自己资料的功能大概率会接收一个JSON体。我们用普通用户账号正常调用了一次看看允许修改哪些字段{ userId: 1024, nickname: 测试用户, phone: 13800000000 }响应是成功的nickname和phone都更新了。到这里一个危险的猜想浮现出来后端是不是把整个用户对象直接拿来更新没有做字段白名单试一下把role字段一并传过去{ userId: 1024, nickname: 测试用户, phone: 13800000000, role: admin }响应依旧成功。这时候心跳都快了几拍——赶紧重新登录账号发现菜单栏里出现了“用户管理”“证书审核”“系统设置”几个全新菜单再回头调用任何一个/api/v1/admin/开头的接口全部畅通无阻。这个平台的安全防线到这里彻底被凿穿了。后来的检测报告里我们把这条链路梳理成了两个核心问题垂直越权管理员接口没有做角色鉴权所有登录用户均可访问服务端字段信任用户资料更新接口未做字段白名单role等关键字段可被客户端任意修改3.4 这条链路为什么能走通复盘整条链路它之所以能走通是因为系统设计上存在几个“想当然”想当然一前端做了路由守卫接口就安全了。这是一个典型的误解。路由守卫管的是“浏览器里的页面访问”而攻击者根本不走页面。想当然二接口路径带/admin/普通用户不会知道。只要这个路径出现在前端JS里就等于公开。哪怕它不出现在JS里通过字典爆破也可能猜到。想当然三后端校验了token就是鉴权了。鉴权Authentication和授权Authorization是两回事。校验token只是确认“你是谁”授权才是确认“你能不能做这件事”。这个系统只做到了前者。想当然后来所有字段都可以被客户端提交。后端接口直接使用了客户端传来的JSON体没有与数据库中的已有字段进行区分也没有校验哪些字段是服务端可控的。这类问题在真实环境中相当普遍尤其是一些内部管理系统开发时默认“系统自己人用”把所有信任都给了前端。可一旦这个系统面向员工开放注册漏洞就暴露出来了。4. 如何修复这类能让JS审计“直通管理员”的问题4.1 后端必须建立统一鉴权中间件最核心的修复方案就是后端不能再依赖前端的“自觉”。每个接口在后端都要经过一个统一的鉴权中间件这个中间件负责做两件事校验登录态、校验角色权限。以常见的Java/Spring为例可以基于Spring Security或者Shiro做RBAC也可以用拦截器/过滤器按URL规则进行角色校验Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor()) .addPathPatterns(/api/v1/**) .excludePathPatterns(/api/v1/public/**); }拦截器内部按请求路径匹配角色要求例如/api/v1/admin/**路径要求当前用户必须包含ADMIN角色。这套校验必须独立于前端并且默认拒绝、白名单放行——拿不准的时候宁可返回403也不要默认允许访问。这里还有个细节接口路径带/admin/只是约定真正判断权限的是用户上下文里的角色字段而不是路径。所以哪怕接口不带/admin/前缀只要当前用户角色是ADMIN且满足授权策略就应该放行。关键是要有一个强制的、无法绕过的权限检查层。4.2 接口入参必须做字段白名单对于用户资料更新这类接口必须明确允许更新哪些字段其他一律忽略而不是把客户端传来的JSON体直接映射到数据库实体。后端应该用DTO数据传输对象定义可更新字段比如只允许更新昵称和手机号那么Controller接收的就只有这两个字段public class UserProfileUpdateDTO { private String nickname; private String phone; // 没有userId没有role没有isAdmin }实体类里的role、userId、createdAt等字段永远不应该出现在这个DTO里。更稳妥的做法是在服务层构建Update对象的时候逐字段赋值而不是全量拷贝。这一点在前期开发节省时间的时候经常被牺牲掉图省事直接用BeanUtils.copyProperties把整个Entity拿来接收前端参数这就是给攻击者送了一把万能钥匙。字段越白名单安全边界越清晰。4.3 高权限操作要有“二次验证”角色提升、管理员创建、敏感证书签发这类关键操作光靠登录态远远不够。建议在服务端增加额外的二次确认机制修改角色、创建管理员时校验当前用户的操作权限并强制输入动态口令或短信验证码关键操作走独立的审批流程比如一个管理员创建另一个管理员必须经过更高权限的负责人审批操作日志完整记录操作人、操作时间、目标对象、变更内容方便事后审计比如一个系统里有“审核员”和“管理员”两级角色那么“创建管理员”这个动作就不应该由任何单一账号独立完成而应该触发一条审批流。这样即使某一个普通用户账号被攻破也不会直接拿到管理员权限。4.4 前端侧也要做“安全加固”前端权限控制不能取消但它的定位应该是“体验层”而不是“安全层”。该隐藏的菜单隐藏、该跳转的路由跳转这些继续保留能挡住绝大多数普通用户的无意操作也能减少被社工钓鱼的入口。与此同时前端还有一些必须注意的加固项关闭生产环境sourcemap避免源码被人直接还原不要把任何密钥、签名盐值硬编码在JS里敏感接口的调用参数不要在前端明文透传服务端上下文信息例如当前用户角色不能只由前端决定前端的这些措施无法抵御攻击者直接构造请求但能显著提高攻击成本——安全从来不是一道墙而是一层层叠加的防御。5. 实操心得与常见问题5.1 避坑指南JS审计要配合同步抓包只读JS代码不做动态验证就像拿着一份地图不去实地走很容易纸上谈兵。我在这次审计里吃了不少亏刚开始看到一个接口就觉得是漏洞结果在Burp里实际请求才发现有些接口对参数格式要求很严格或者需要额外的时间戳签名。所以我的经验是JS分析负责“找出有哪些接口”抓包工具负责“验证接口的真实行为”两者缺一不可。排查接口时最好把浏览器开发工具和Burp联动起来在页面上每操作一个功能就去抓包里看对应的请求是怎么发的然后复制成可重复发送的请求再去改参数。这样能最大限度还原应用的正常调用方式避免自己瞎猜参数格式。5.2 常见问题速查表审计过程中比较典型的几个“疑似漏洞实际不是”的坑帮大家提前排掉问题现象实际情况处理方式JS里看到管理员接口但访问返回401后端确实校验登录态只是没校验角色继续尝试用普通用户token访问传role字段进去没变化DTO做了字段白名单尝试其他字段或检查是否有批量更新接口接口返回200但数据为空可能是逻辑删除、数据范围过滤查看返回包结构尝试传不同查询条件SPA路由守卫拦截了页面只拦页面不等于拦接口直接构造接口请求绕过页面接口在JS里搜不到chunk未全量加载或做了拆分触发所有业务路由后再提取一次JS5.3 扩展思路这招还能用在哪些地方从普通用户“JS审计直通管理员”的思路并不局限于证书管理类系统。所有带角色区分的Web应用都可以沿用这条方法论比如企业办公系统、电商平台、工单系统、教育平台等等。核心套路非常固定前端JS还原API地图找管理接口验证后端鉴权再找可控字段提权。如果目标系统没有用户注册入口普通账号都拿不到那么这个思路就得反过来用——先看是否有匿名可访问的公开接口或者能否通过其他方式获得一个低权限账号。很多系统的默认账号、试用账号、第三方对接账号都是突破口。有了低权限账号之后上面的JS审计思路就能完整跑一遍。还有一个值得强调的扩展点前端JS经常包含第三方服务的AK/SK、OSS存储桶的临时密钥、数据库连接串等更高危的敏感信息。这次审计虽然没用到但我之前遇到过一个项目前端JS里直接放了一个云厂商的高权限AK拿到之后能操作对方的整个对象存储。JS审计的价值远不止越权这一处。6. 写在最后的体会这次审计最有意思的其实不是漏洞本身而是它的成因——一个开发团队花了很大力气做前端路由守卫、菜单显隐、按钮权限却唯独忘了在后端接口上落实同一套规则。这种“前端很繁忙后端很诚实”的配置在真实项目里并不少见。我个人的体会是安全检查永远要用“假设对方什么都看得到”的前提来做前端JS、打包产物、接口文档、报错堆栈这些全是面向用户开放的资产。你能从里面拼出多少业务地图就基本决定了你能发现多深的问题。作为开发者的朋友看完这篇文章不用急着去怪安全测试太“狡诈”重点是自己动手验证一下把你项目前端JS里的所有接口挑几个拿普通账号请求一遍看看结果如何。如果发现管理接口确实公开恭喜你今天省下了一次被外部审计挖到高危漏洞的尴尬。修复思路也很明确后端统一鉴权、接口字段白名单、关键操作二次校验把这三点落地这条“普通用户直通管理员”的路就彻底堵死了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑