以下文章来源于渗透测试之道 ,作者kk1230
一、什么是VUE
Vue.js 是一个渐进式 JavaScript 框架,用于构建用户界面。而 Webpack 是一个模块打包工具,用于将项目中的各种资源(如 JavaScript 模块、CSS 样式文件、图片等)打包成浏览器可以识别的文件。
Webpack概念机制Webpack 主要用于将项目中的各种资源(如 JavaScript 模块、CSS 样式文件、图片、字体等)视为模块,通过加载器(loader)和插件(plugin)对这些模块进行处理,最终将它们打包成一个或多个浏览器可以识别的文件(通常是 bundle.js 文件)。例如,在一个大型的前端项目中,可能会有多个 JavaScript 文件,每个文件负责不同的功能模块。Webpack 可以将这些文件按照依赖关系进行分析,然后合并成一个文件,这样浏览器在加载时就只需要加载一个文件,减少了 HTTP 请求的次数,从而提高了页面的加载速度。
二、 手工测试
2.1 框架特征识别
✅浏览器插件Wappalyzer:很容易看出这是一个Webpack打包的系统
✅专业开发者插件Vue.js devtools:亮起来就是vue框架,有时候需要强制开启
✅查看JS:大量这种chunk结构的打包文件,有些js不会自动加载,需要拼接chunk才可以访问,往往这类js会存在大量接口和敏感信息
2.2 获取接口和路由
✅浏览器插件
FindSomething、superSearchPlus
快速信息搜集,看一眼就行
✅BurpSuit插件
JsRouteScan、HAE、BurpJSLinkFinder等等,看一眼就行
✅swagger等接口文档
Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务,JAVA在金融机构开发语言的地位一直居高不下,而作为JAVA服务端的大一统框架Spring,便将Swagger规范纳入自身的标准,建立了Spring-swagger项目,所以在实际测试环境中,基于spring框架的swagger-ui接口展示及调试文档页面最为常见。
【常见的路径】
/v2/api-docs
/swagger-ui.html
/swagger
/api/swagger
/Swagger/ui/index
/api/swaggerui
/swagger/ui
/api/swagger/ui
/api/swagger-ui.html
/user/swagger-ui.html
/swagger/index.html
/api.html
/sw/swagger-ui.html
/api/swagger-ui.html
/swagger/v1/swagger.json
/swagger/v2/swagger.json
/api-docs
/api/doc
/docs/
/doc.html
/v1/api-docs
/v3/api-docs
遇到最多的应该是这种非图形化的API接口文档
下一个浏览器插件Swagger-UI
填入API文档的地址即可进行图形化测试
✅Druid Monitor、Spring Boot Actuator等未授权漏洞
重点关注/druid/weburi.html,在URI监控中存在大量接口路径
其他端点不说了,针对接口和路由的泄露重点关注/actuator/mappings,用于列出应用程序中的所有 URL 映射@RequestMapping及其对应的处理方法。
✅浏览器调试
“使用最原始的方式获取的路由往往是最全面的”
开启Vue.js devtools进行调试,先看下此时是没有vue的选项卡的
Vue Devtools 是 Vue 官方发布的调试浏览器插件,可以安装在 Chrome 和 Firefox 等浏览器上,直接内嵌在开发者工具中,使用体验流畅。Vue Devtools 由 Vue.js 核心团队成员 Guillaume Chau 和 Evan You 开发。
如果vue的图标还没有亮起来 可以在控制台输入下面代码强制开启
var Vue, walker, node;
walker = document.createTreeWalker(document.body,1);
while ((node = walker.nextNode())) {if (node.__vue__) {Vue = node.__vue__.$options._base;if (!Vue.config.devtools) {Vue.config.devtools = true;if (window.__VUE_DEVTOOLS_GLOBAL_HOOK__) {window.__VUE_DEVTOOLS_GLOBAL_HOOK__.emit("init", Vue);console.log("==> vue devtools now is enabled");}}break;}
}
vue3则输入该代码
const el = document.querySelector('#app')
const vm = el.__vue_app__window.__VUE_DEVTOOLS_GLOBAL_HOOK__.apps.push({app: vm,version: vm.version,types: {Comment: Symbol("Comment"),Fragment: Symbol("Fragment"),Static: Symbol("Static"),Text: Symbol("Text"),},
})
if (window.__VUE_DEVTOOLS_GLOBAL_HOOK__) {window.__VUE_DEVTOOLS_GLOBAL_HOOK__.emit("init", vm);console.log("==> vue devtools now is enabled");
}
因为Vue devtools
限制只允许在本地环境开启调试模式,这里绕过的方法还有很多
【方法一】:强烈推荐使用谷歌插件Vue force dev+Vue.js devtools组合
【方法二】:控制台依次输入下面的几行代码,进行绕过
# 获取 Vue Devtools 对应的全局变量(第一行代码)const devtools = window.__VUE_DEVTOOLS_GLOBAL_HOOK__
# 获取项目中 Vue 实例,需要根据具体情况进行调整(第二行代码)const Vue = $('#app').__vue__.__proto__.__proto__.constructor
# 在生产环境中启用 vue-devtools(第三行代码)
Vue.config.devtools = true
# 开启vue-devtools的关键(第四行代码)
devtools.emit('init', Vue)
【方法三】:修改插件源码
插件地址
https://github.com/vuejs/devtools
打开main.js文件,在最末尾的地方添加下方这一行代码。
Vue.config.devtools = true
最终在生产环境成功开启了Vue devtools的调试模式
下面是各功能的说明
- Components(组件):
Vue 组件是封装可复用 UI 的独立模块,如同积木块般组合构建应用。每个组件包含自包含的模板、逻辑与样式,支持嵌套形成层级结构,实现复杂界面的分治开发,同时保持作用域隔离,避免代码冲突。
- Props(属性):
Props 是父组件向子组件传递数据的单向通道,遵循 “自上而下” 的数据流。它通过属性绑定传递值,支持类型校验和默认值设置,强制子组件不可直接修改,需通过事件通知父级变更,确保数据流动可追踪。
- Data(数据):
Data 是组件私有的响应式状态仓库,以函数形式返回初始数据。其内部变量变化会触发视图自动更新,但新增属性需用特定 API(如$set)保持响应性,用于管理组件内部动态状态,与 Props 形成 “外传内管” 的数据分工。
也可以使用下面大佬的脚本快速获取网站路由(仅对部分网站生效)
function findVueRoot(root) {const queue = [root];while (queue.length > 0) {const currentNode = queue.shift();if (currentNode.__vue__ || currentNode.__vue_app__ || currentNode._vnode) {console.log("vue detected on root element:", currentNode);return currentNode}for (let i = 0; i < currentNode.childNodes.length; i++) {queue.push(currentNode.childNodes[i]);}}return null;
}function findVueRouter(vueRoot) {let router;try {if (vueRoot.__vue_app__) {router = vueRoot.__vue_app__.config.globalProperties.$router.options.routesconsole.log("find router in Vue object", vueRoot.__vue_app__)} else if (vueRoot.__vue__) {router = vueRoot.__vue__.$root.$options.router.options.routesconsole.log("find router in Vue object", vueRoot.__vue__)}} catch (e) {}try {if (vueRoot.__vue__ && !router) {router = vueRoot.__vue__._router.options.routesconsole.log("find router in Vue object", vueRoot.__vue__)}} catch (e) {}return router
}function walkRouter(rootNode, callback) {const stack = [{node: rootNode, path: ''}];while (stack.length) {const { node, path} = stack.pop();if (node && typeof node === 'object') {if (Array.isArray(node)) {for (const key in node) {stack.push({node: node[key], path: mergePath(path, node[key].path)})}} else if (node.hasOwnProperty("children")) {stack.push({node: node.children, path: path});}}callback(path, node);}
}function mergePath(parent, path) {if (path.indexOf(parent) === 0) {return path}return (parent ? parent + '/' : '') + path
}function main() {const vueRoot = findVueRoot(document.body);if (!vueRoot) {console.error("This website is not developed by Vue")return}let vueVersion;if (vueRoot.__vue__) {vueVersion = vueRoot.__vue__.$options._base.version;} else {vueVersion = vueRoot.__vue_app__.version;}console.log("Vue version is ", vueVersion)const routers = [];const vueRouter = findVueRouter(vueRoot)if (!vueRouter) {console.error("No Vue-Router detected")return}console.log(vueRouter)walkRouter(vueRouter, function (path, node) {if (node.path) {routers.push({name: node.name, path})}})return routers
}console.table(main())
做个了浏览器小插件适配了该脚本
插件🔗:
https://github.com/kk12-30/vue-router
总结
1、 Vue应用信息提取方法
定位根节点DOM
:Vue应用的根节点通常是,可通过以下方式获取:
const rootElement = document.getElementById('app'); // 或其他自定义根元素ID
获取Vue实例
Vue2:通过__vue__属性直接访问实例
const vueInstance = rootElement.__vue__;
Vue3:通过__vue_app__属性访问应用上下文
const vueApp = rootElement.__vue_app__;
提取路由信息
若使用Vue Router,可通过实例的$router.options.routes获取路由配置:
// Vue2
const routes = rootElement.__vue__.$router.options.routes;
// Vue3
const routes = rootElement.__vue_app__.config.globalProperties.$router.options.routes;
// 输出路由列表
console.log(routes.map(route => route.path));
2、React应用的类似方法
- React不会直接将实例附加到DOM,但可通过开发者工具或内部属性访问:
React Developer Tools:通过浏览器插件查看组件树、状态及路由。
手动访问内部实例(不稳定,依赖版本):
// 查找React根元素
const rootElement = document.getElementById('root');
// 访问React Fiber内部实例(示例)
const reactKey = Object.keys(rootElement).find(key => key.startsWith('__reactFiber$'));
const reactInstance = rootElement[reactKey];
// 递归遍历组件树提取路由信息(需结合实际路由库如React Router)
- 通过路由库暴露的API:
如React Router的window.REACT_ROUTER_HISTORY(若未隐藏)。
3、绕过Vue生产环境Devtools限制
方法一
:强制启用Devtools在控制台中手动注入配置(仅对未压缩代码有效):
// Vue2
Vue.config.devtools = true;
// Vue3
app.config.devtools = true;
方法二
:Hook Vue实例通过劫持Vue构造函数或实例方法重新启用Devtools:
// Vue2示例
const originalVue = window.Vue;
window.Vue = function (options) {options.devtools = true;return new originalVue(options);
};
限制
:生产环境代码可能被压缩、混淆或禁用配置覆盖,成功率取决于构建配置。
【手工fuzz】:
获取接口和路由的目的无非是为了测试未授权接口、寻找进入系统的入口
例如拼接/register、/reg你进可以进入注册页面,有些路由可能存在没做鉴权或泄露敏感信息等等
有时候路由多就得一个一个手动拼接上去,再观察burpsuit中该页面返回了哪个接口,一旦数据包多起来就会眼花缭乱了。。
还有一种大家都喜欢简单粗暴的方式,就是将FindSomething插件中所有接口复制,直接在burpsuit中爆破,正所谓大力出奇迹~
Tip
:实战中往往需要多种工具相互配合才能获取较多且全面的接口和路由
2.3 获取敏感信息
✅浏览器插件
FindSomething等等,快速信息搜集,看一眼就行
✅BurpSuit插件
HAE、APIFinder、Unexpected_information等等,看一眼就行
✅F12大法
通过搜索敏感词:password、accesskey、secret等等
这里给出一个搜索敏感信息的正则
(?:(\b[1-9]\d{5}(?:19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dXx]\b)|(\b(?:AKIA|LTAI)[A-Z0-9]{16,}\b)|(eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.(?:[a-zA-Z0-9_-]+))|((password|passwd|api[_-]?key|secret|token|auth|credentials|private[_-]?key|access[_-]?key|database|pwd)\s*[:=]\s*(["'`])(?:(?!\4).)*\4))
✅Druid Monitor、Spring Boot Actuator等未授权漏洞
重点关注/druid/websession.html,可获取历史的session值,进行session替换登录系统
其他端点不说了,针对敏感泄露重点关注
/actuator/env、/actuator/heapdump、/actuator/httptrace
/env端点往往存在很多配置的密码等信息,但很多时候密码都会带星星,除了用 heapdump_tool、JDumpSpider工具对heapdump进行分析,还可以使用java自带的软件jvisualvm或MemoryAnalyzer进行分析(Tip
:手工分析可获取更多信息)
jvisualvm用法如下
# 获取星星中的信息
select s from java.lang.String s where /查询字段/.test(s.value.toString())# 或者使用(这个查询会返回所有以 password 为子字符串的键的值)
select s.value.toString() from java.util.Hashtable$Entry s where /password/.test(s.key.toString())# 这里查询字段为spring.datasource.password
select s from java.lang.String s where /spring.datasource.password/.test(s.value.toString())
查询后点击进入实例
在实例数这里就可以查看带星星的明文信息了
如果存在remeberMe字段,可以过滤下面的类
org.apache.shiro.web.mgt.CookieRememberMeManager
使用下面脚本转换(将x替换为decryptionCipherKey值)
import base64
import struct
data=struct.pack('<bbbbbbbbbbbbbbbb', x, x, x, x, x, x, x....)
print(base64.b64encode(data))
MemoryAnalyzer必须要 JDK 11+ 才能成功启动
配置文件MemoryAnalyzer.ini 添加下面参数即可
-vm
D:\java\java17\bin\javaw.exe
# 搜索明文密码
select * from java.util.Hashtable$Entry x WHERE (toString(x.key).contains("password"))
select * from java.util.LinkedHashMap$Entry x WHERE (toString(x.key).contains("password"))
SELECT s FROM java.lang.String s WHERE (s.toString() LIKE ".*password.*")# 关键字
select * from byte[] s where toString(s) like ".*password.*"
select * from char[] s where toString(s) like ".*password.*"
select * from java.lang.String s where toString(s) like ".*password.*"
# 搜索JWT
select * from java.lang.String s where s.toString().startsWith("eyJ")
SELECT * FROM byte[] s WHERE s.toString().contains("eyJ")
select * from java.lang.String s where s.toString().contains("jwt")
select * from java.util.LinkedHashMap$Entry x WHERE (toString(x.key).contains("Jwt"))# 如果jwt设置在环境变量中
select * from org.springframework.web.context.support.StandardServletEnvironment
spring boot 1.x
select * from java.util.Hashtable$Entry x WHERE (toString(x.key).contains("jwt"))
spring boot 2.x
select * from java.util.LinkedHashMap$Entry x WHERE (toString(x.key).contains("jwt"))# 搜索session
select * from java.lang.String s where toString(s) like ".*SESSION.*"
select * from byte[] s where toString(s) like ".*SESSION.*"
select * from char[] s where toString(s) like ".*SESSION.*"# 搜索GET/POST数据包
select * from java.lang.String s where toString(s) like ".*(GET|POST) /.*"
#搜索POST类型含有login字符
select * from byte[] s where toString(s) like ".*login.*" and toString(s) like ".*POST.*"#搜索shirokey
select * from org.apache.shiro.web.mgt.CookieRememberMeManager#搜索物理路径
SELECT file.path.value.toString() FROM java.io.File file #根据长度进行搜索
SELECT * FROM java.lang.String s WHERE (s.toString().length() > 100)
三、自动化测试
3.1 优秀工具推荐
3.2 实现自动化测试
第一步:获取JS
1、快速定位 Webpack 打包文件
(1)使用浏览器开发者工具
打开 Sources→ 筛选文件名包含 bundle、chunk、main 或哈希字符串(例如:app.1a2b3c.js)。
在 Network 标签中筛选 JS 类型文件,观察加载顺序(入口文件通常最先加载)。
(2)识别 Webpack 特征
Webpack 打包的代码通常包含以下标识:
// 常见特征
(window.webpackJsonp = window.webpackJsonp || []).push(...)
// 或
function __webpack_require__(moduleId) { ... }
2、提取 Webpack 模块的 3 种方法
方法 1
:直接格式化代码
在 Sources 中打开目标 JS 文件,点击底部 {} 按钮格式化代码。
搜索webpack_require或模块 ID(如 ./src/index.js)来定位关键逻辑。
方法 2
:控制台导出模块列表
在浏览器控制台执行以下代码(需在目标页面上下文):
// 提取所有模块(适配 Webpack 4+)
const modules = Object.values(__webpack_require__.m);
modules.forEach((code, id) => {console.log(`Module ID: ${id}`, code.toString());
});// 若为 Webpack 5+ 分块加载,尝试:
window.webpackChunk.forEach(chunk => {chunk[1].forEach((mod, id) => {console.log(`Chunk Module: ${id}`, mod.toString());});
});
方法 3
:使用 Source Map 还原代码
在 Network 标签中查找 .map 文件(例如:app.js.map)。
下载 .map
文件,使用工具还原源码:
# 使用 reverse-sourcemap 工具
npx reverse-sourcemap --output-dir ./src app.js.map
3、处理动态加载的代码块(Chunks)
(1)触发动态加载
操作页面(如点击按钮、滚动)→ 在 Network 中捕获新加载的 JS文件(文件名通常类似 374.js 或 chunk-vendors.js)。
(2)关联入口文件
动态加载的代码块通常通过 import() 或 webpackJsonp 加载。可以在入口文件中搜索 lazy loading 逻辑。
✅因为Packer-Fuzzer工具提取JS会更加准确,这里可以添加本地读取JS进行测试
Packer-Fuzzer获取的JS文件在tmp目录下
-f指定本地JS文件夹
第二步:正则匹配获取接口、路由、敏感信息
针对每个类型编写相应的正则表达式,然后使用 FindAllString 或者 FindAllStringSubmatch 来提取匹配的内容
【定位路由配置的四大方法】:
- 方法 一:静态代码分析(正则匹配)
适用场景
:快速提取简单路由配置
// 匹配路由数组声明
const routeArrayRegex = /(?:routes|createRouter)\s*:\s*\[([\s\S]*?)\]/g;// 匹配单个路由对象
const routeObjectRegex = /{\s*(?:path|component|children|meta)\s*:.*?,?\s*
}+/g;// 提取路径示例
const pathRegex = /path\s*:\s*(["'`])(.*?)\1/g;
优缺点
:
✅ 快速实现
❌ 无法处理复杂逻辑(动态生成、嵌套对象)
- 方法二:AST 解析(精准分析)
步骤
:
1、使用 Babel 解析代码为 AST
2、遍历 AST 查找路由配置
3、提取路径、组件、子路由等信息
实现代码
:
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;const code = `const routes = [{path: '/user/:id',component: () => import('@/views/User.vue'),children: [{ path: 'profile', component: Profile }]}]
`;const ast = parser.parse(code, {sourceType: 'module',plugins: ['jsx']
});const foundRoutes = [];traverse(ast, {ArrayExpression(path) {if (path.parent.id?.name === 'routes') {path.node.elements.forEach(element => {if (element.type === 'ObjectExpression') {const route = {};element.properties.forEach(prop => {if (prop.key.name === 'path') {route.path = prop.value.value;}if (prop.key.name === 'children') {// 递归处理子路由}});foundRoutes.push(route);}});}}
});console.log(foundRoutes);
输出结果
:
[ { path: '/user/:id' } ]
- 方法 三:运行时分析
适用场景
:需要获取最终生效的路由配置
// 在 Vue 组件中
export default {mounted() {console.log(this.$router.getRoutes());// 输出完整路由表:// [// {// path: '/user/:id',// components: { ... },// children: [ ... ]// }// ]}
}
- 方法 四:文件系统路由解析(Nuxt.js)
目录结构
:
pages/├─ index.vue -> /├─ users/│ ├─ index.vue -> /users│ └─ [id].vue -> /users/:id└─ about.vue -> /about
解析逻辑
:
1、扫描 pages 目录结构
2、将文件名转换为动态参数:
[param].vue → :param
_param.vue → :param(Nuxt 约定)
3、生成嵌套路由结构
【高级场景处理技巧】
1、动态路由注册
处理 router.addRoute() 调用:
// 匹配模式
const addRouteRegex = /router\.addRoute\(([\s\S]*?)\)/g;// 示例代码分析
router.addRoute({path: '/admin',component: AdminPanel,meta: { requiresAuth: true }
});
2、 懒加载组件分析
识别动态导入语法:
component: () => import(/* webpackChunkName: "user" */ '@/views/User.vue')
3、 权限路由检测
检查路由元信息:
const authRoutes = router.getRoutes().filter(route => {return route.meta?.requiresAuth === true;
});
【正则表达式设计】:
Vue Router 的匹配优先级按照以下顺序:
静态路径
/about
动态参数
/:id
带正则的动态参数
/:id(\\d+)
通配符
/*
最终结果生成文本
- 第三步:自动化fuzz接口
拼接正则匹配的接口进行fuzz,包括GET/POST/PUT/json请求方式、403bypass、自动添加测试参数等功能
- 第四步:漏洞检测
暴露端点检测、漏洞检测
原理就是关键字匹配
- 第五步:vue路由测试
启动一个真实浏览器测试 Vue 路由,自动切换下一个路由
自动化工具获取:本工具使用方法在下一篇文章公布
原文🔗
https://mp.weixin.qq.com/s/nxb3IP1Q2KkB-AmTzc5cFg