Vue.js 是一个渐进式 JavaScript 框架,用于构建用户界面。而 Webpack 是一个模块打包工具,用于将项目中的各种资源(如 JavaScript 模块、CSS 样式文件、图片等)打包成浏览器可以识别的文件。
Webpack 主要用于将项目中的各种资源(如 JavaScript 模块、CSS 样式文件、图片、字体等)视为模块,通过加载器(loader)和插件(plugin)对这些模块进行处理,最终将它们打包成一个或多个浏览器可以识别的文件(通常是 bundle.js 文件)。例如,在一个大型的前端项目中,可能会有多个 JavaScript 文件,每个文件负责不同的功能模块。Webpack 可以将这些文件按照依赖关系进行分析,然后合并成一个文件,这样浏览器在加载时就只需要加载一个文件,减少了 HTTP 请求的次数,从而提高了页面的加载速度。
0x02 手工测试
0x021 框架特征识别
✅浏览器插件Wappalyzer:很容易看出这是一个Webpack打包的系统
✅专业开发者插件Vue.js devtools:亮起来就是vue框架,有时候需要强制开启
✅查看JS:大量这种chunk结构的打包文件,有些js不会自动加载,需要拼接chunk才可以访问,往往这类js会存在大量接口和敏感信息
0x022 获取接口和路由
✅浏览器插件
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
✅浏览器调试
“使用最原始的方式获取的路由往往是最全面的”
开启Vue.js devtools进行调试,先看下此时是没有vue的选项卡的
Vue Devtools 是 Vue 官方发布的调试浏览器插件,可以安装在 Chrome 和 Firefox 等浏览器上,直接内嵌在开发者工具中,使用体验流畅。Vue Devtools 由 Vue.js 核心团队成员 Guillaume Chau 和 Evan You 开发。
如果vue的图标还没有亮起来 可以在控制台输入下面代码强制开启
varVue, 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;
}
}
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限制只允许在本地环境开启调试模式,这里绕过的方法还有很多
方法1、强烈推荐使用谷歌插件Vue force dev+Vue.js devtools组合
方法2、控制台依次输入下面的几行代码,进行绕过
获取 VueDevtools 对应的全局变量(第一行代码)
const devtools = window.__VUE_DEVTOOLS_GLOBAL_HOOK__
获取项目中 Vue 实例,需要根据具体情况进行调整(第二行代码)
constVue = $('#app').__vue__.__proto__.__proto__.constructor
在生产环境中启用 vue-devtools(第三行代码)
Vue.config.devtools = true
开启vue-devtools的关键(第四行代码)
devtools.emit('init', Vue)
方法3、修改插件源码
插件地址 https://github.com/vuejs/devtools
打开main.js文件,在最末尾的地方添加下方这一行代码。
Vue.config.devtools = true
最终在生产环境成功开启了Vue devtools的调试模式
下面是各功能的说明
Components(组件):Vue 组件是封装可复用 UI 的独立模块,如同积木块般组合构建应用。每个组件包含自包含的模板、逻辑与样式,支持嵌套形成层级结构,实现复杂界面的分治开发,同时保持作用域隔离,避免代码冲突。
Props(属性):Props 是父组件向子组件传递数据的单向通道,遵循 “自上而下” 的数据流。它通过属性绑定传递值,支持类型校验和默认值设置,强制子组件不可直接修改,需通过事件通知父级变更,确保数据流动可追踪。
Data(数据):Data 是组件私有的响应式状态仓库,以函数形式返回初始数据。其内部变量变化会触发视图自动更新,但新增属性需用特定 API(如$set
)保持响应性,用于管理组件内部动态状态,与 Props 形成 “外传内管” 的数据分工。
functionfindVueRoot(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]);
}
}
returnnull;
}
functionfindVueRouter(vueRoot) {
let router;
try {
if (vueRoot.__vue_app__) {
router = vueRoot.__vue_app__.config.globalProperties.$router.options.routes
console.log("find router in Vue object", vueRoot.__vue_app__)
} elseif (vueRoot.__vue__) {
router = vueRoot.__vue__.$root.$options.router.options.routes
console.log("find router in Vue object", vueRoot.__vue__)
}
} catch (e) {}
try {
if (vueRoot.__vue__ && !router) {
router = vueRoot.__vue__._router.options.routes
console.log("find router in Vue object", vueRoot.__vue__)
}
} catch (e) {}
return router
}
functionwalkRouter(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)})
}
} elseif (node.hasOwnProperty("children")) {
stack.push({node: node.children, path: path});
}
}
callback(path, node);
}
}
functionmergePath(parent, path) {
if (path.indexOf(parent) === 0) {
return path
}
return (parent ? parent + '/' : '') + path
}
functionmain() {
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())
总结
1. Vue应用信息提取方法
-
定位根节点DOM:Vue应用的根节点通常是
<div id="app"></div>
,可通过以下方式获取:
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
获取路由配置:
// Vue2const routes = rootElement.__vue__.$router.options.routes;// Vue3const 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在控制台中手动注入配置(仅对未压缩代码有效):
// Vue2Vue.config.devtools = true;// Vue3app.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中爆破,正所谓大力出奇迹~
0x023 获取敏感信息
✅浏览器插件
FindSomething等等,快速信息搜集,看一眼就行
✅BurpSuit插件
HAE、APIFinder、Unexpected_information等等,看一眼就行
✅F12大法
(?:(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
importstruct
data=struct.pack('<bbbbbbbbbbbbbbbb', x, x, x, x, x, x, x....)
print(base64.b64encode(data))
MemoryAnalyzer必须要 JDK 11+ 才能成功启动
配置文件MemoryAnalyzer.ini 添加下面参数即可
-vm
D:javajava17binjavaw.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*fromchar[] 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*fromchar[] 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)
0x03 自动化测试
0x031 优秀工具推荐
|
|
---|---|
Packer-Fuzzer |
|
SpringBoot-Scan |
|
jjjjjjjjjjjjjs |
|
URLFinder |
|
0x032 实现自动化测试
第一步:获取JS
一、快速定位 Webpack 打包文件
1. 使用浏览器开发者工具
-
打开 Sources→ 筛选文件名包含 bundle、chunk、main 或哈希字符串(例如:
app.1a2b3c.js
)。 -
在 Network 标签中筛选 JS 类型文件,观察加载顺序(入口文件通常最先加载)。
2. 识别 Webpack 特征
Webpack 打包的代码通常包含以下标识:
// 常见特征(window.webpackJsonp = window.webpackJsonp || []).push(...)// 或function __webpack_require__(moduleId) { ... }
二、提取 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
三、处理动态加载的代码块(Chunks)
1.触发动态加载
-
操作页面(如点击按钮、滚动)→ 在 Network 中捕获新加载的 JS文件(文件名通常类似
374.js
或chunk-vendors.js
)。
2.关联入口文件
动态加载的代码块通常通过 import() 或 webpackJsonp 加载。可以在入口文件中搜索 lazy loading 逻辑。
第二步:正则匹配获取接口、路由、敏感信息
定位路由配置的四大方法
方法 1:静态代码分析(正则匹配)
适用场景:快速提取简单路由配置
// 匹配路由数组声明constrouteArrayRegex=/(?:routes|createRouter)s*:s*[([sS]*?)]/g;// 匹配单个路由对象constrouteObjectRegex=/{s* (?:path|component|children|meta)s*:.*?,?s*}+/g;// 提取路径示例constpathRegex=/paths*:s*(["'`])(.*?)1/g;
优缺点:
-
✅ 快速实现
-
❌ 无法处理复杂逻辑(动态生成、嵌套对象)
方法 2:AST 解析(精准分析)
步骤:
-
使用 Babel 解析代码为 AST
-
遍历 AST 查找路由配置
-
提取路径、组件、子路由等信息
实现代码:
constparser=require('@babel/parser');consttraverse=require('@babel/traverse').default;constcode=`const routes = [{path: '/user/:id',component: () => import('@/views/User.vue'),children: [{ path: 'profile', component: Profile }]}]`;constast=parser.parse(code, {sourceType: 'module',plugins: ['jsx']});constfoundRoutes= [];traverse(ast, {ArrayExpression(path) {if (path.parent.id?.name==='routes') {path.node.elements.forEach(element=> {if (element.type==='ObjectExpression') {constroute= {};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' } ]
方法 3:运行时分析
适用场景:需要获取最终生效的路由配置
// 在 Vue 组件中exportdefault {mounted() {console.log(this.$router.getRoutes());// 输出完整路由表:// [// {// path: '/user/:id',// components: { ... },// children: [ ... ]// }// ] }}
方法 4:文件系统路由解析(Nuxt.js)
目录结构:
pages/ ├─ index.vue -> / ├─ users/ │ ├─ index.vue -> /users │ └─ [id].vue -> /users/:id └─ about.vue -> /about
解析逻辑:
-
扫描
pages
目录结构 -
将文件名转换为动态参数:
-
[param].vue
→:param
-
_param.vue
→:param
(Nuxt 约定) -
生成嵌套路由结构
高级场景处理技巧
1. 动态路由注册
处理 router.addRoute()
调用:
// 匹配模式constaddRouteRegex=/router.addRoute(([sS]*?))/g;// 示例代码分析router.addRoute({path: '/admin',component: AdminPanel,meta: { requiresAuth: true }});
2. 懒加载组件分析
识别动态导入语法:
component: () =>import(/* webpackChunkName: "user" */'@/views/User.vue')
3. 权限路由检测
检查路由元信息:
constauthRoutes=router.getRoutes().filter(route=> {returnroute.meta?.requiresAuth===true;});
正则表达式设计
Vue Router 的匹配优先级按照以下顺序:
静态路径
/about
动态参数
/:id
带正则的动态参数
/:id(\d+)
通配符
/*
最终结果生成文本
第三步:自动化fuzz接口
拼接正则匹配的接口进行fuzz,包括GET/POST/PUT/json请求方式、403bypass、自动添加测试参数等功能
第四步:漏洞检测
暴露端点检测、漏洞检测
第五步:vue路由测试
启动一个真实浏览器测试 Vue 路由,自动切换下一个路由
自动化工具获取:本工具使用方法在下一篇文章公布
浏览器插件获取:回复公众号250211
—END—
◤ 渗透测试之道 ◢
◈ 技术无界丨渗透有度 ◈
原文始发于微信公众号(梅苑安全):VUE框架渗透测试的一些技巧(实现自动化测试)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论