ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

一次Vue路由权限的绕过:从前端“隐藏”到后端“暴露”

一次Vue路由权限的绕过:从前端“隐藏”到后端“暴露” 0x01 问题描述前端封禁了数据还在吗在一次授权安全测试中我们遇到了一个典型的“前端权限控制”场景。目标是一个使用 Vue.js Vue Router 构建的管理后台其管理员页面/admin通过路由守卫beforeEach进行了保护仅对 roleadmin 的用户可见。然而问题来了如果用户不通过前端路由导航而是直接调用后端 API那些被“隐藏”的数据是否依然暴露在外测试目标项目某内部管理系统插件FindSomething用于发现隐藏接口AntiDebugBreaker用于绕过前端调试限制攻击流程图flowchart TD A[前端 JS 分析发现接口] -- B[绕过前端限制] B -- C[直接调用敏感 API 获取数据] C -- D[后端未校验角色返回数据]这条攻击链路的关键点在于前端路由守卫只控制页面渲染并不控制后端数据访问。攻击者只要绕过 UI 层直接向后端接口发起请求就能拿到本应被“隐藏”的数据。整个链路的核心是后端接口未对调用者角色进行二次校验导致前端的一切权限控制形同虚设。0x02 技术原理权限控制的“两张皮”1. Vue Router 的“守卫”逻辑Vue Router 提供了导航守卫如 beforeEach可以在路由跳转前执行检查。常见的权限检查代码如下router.beforeEach((to, from, next) { if (to.meta.requiresAuth !store.state.user.isAdmin) { next(/login); // 非管理员跳转登录页 } else { next(); // 放行 } });这层检查仅发生在前端浏览器中。它控制的是“页面能否被渲染”而非“数据能否被获取”。2. 后端接口的“裸奔”现实许多开发团队存在一个认知误区“前端隐藏了后端就安全了”。实际上前端路由守卫与后端 API 权限校验是两套独立的体系。如果后端接口如 GET /api/contacts_all没有对调用者的身份和权限进行二次验证那么它就对任何知晓其地址的请求者敞开大门。漏洞核心漏洞类型未授权访问Broken Object Level AuthorizationOWASP API Top 1 - 2023。根本原因权限校验仅在前端实施后端缺乏对应的访问控制。攻击路径绕过前端 UI直接访问后端暴露的敏感 API 接口。0x03 复现过程三步拿到“隐藏”数据整个攻击链路可以概括为先从前端打包产物中分析出隐藏接口再绕过前端调试限制最后直接调用敏感 API 获取数据。流程如下图所示flowchart TD A[前端 JS 分析发现接口] -- B[绕过前端限制] B -- C[直接调用敏感 API 获取数据]这条链路的关键在于前端路由守卫只控制页面渲染并不控制后端数据访问。攻击者只要绕过 UI 层直接向后端接口发起请求就能拿到本应被“隐藏”的数据。第一步发现隐藏接口使用 FindSomething 等插件对前端打包后的 JS 文件进行静态分析或通过抓包观察前端页面的网络请求寻找那些未被 UI 直接调用但可能存在的 API 端点如 /api/contacts_all。第二步绕过前端限制由于目标系统可能设置了前端反调试使用 AntiDebugBreaker 插件或手动在 DevTools 中禁用断点、覆盖调试函数确保能正常发起请求。第三步直接调用敏感API使用 Yakit、Burp Suite 或 Postman 等工具构造一个直接指向敏感接口的 HTTP 请求GET /api/contacts_all HTTP/1.1 Host: target.com Cookie: session_idcurrent_user_session关键点即使当前登录的用户角色是普通用户roleuser只要他处于登录状态拥有有效的 session这个请求通常就能成功。因为后端只验证了“是否登录”而没有验证“是否是管理员”。0x04 原因分析为何漏洞会产生职责认知偏差开发团队误以为Vue Router的导航守卫就是完整的权限解决方案忽略了前后端分离架构中后端必须是权限校验的最后一道防线。安全设计缺失在系统设计阶段未建立“资源-角色-权限”的映射模型也未要求对所有数据接口进行强制鉴权。测试覆盖不足安全测试或代码审计通常聚焦于可见功能容易遗漏那些未在UI上直接暴露的后端接口。便捷性压倒安全性为了方便内部开发或调试有时会临时开放某些接口事后却忘记关闭权限验证。0x05 防范建议构建真正的纵深防御1. 后端强制鉴权根本措施下面通过一张对比表直观展示仅做前端路由守卫与采用后端强制鉴权 前端守卫两种方案在关键维度上的差异对比维度修复前仅前端路由守卫修复后后端强制鉴权 前端守卫权限校验位置仅在前端浏览器中校验后端接口不校验调用者身份与角色后端每个敏感接口都校验身份与角色前端守卫仅作为辅助体验层未授权访问风险高攻击者绕过 UI 直接请求接口即可越权获取数据低即使绕过前端后端仍会拦截并返回 403数据泄露可能性高敏感数据可被任意登录用户直接拉取低非管理员无法获取敏感数据响应中敏感字段还会脱敏合规性不满足未授权访问属于 OWASP API Top 1 高危漏洞难以通过安全审计满足符合纵深防御与最小权限原则可顺利通过安全审计与合规检查原则永远不要信任前端。在后端为每一个业务接口尤其是数据查询、修改、删除接口添加权限校验。实现在 API 网关或每个 Controller 方法开头检查当前用户的角色/权限是否与接口要求的权限匹配。代码示例Node.js Express// 中间件检查管理员权限 const requireAdmin (req, res, next) { if (req.user req.user.role admin) { next(); } else { res.status(403).json({ error: Forbidden }); } }; // 应用中间件到敏感路由 app.get(/api/contacts_all, requireAdmin, (req, res) { // 返回数据 });代码示例Java Spring Boot Spring Security// 方式一使用 PreAuthorize 注解推荐声明式鉴权 RestController RequestMapping(/api) public class ContactController { // 仅 ADMIN 角色可访问否则返回 403 PreAuthorize(hasRole(ADMIN)) GetMapping(/contacts_all) public ListContact getAllContacts() { return contactService.findAll(); } } // 方式二自定义 HandlerInterceptor命令式鉴权适合统一拦截 Component public class AdminAuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 从 SecurityContext 中获取当前登录用户 Authentication auth SecurityContextHolder.getContext().getAuthentication(); boolean isAdmin auth ! null auth.getAuthorities().stream() .anyMatch(g - g.getAuthority().equals(ROLE_ADMIN)); if (!isAdmin) { response.setStatus(HttpServletResponse.SC_FORBIDDEN); return false; // 拦截请求返回 403 } return true; // 放行 } } // 注册拦截器并应用到敏感路径 Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AdminAuthInterceptor()) .addPathPatterns(/api/contacts_all, /api/admin/**); } }关键配置说明启用方法级安全在启动类或配置类上添加EnableMethodSecurity注解方式才会生效。角色前缀Spring Security 默认要求角色以ROLE_开头hasRole(ADMIN)实际匹配ROLE_ADMIN。拦截器顺序HandlerInterceptor 在 Controller 方法执行前运行适合做统一入口校验注解方式更贴近具体接口二者可结合使用。修复前漏洞示例Node.js Express未加权限校验// 漏洞点路由未挂载 requireAdmin 中间件任何登录用户都可访问 app.get(/api/contacts_all, (req, res) { // 直接返回全部联系人数据未校验调用者角色 res.json(contactService.findAll()); });修复前漏洞示例Java Spring Boot未加权限校验// 漏洞点Controller 方法未加 PreAuthorize也未配置拦截器 RestController RequestMapping(/api) public class ContactController { // 任何登录用户都能调用未校验 ADMIN 角色 GetMapping(/contacts_all) public ListContact getAllContacts() { return contactService.findAll(); } }对比说明修复前后端接口对调用者身份与角色不做任何校验攻击者只要绕过前端 UI 直接请求即可越权获取数据修复后通过requireAdmin中间件或PreAuthorize(hasRole(ADMIN))注解后端在返回数据前强制校验角色非管理员一律返回 403。2. API接口模糊化与最小化暴露接口隐藏避免使用 /api/contacts_all 这样意图明显的接口命名。使用无意义的 UUID 或通过前端动态生成的 token 作为接口路径的一部分。暴露最小化严格遵循业务需要设计API。管理员查看全部联系人的需求应通过调用多个已授权的细粒度接口如分页查询来组合实现而非提供一个“一键获取所有”的超级接口。3. 强化前端守卫辅助措施冗余检查尽管不能作为唯一防线但仍需在前端进行权限检查以提供良好的用户体验和初级防护。数据脱敏即便非管理员用户通过某种方式请求到了数据前端组件在渲染前应对敏感字段如手机号、身份证号进行脱敏处理。4. 安全测试左移安全测试左移将安全测试前置到开发与集成阶段尽早发现并修复此类未授权访问漏洞避免上线后被动补救。具体可从以下三个方向落地代码审计在代码 Review 阶段重点关注所有后端路由的定义处确保每个路由都配置了正确的权限中间件。自动化扫描在 CI/CD 管道中引入自动化 API 安全测试工具对全量接口包括未在前端使用的进行未授权访问测试。渗透测试定期进行黑盒/灰盒测试模拟攻击者视角尝试绕过前端直接探测和攻击后端 API。5. 修复验证清单完成上述修复后建议按以下清单逐项验证确保未授权访问漏洞已被彻底封堵使用普通用户 session 直接请求 /api/contacts_all应返回 403 状态码。检查所有后端路由是否都配置了权限中间件确保没有遗漏任何敏感接口。扫描前端 JS 产物确认敏感接口路径已模糊化不再出现 /api/contacts_all 这类直白命名。验证非管理员用户通过 UI 无法访问 /admin 页面路由守卫仍正常生效。确认敏感字段如手机号、身份证号在非管理员用户的响应中已脱敏处理。代码审计在代码Review阶段重点关注所有后端路由的定义处确保每个路由都配置了正确的权限中间件。自动化扫描在CI/CD管道中引入自动化API安全测试工具对全量接口包括未在前端使用的进行未授权访问测试。渗透测试定期进行黑盒/灰盒测试模拟攻击者视角尝试绕过前端直接探测和攻击后端API。6. 开发者排查快速定位未授权访问接口除了按清单逐项验证开发者还可以借助浏览器 DevTools 与 Yakit 主动排查项目中所有可能未授权访问的 API 接口。下面给出两条可落地的排查路径。路径一通过 DevTools Network 面板定位接口打开浏览器 DevTools 的 Network 面板勾选 Fetch/XHR 过滤条件随后以普通用户身份在系统中逐页操作观察所有发出的请求。重点筛选返回 200 且响应体包含敏感字段如手机号、身份证号、内部编号的接口这些接口很可能缺少后端权限校验。判断标准若某个接口在普通用户 session 下返回 200 且携带敏感数据而该接口本应仅管理员可访问即可判定为未授权访问。可复制该请求为 cURL 命令再用普通用户 session 单独重放验证。路径二通过 Yakit 端口扫描与接口探测在 Yakit 中新建端口扫描任务对目标服务的常见端口如 80、443、8080、8443进行探测确认后端服务实际暴露的端口范围。随后使用 Yakit 的 Web Fuzzer 或 MITM 抓包模块对扫描到的端口发起接口枚举请求批量探测 /api/ 下的常见路径。Yakit Web Fuzzer 具体操作步骤第一步配置请求模板。在 Yakit 中打开 Web Fuzzer 模块新建一个请求将请求方法设为 GET目标地址填写为https://target.com{{path}}其中{{path}}是用于批量枚举的变量占位符。在请求头区域添加Cookie: session_idcurrent_user_session确保以普通用户身份发起探测。若目标服务运行在非 443 端口可在地址中显式指定端口例如https://target.com:8080{{path}}。第二步设置批量枚举字典。在 Web Fuzzer 的「字典」或「Payload」配置中点击「导入」按钮选择本地字典文件每行一个路径例如/api/contacts_all、/api/users、/api/admin、/api/config、/api/orders等。Yakit 会自动将字典中的每个值替换到{{path}}占位符并逐个发起请求。字典文件建议使用纯文本格式.txt每行一个路径避免包含多余空格或空行以免影响枚举结果。第三步根据返回状态码快速判定。发起批量请求后在结果列表中重点观察每个接口的 HTTP 状态码若某个接口返回200说明该接口对普通用户开放存在未授权访问风险应立即标记并检查其后端是否配置了权限中间件若返回403说明后端已正确拦截该接口权限校验正常。此外返回302重定向的接口也需留意可能存在绕过空间——例如重定向到登录页但未真正校验权限或重定向后仍可访问敏感数据应进一步跟进验证。Yakit Web Fuzzer 界面操作详解在 Yakit 的 Web Fuzzer 界面中整体布局分为左侧的「请求编辑区」和右侧的「响应结果区」。请求编辑区顶部是请求方法下拉框与目标地址输入框下方是请求头、请求体等 Tab 页签响应结果区则按「单次响应」与「批量结果」两种视图展示。下面结合界面元素说明如何完成配置、导入与结果筛选。第一步配置请求模板。在请求编辑区顶部将请求方法下拉框切换为GET在目标地址输入框中填写https://target.com{{path}}其中{{path}}是 Yakit 识别的高亮变量占位符输入后会自动变色表示已生效。随后切换到「请求头」Tab点击「添加」按钮新增一行在 Key 列填入CookieValue 列填入session_idcurrent_user_session确保以普通用户身份发起探测。若目标服务运行在非 443 端口可在地址中显式指定端口例如https://target.com:8080{{path}}。第二步导入字典文件。在请求编辑区下方找到「字典」或「Payload」配置面板点击「导入」按钮在弹出的文件选择框中选中本地字典文件.txt 格式每行一个路径例如/api/contacts_all、/api/users、/api/admin等。导入后Yakit 会在面板中列出字典条目总数并自动将每个值替换到{{path}}占位符。确认字典内容无误后点击「开始」或「发送」按钮发起批量请求Yakit 会按字典顺序逐个请求并实时刷新结果。第三步查看批量请求结果列表并快速筛选高危接口。批量请求完成后切换到「批量结果」视图Yakit 会以表格形式逐行展示每个接口的请求结果主要列包括「序号」「目标地址」「HTTP 状态码」「响应长度」「耗时」等。Yakit 会根据状态码对结果行进行颜色标识返回200的结果行通常以绿色高亮返回403的结果行以橙色高亮返回302的结果行以黄色高亮返回404的结果行以灰色高亮。开发者只需在结果列表顶部点击「状态码」列标题按状态码排序或使用筛选框输入200即可快速过滤出所有返回 200 的接口——这些绿色高亮行即为最可能存在未授权访问风险的高危接口应逐条点击查看响应体确认是否包含手机号、身份证号等敏感字段并立即标记跟进。实战案例批量枚举 10 个常见 /api/ 路径下面以一个真实场景为例演示如何用普通用户 session 批量枚举 /api/ 下的 10 个常见路径。假设目标为https://target.com当前登录用户为普通用户roleuser其有效 Cookie 为session_idcurrent_user_session。在 Web Fuzzer 中配置请求模板https://target.com{{path}}并导入包含以下 10 个路径的字典文件/api/contacts_all /api/users /api/admin /api/config /api/orders /api/profile /api/export /api/backup /api/logs /api/settings发起批量请求后Yakit 结果列表会按字典顺序逐个展示每个接口的响应。下面给出典型的返回结果对比接口路径HTTP 状态码响应体特征判定结论/api/contacts_all200返回完整联系人列表含手机号、身份证号等敏感字段未授权访问高危/api/users200返回全部用户账号信息含内部编号与角色字段未授权访问高危/api/admin403返回{error:Forbidden}权限校验正常/api/config403返回{error:Forbidden}权限校验正常/api/orders200返回订单列表含客户姓名与收货地址未授权访问高危/api/profile200返回当前用户自身资料无敏感越权数据正常仅返回本人数据/api/export302重定向到 /login但重定向前未校验角色需跟进验证可能存在绕过空间/api/backup403返回{error:Forbidden}权限校验正常/api/logs200返回系统操作日志含管理员操作记录未授权访问高危/api/settings403返回{error:Forbidden}权限校验正常基于状态码的完整判断逻辑返回 200 且响应体包含敏感数据说明该接口对普通用户开放且未做角色校验属于未授权访问漏洞应立即标记并检查后端是否配置了权限中间件。若接口返回 200 但仅包含当前用户自身数据如 /api/profile则属于正常情况需结合响应体内容进一步甄别。返回 403说明后端已正确拦截非管理员请求该接口权限校验正常无需处理。返回 302 重定向需重点跟进。若重定向到登录页但未真正校验权限或重定向后仍可通过手动构造请求访问敏感数据则仍存在绕过风险应手动重放验证。返回 401说明接口要求身份认证但当前 session 无效需先确认 Cookie 是否正确携带。返回 404说明该路径不存在或已被移除可忽略。综合以上判定本次枚举共发现/api/contacts_all、/api/users、/api/orders、/api/logs四个接口存在未授权访问风险/api/export需进一步跟进验证。针对这些接口应按照 0x05 节「1. 后端强制鉴权」给出的方案为每个敏感接口补充角色校验中间件或注解修复后再次枚举确认全部返回 403。具体排查命令可参考# 使用 Yakit 端口扫描模块探测目标端口 yakit port-scan --host target.com --ports 80,443,8080,8443 使用 curl 以普通用户 session 批量探测常见 API 路径 for path in /api/contacts_all /api/users /api/admin /api/config; do code$(curl -s -o /dev/null -w %{http_code} -H Cookie: session_idcurrent_user_session https://target.com${path}) echo ${path} - ${code} done判断标准若上述探测中某个接口返回 200 或 302而非 401/403说明该接口对普通用户开放存在未授权访问风险应立即检查其后端是否配置了权限中间件。0x06 总结这次漏洞复现清晰地揭示了一个在Vue/React等SPA应用中普遍存在的风险前端路由守卫不等于API安全。安全是一个整体不能依赖于单一层面的控制。真正的安全架构必须贯彻“纵深防御”思想确保从用户界面到数据库的每一层都有相应的保护措施。对于开发者而言树立“后端接口默认不信任任何请求”的意识是避免此类未授权访问漏洞的第一步。参考资料以下资料与本文讨论的未授权访问漏洞直接相关可作为进一步阅读与修复参考OWASP API Security Top 10 - 2023OWASP Top 10 API Security Risks – 2023 - OWASP API Security Top 10。本文复现的未授权访问漏洞对应其中排名第一的 Broken Object Level AuthorizationBOLA该文档提供了完整的风险说明与修复建议。Vue Router 导航守卫官方文档Navigation Guards | Vue Router。本文 0x02 节分析的 beforeEach 路由守卫即来自该文档它解释了前端守卫只能控制页面渲染、无法替代后端鉴权的边界。Spring Security PreAuthorize 官方文档Method Security :: Spring Security。本文 0x05 节给出的后端强制鉴权示例基于该文档它说明了如何通过方法级注解在后端实现角色校验从而封堵未授权访问。
返回列表