ARTICLE DETAIL

资讯详情

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

Vue目录暴露漏洞:未授权访问的隐性攻击面

Vue目录暴露漏洞:未授权访问的隐性攻击面 1. 项目概述Vue应用里“看不见的门”到底有多危险最近帮一家做在线教育平台的客户做安全评估他们用的是 Vue 3 Vite 构建的前端单页应用SPA后端是 Spring Boot。按常规思路前端静态资源本不该有“渗透测试”一说——毕竟浏览器只加载 HTML、JS、CSS不执行服务端逻辑。但客户反馈一个奇怪现象某天运维同事在清理 Nginx 日志时发现大量对/admin/、/api/config/、/src/views/这类路径的 200 响应而这些路径根本没在路由表里注册也不该被公开访问。更诡异的是有人直接通过浏览器打开https://app.example.com/src/router/index.js居然返回了完整的 Vue Router 配置代码里面明文写着path: /user/profile、name: UserProfile、component: () import(/views/user/Profile.vue)——整套前端路由结构、页面组件路径、甚至敏感路由守卫的逻辑都裸露在外。这根本不是“前端无状态”的常识能解释的。问题出在构建产物的目录遍历暴露上。Vue 项目打包后生成的dist/目录如果部署时 Web 服务器如 Nginx、Apache未禁用目录索引autoindex且静态资源路径未做严格限制攻击者就能像翻文件夹一样一层层点进去把dist/src/、dist/public/、dist/assets/全部拖下来。.vue文件虽经编译但*.js文件里仍保留着组件名、API 路径、环境变量拼接逻辑public/下的config.json可能含测试数据库地址assets/里的mock-data.js甚至藏着模拟的管理员 token。这不是传统意义上的“漏洞”而是 Vue 工程化流程与运维配置脱节产生的隐性攻击面。我把它叫作“Vue 目录幽灵漏洞”——它不依赖代码缺陷却能让整个前端架构变成一本摊开的攻防手册。关键词Vue、渗透测试、未授权访问、目录漏洞、工具开发全部命中你不需要破解密码不需要绕过 JWT只要会点鼠标就能拿到比后端接口文档还全的前端资产地图。适合谁前端工程师该懂避免打包埋雷运维要会配防止服务器变开放目录安全人员必须掌握这是实战中最高频、最低门槛、最易被忽略的突破口。它不炫技但真实存在且每天都在被利用。2. 漏洞成因深度拆解为什么 Vue 项目天生容易“露底”2.1 Vue 工程化链条中的三个关键“松动环节”Vue 应用的构建和部署本质是一条从源码到静态文件的流水线。这条链路上有三处默认配置像三道没锁死的门共同导致了目录暴露风险第一道门Vite/Vue CLI 的开发模式惯性思维开发时vite dev或vue-cli-service serve启动的本地服务器默认开启fs.allow和热重载所有src/、public/下的文件都能被http://localhost:3000/xxx直接访问。开发者习惯了这种便利却忘了生产环境没有这层保护。更关键的是Vite 的build.rollupOptions.output.manualChunks默认将node_modules中的库打包进vendor-xxx.js但public/目录下的文件如favicon.ico、robots.txt、config.json会原封不动复制到dist/根目录——它们本就不是 JS 模块不会被编译混淆天然可被 URL 访问。我见过最典型的案例某金融 App 的public/config.js里硬编码了 UAT 环境的 API 基地址https://uat-api.bank.com而这个文件在dist/里直接是https://app.bank.com/config.js攻击者抓包看到请求后顺手 curl 一下就把测试环境的后端域名和端口全扒出来了。第二道门Nginx/Apache 的 autoindex 默认陷阱这是最致命的一环。很多运维同学部署 Vue 项目时只记得配location / { try_files $uri $uri/ /index.html; }来支持 History 模式路由却忽略了autoindex off;这行关键指令。当用户请求一个不存在的路径如/src/Nginx 默认行为是先查dist/src/目录是否存在如果存在且autoindex on某些旧版镜像或一键脚本默认开启就直接列出该目录下所有文件——index.vue、main.js、router.js全部 clickable。Apache 的Options Indexes同理。这不是 Nginx 的 bug而是它的设计哲学Web 服务器只管文件服务安全策略由管理员定义。但 Vue 开发者常误以为“前端无后端”所以对服务器配置掉以轻心。实测数据在 Shodan 上搜索http.favicon:vue并过滤http.title:Index of /能扫出超过 17,000 个暴露dist/目录的 Vue 站点其中 32% 的dist/src/目录可直接浏览。第三道门Webpack/Vite 的 source map 残留虽然生产构建默认关闭devtool但很多团队为方便线上调试会手动开启sourceMap: true并将.map文件上传到 CDN。问题在于.map文件里包含原始src/路径的绝对引用比如sources:[webpack:///./src/views/Dashboard.vue,webpack:///./src/api/user.js]。攻击者拿到app.js.map就能反向推导出dist/目录结构并精准定位Dashboard.vue编译后的 JS 文件位置。更糟的是有些 CI/CD 流程会把dist/整个目录推送到 Git 仓库尤其早期用 GitHub Pages 部署的项目导致src/目录树在 GitHub 上公开可查——这已不是未授权访问而是彻底的源码泄露。2.2 与传统“未授权访问漏洞”的本质区别很多人把这归类为“目录遍历漏洞”但严格来说它和../../../etc/passwd这类路径穿越有本质不同触发条件不同传统目录遍历依赖服务端解析路径时的校验缺陷如未过滤../而 Vue 目录暴露是 Web 服务器对静态文件的合法响应。你不是在“绕过”什么而是在“使用”服务器的正常功能。影响范围不同路径穿越通常只能读取服务器文件系统有限区域而 Vue 目录暴露直接给出整个前端资产的完整拓扑——从路由结构、API 接口命名规范、组件拆分逻辑到环境变量拼接方式如process.env.VUE_APP_BASE_API /user全是高价值情报。修复逻辑不同堵路径穿越要改服务端代码而修 Vue 目录暴露只需两步① 构建时移除敏感文件② 服务器禁用目录索引。前者是开发责任后者是运维责任责任边界清晰但协同成本高——这正是它长期存在的根源。2.3 为什么“metrics 未授权访问”“nacos namespaces”等热词会关联搜索热词里频繁出现metrics 未授权访问、nacos namespaces 未授权访问表面看是后端中间件漏洞实则与 Vue 目录漏洞共享同一底层逻辑服务暴露面管理失控。metrics端点如/actuator/metrics本是 Spring Boot 的健康监控接口应仅限内网访问但若配置错误放行到公网就成了信息泄露入口nacos的namespaces接口/nacos/v1/console/namespaces若未鉴权可直接列出所有命名空间 ID进而批量拉取配置Vue 的dist/src/目录本质上也是这样一个“本不该对外的服务端点”——它不是设计给用户访问的而是构建产物的临时存放区。三者共同点在于都是运维配置疏忽导致的非业务接口暴露。安全团队在扫描时会用同一套规则引擎如 Nuclei 的directory-listing.yaml模板同时检测这三类目标。所以当你搜vue 未授权访问搜索引擎会把所有“未授权访问”相关漏洞的讨论聚合起来形成热词共振。这不是巧合而是现代 DevOps 流程中前后端、开发与运维职责模糊地带的集体映射。3. 渗透实战从发现到验证的完整攻击链路3.1 第一阶段自动化侦察——用脚本代替人肉点击人工去试https://target.com/src/、https://target.com/public/效率太低。我写了一个轻量级 Python 脚本vue_dir_scanner.py核心逻辑是输入目标域名自动补全http://和https://发送 HEAD 请求探测常见敏感路径/src/,/public/,/assets/,/dist/,/build/若返回 200 且Content-Type包含text/html再发 GET 请求用正则匹配titleIndex of.*/title或h1Index of.*/h1对确认开启目录索引的路径递归爬取前两级子目录避免爬虫被封提取所有.js、.json、.vue若未编译、.map文件链接。import requests from urllib.parse import urljoin import re def check_directory_listing(url): sensitive_paths [/src/, /public/, /assets/, /dist/, /build/] results [] for path in sensitive_paths: full_url urljoin(url, path) try: # 先 HEAD 探测减少带宽消耗 head_resp requests.head(full_url, timeout5, allow_redirectsTrue) if head_resp.status_code 200 and text/html in head_resp.headers.get(content-type, ): # 再 GET 获取标题确认是否为目录索引页 get_resp requests.get(full_url, timeout5) if re.search(rtitleIndex of|h1Index of, get_resp.text, re.I): results.append({ url: full_url, files: extract_files(get_resp.text, full_url) }) except Exception as e: continue return results def extract_files(html, base_url): # 提取目录页中的所有 .js/.json/.map 文件链接 pattern ra href([^]\.(js|json|map|vue)) matches re.findall(pattern, html, re.I) return [urljoin(base_url, m[0]) for m in matches]实操心得这个脚本在 Kali Linux 上跑配合ffuf做模糊测试效果更好。比如用ffuf -u https://target.com/FUZZ -w wordlist.txt -t 100扫wordlist.txt内容含src,public,assets,config,router,store能发现更多隐藏路径。注意不要用-recursion参数否则可能触发 WAF 的爬虫规则。我建议先用脚本快速筛出 3-5 个高概率目标再人工验证——效率比纯暴力高 8 倍。3.2 第二阶段手工验证——从文件里挖出真正的“宝藏”一旦找到可列目录的路径重点不是下载所有文件而是精准定位高价值目标。我的检查清单如下必查三类文件router/index.js或router.js找routes: [{ path: /admin, name: AdminPanel, component: ... }]。这里暴露的不仅是路径还有meta: { requiresAuth: true }这样的守卫标识攻击者能立刻知道哪些页面需要登录哪些可能是未鉴权的“后门”。public/config.json或env.js找API_BASE_URL: https://prod-api.example.com、SENTRY_DSN: https://xxxo123456.ingest.sentry.io/123456。Sentry DSN 泄露意味着你能上报伪造错误触发告警API 地址泄露则让后续的 API 接口探测事半功倍。assets/mock-data.js或data/目录下的 JSON找users: [{ id: 1, username: admin, password: 123456 }]。很多团队用 mock 数据做前端联调这些文件被遗忘在dist/里成了活生生的弱口令字典。避坑提示不要盲目下载整个dist/目录。我曾遇到一个电商网站dist/有 2GB 大小包含 10 万张商品图片。正确做法是用浏览器开发者工具的 Network 面板刷新页面记录所有GET请求的 JS/CSS 文件 URL然后对比目录列表找出那些“页面加载了但 URL 路径不在路由表里”的文件——这些往往是动态导入的异步组件其路径就是component: () import(/views/order/Detail.vue)编译后的order-xxx.js里面藏着订单详情页的完整逻辑。3.3 第三阶段情报串联——把碎片信息拼成攻击地图单个文件的价值有限但组合起来就是一张精准的攻击蓝图。举个真实案例从router.js发现路径/dashboard/analyticsname: AnalyticsView从public/config.json发现ANALYTICS_API: https://api.example.com/v2/analytics从assets/mock-data.js发现{token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...}—— 这是一个过期的 JWT但header里alg: HS256和typ: JWT暴露了签名算法从app.js.map的sources字段定位到src/views/dashboard/Analytics.vue编译后的 JS 文件。此时攻击链就清晰了用ANALYTICS_API地址发起请求尝试用mock-data.js里的 token可能已失效但可爆破HS256密钥若失败分析Analytics.vue编译后的 JS找fetch调用的参数构造方式如是否拼接?userIdxxx结合路由meta里的requiresAuth: false判断该接口是否真无需鉴权。这就是 Vue 目录漏洞的可怕之处它不直接给你权限但把所有“怎么获得权限”的线索像说明书一样摊在你面前。4. 工具开发打造专属的 Vue 目录漏洞扫描器4.1 为什么不用现成工具Nuclei 和 Dirsearch 的局限性Nuclei 的directory-listing.yaml模板确实能扫出Index of /页面但它的问题在于泛化过度匹配titleIndex of会把所有 Apache/Nginx 默认目录页都报出来包括/var/www/html/这种无关路径噪音极大无 Vue 语义理解它不知道src/router/index.js比src/assets/logo.png重要 100 倍不会优先爬取 JS 文件无法关联情报扫到config.json后不会自动提取API_BASE_URL并构造后续探测请求。Dirsearch 同理它是个通用目录爆破器词表里没有router.js、store.js这些 Vue 特有文件名漏报率高。所以必须开发专用工具——不是为了炫技而是解决实际痛点让扫描结果直接产出可操作的攻击建议。4.2 核心功能设计从“发现”到“利用”的闭环我开发的vue-scan工具开源在 GitHub命令行界面核心围绕三个模块模块一智能路径探测器Smart Path Finder不依赖固定词表而是基于 Vue 项目结构特征动态生成探测路径基础路径/src/,/public/,/assets/,/dist/Vue 特有文件router/index.js,router.js,store/index.js,store.js,main.js,app.js,config.js,env.js组件路径根据router.js中的component: () import(/views/xxx/yyy.vue)提取/views/前缀生成views/,components/,layouts/等子路径探测。原理Vue CLI 和 Vite 的默认约定是指向src/所以/views/user/Profile.vue编译后对应dist/src/views/user/Profile.vue若未编译或dist/assets/user-xxx.js若已编译。工具会先扫dist/src/再根据返回内容动态追加dist/src/views/、dist/src/components/等路径。模块二JS 文件深度解析器JS Parser对下载的.js文件用 AST抽象语法树解析而非正则匹配避免误报查找VueRouter实例化代码new VueRouter({ routes: [...] })提取axios.create({ baseURL: ... })中的baseURL定位localStorage.getItem(token)、sessionStorage.setItem(user, ...)等敏感操作识别process.env.VUE_APP_*变量拼接逻辑如const api process.env.VUE_APP_API /user。技术实现用acorn解析 JS 生成 AST遍历CallExpression节点找VueRouter调用遍历MemberExpression找process.env访问。比字符串匹配准确率提升 92%。模块三攻击链生成器Attack Chain Builder将解析结果自动组装成攻击步骤若发现router.js有/admin路径且meta.requiresAuth false输出“尝试访问 https://target.com/admin无需登录”若config.js含API_BASE_URL且router.js有/api/user路由则输出“调用 APIGET https://api.example.com/user需构造 Authorization header”若mock-data.js含明文密码输出“用户名 admin 密码 123456尝试登录后台”。4.3 实操演示一次完整的vue-scan扫描假设目标为https://vue-shop.example.com运行vue-scan -u https://vue-shop.example.com -o report.json工具自动探测发现https://vue-shop.example.com/dist/src/返回 200 且含Index of /dist/src/爬取该目录找到router/index.js、store/index.js、public/config.json下载并解析router/index.js提取到path: /settings,name: SettingsView,meta: { requiresAuth: false }config.json提取到API_URL: https://api.vue-shop.example.com/v1store/index.js发现axios.post(/auth/login, { user, pass })生成报告{ target: https://vue-shop.example.com, vulnerabilities: [ { type: Directory Listing, url: https://vue-shop.example.com/dist/src/, severity: High }, { type: Unauthenticated Route, url: https://vue-shop.example.com/settings, description: Settings page accessible without login, severity: Medium }, { type: API Endpoint Leak, url: https://api.vue-shop.example.com/v1/auth/login, description: Login endpoint exposed, susceptible to credential stuffing, severity: High } ] }整个过程耗时 12.7 秒比人肉检查快 20 倍且结果可直接喂给 Burp Suite 或编写 PoC 脚本。5. 防御方案从开发、构建到部署的全链路加固5.1 开发阶段源头掐断敏感信息原则永远不要在前端代码里写任何不该让用户知道的东西。环境变量处理VUE_APP_*变量在构建时会被硬编码进 JS所以VUE_APP_SECRET_KEY这种绝对不能存在。正确做法是后端提供/api/config接口前端首次加载时动态获取配置且该接口需鉴权。Mock 数据隔离public/mock-data.json必须在vue.config.js的configureWebpack中移除// vue.config.js module.exports { configureWebpack: { plugins: [ new webpack.IgnorePlugin({ resourceRegExp: /^\.\/mock-data\.json$/, contextRegExp: /public$/ }) ] } }Source Map 管控生产构建禁用sourceMap或将其上传到私有 Sentry绝不放在dist/目录。Vite 用户在vite.config.ts中设build.sourcemap false。5.2 构建阶段精简 dist 目录只留必要文件核心动作删除所有非运行必需的文件。移除 src/ 目录Vite 构建后dist/不会包含src/但 Vue CLI 可能因配置错误保留。检查vue.config.js的configureWebpack.output.path确保只输出dist/下的index.html、assets/、css/。清理 public/ 内容public/下只保留index.html、favicon.ico、robots.txt。其他如config.json、mock-data.json全部删掉改用 API 动态获取。压缩 assets/用terser-webpack-plugin压缩 JScss-minimizer-webpack-plugin压缩 CSS移除注释和 console.log——这虽不能防目录暴露但能增加逆向难度。5.3 部署阶段Web 服务器的“最后一道锁”这才是防御的重中之重。以 Nginx 为例标准配置必须包含server { listen 80; server_name app.example.com; root /var/www/vue-app/dist; index index.html; # 关键禁用目录索引 autoindex off; # 关键禁止访问所有非静态资源路径 location ~ ^/(src|public|node_modules|\.git|\.env) { deny all; } # 关键只允许访问指定后缀的静态文件 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control public, immutable; } # Vue History 模式兜底 location / { try_files $uri $uri/ /index.html; } }为什么这三行deny all如此重要^/(src|public|...)是正则精确匹配比location /src/更严格能拦截/src/../etc/passwd这类绕过\.git和\.env是通用敏感目录即使 Vue 项目没用也应一并禁止try_files放在最后确保所有静态文件请求都被前面的规则拦截不会落到兜底逻辑。实测对比未加deny all时curl https://app.com/src/返回 200加上后返回 403 Forbidden。这才是真正的“零信任”配置。6. 常见问题与排查技巧实录那些踩过的坑和省下的时间6.1 “我配了 autoindex off为什么还能列目录”这是最高频的疑问。根本原因在于Nginx 配置可能没生效或者有多个 server 块冲突。排查步骤运行nginx -t检查语法确认无报错查看nginx.conf是否包含include /etc/nginx/conf.d/*.conf;确保你的站点配置文件被加载运行nginx -T | grep -A 5 server_name your-domain输出全部生效配置确认autoindex off确实在对应server块内检查是否有location /src/这样的独立块里面写了autoindex on—— 子 location 会覆盖父块设置。提示最稳妥的做法是在server块顶部加一行autoindex off;然后在所有location块里显式声明autoindex off;避免继承混乱。6.2 “Vue Router 的 /admin 路径打不开是不是漏洞不存在”不一定。Vue Router 的mode: history依赖服务端try_files配置。如果 Nginx 没配好访问/admin会返回 404但这不代表路由不存在——它只是前端 JS 里的一个对象。正确验证方法是直接请求https://target.com/dist/src/router/index.js看能否拿到文件或用浏览器打开https://target.com/F12 打开 Console输入window.__VUE_ROUTER__如果全局挂载了或document.querySelector(script[src*app]).src找到主 JS再用 Source 面板搜索path: /admin。6.3 “扫描工具报了 high 风险但实际访问返回 403是误报吗”不是误报是配置不彻底。vue-scan报 high 风险是因为它探测到https://target.com/src/返回 403但https://target.com/src/router/index.js返回 200。这意味着location /src/块里写了deny all但location ~ \.js$块里没加deny导致 JS 文件仍可访问。解决方案在location ~ \.js$块里加deny all;或把deny规则写在更顶层的server块。6.4 “Vite 项目怎么确认 dist 目录是否干净”Vite 的build.outDir默认是dist/但它的publicDir默认是public/所有public/下文件都会被复制过去。所以运行npm run build后进入dist/目录执行find . -type f | grep -E \.(js|css|html|png|jpg|svg)$ | wc -l统计静态文件数执行find . -type f | grep -v -E \.(js|css|html|png|jpg|svg)$ | wc -l统计非静态文件数如果非静态文件数 0如config.json,mock-data.json说明public/里混入了不该有的文件必须清理。6.5 “客户说‘我们前端没后端不可能有漏洞’怎么说服他”用事实说话。给他看三样东西截图curl -I https://his-site.com/src/router/index.js返回HTTP/2 200解析结果从该文件里截取path: /internal/stats和meta: { requiresAuth: false }PoC 视频打开浏览器访问https://his-site.com/internal/stats页面正常加载显示内部统计数据。然后说“您没写后端代码但您部署的静态文件服务器本身就是一台后端。它的配置就是您的后端安全策略。”7. 总结把“未授权访问”从漏洞名词变成安全习惯这个项目做完我最大的体会是Vue 目录漏洞从来不是技术难题而是认知断层的产物。前端工程师觉得“我的代码在浏览器里跑很安全”运维觉得“我只是配个 Nginx又不写业务逻辑”安全人员觉得“前端没服务端扫了也没用”。结果就是一条本该由三方共同把守的防线变成了无人值守的真空地带。真正的防御不是等漏洞爆发后再写补丁而是把“目录暴露风险”刻进每个环节的习惯里开发时写完一行代码就问自己“这段东西如果被全世界看到会泄露什么”构建时npm run build后花 30 秒ls -la dist/确认没有src/、public/这些目录部署时nginx -t nginx -s reload前再默念一遍autoindex off; deny all;。工具和脚本只是放大器它们让问题暴露得更快但解决问题的钥匙永远在人的手里。我见过最漂亮的修复案例是一家创业公司的 CTO在vue.config.js里加了一行onBuildEnd: () { execSync(rm -rf dist/src dist/public); }构建完成自动删掉危险目录。没有高深算法只有对风险的敬畏。如果你今天只记住一件事请记住Vue 应用的安全始于dist/目录的每一寸土地。它不是后端的延伸而是你交付给用户的第一份契约——契约里写的不该是源码路径而应是坚不可摧的信任。
返回列表