0x01 问题描述:前端封禁了,数据还在吗?
在一次授权安全测试中,我们遇到了一个典型的“前端权限控制”场景。目标是一个使用 Vue.js + Vue Router 构建的管理后台,其管理员页面(/admin)通过路由守卫(beforeEach)进行了保护,仅对 role=admin 的用户可见。
然而,问题来了:如果用户不通过前端路由导航,而是直接调用后端 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 Authorization,OWASP 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_id=current_user_session关键点:即使当前登录的用户角色是普通用户(role=user),只要他处于登录状态(拥有有效的 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 List<Contact> 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 List<Contact> 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_id=current_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 列填入Cookie,Value 列填入session_id=current_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,当前登录用户为普通用户(role=user),其有效 Cookie 为session_id=current_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_all | 200 | 返回完整联系人列表,含手机号、身份证号等敏感字段 | 未授权访问,高危 |
/api/users | 200 | 返回全部用户账号信息,含内部编号与角色字段 | 未授权访问,高危 |
/api/admin | 403 | 返回{"error":"Forbidden"} | 权限校验正常 |
/api/config | 403 | 返回{"error":"Forbidden"} | 权限校验正常 |
/api/orders | 200 | 返回订单列表,含客户姓名与收货地址 | 未授权访问,高危 |
/api/profile | 200 | 返回当前用户自身资料,无敏感越权数据 | 正常(仅返回本人数据) |
/api/export | 302 | 重定向到 /login,但重定向前未校验角色 | 需跟进验证,可能存在绕过空间 |
/api/backup | 403 | 返回{"error":"Forbidden"} | 权限校验正常 |
/api/logs | 200 | 返回系统操作日志,含管理员操作记录 | 未授权访问,高危 |
/api/settings | 403 | 返回{"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_id=current_user_session" "https://target.com${path}") echo "${path} -> ${code}" done判断标准:若上述探测中某个接口返回 200 或 302(而非 401/403),说明该接口对普通用户开放,存在未授权访问风险,应立即检查其后端是否配置了权限中间件。
0x06 总结
这次漏洞复现清晰地揭示了一个在Vue/React等SPA应用中普遍存在的风险:前端路由守卫不等于API安全。
安全是一个整体,不能依赖于单一层面的控制。真正的安全架构必须贯彻“纵深防御”思想,确保从用户界面到数据库的每一层都有相应的保护措施。对于开发者而言,树立“后端接口默认不信任任何请求”的意识,是避免此类未授权访问漏洞的第一步。
参考资料
以下资料与本文讨论的未授权访问漏洞直接相关,可作为进一步阅读与修复参考:
- OWASP API Security Top 10 - 2023:OWASP Top 10 API Security Risks – 2023 - OWASP API Security Top 10。本文复现的未授权访问漏洞对应其中排名第一的 Broken Object Level Authorization(BOLA),该文档提供了完整的风险说明与修复建议。
- Vue Router 导航守卫官方文档:Navigation Guards | Vue Router。本文 0x02 节分析的 beforeEach 路由守卫即来自该文档,它解释了前端守卫只能控制页面渲染、无法替代后端鉴权的边界。
- Spring Security @PreAuthorize 官方文档:Method Security :: Spring Security。本文 0x05 节给出的后端强制鉴权示例基于该文档,它说明了如何通过方法级注解在后端实现角色校验,从而封堵未授权访问。