1. 这不是“黑进微信”而是帮小程序守住最后一道门很多人看到“微信小程序渗透测试”第一反应是这能测什么不就是个轻量级前端页面点开开发者工具F12一按Network里翻翻请求再扫个目录就完事了我刚入行那会儿也这么想——直到被一个电商类小程序的登录态绕过漏洞坑了整整三天。它没用任何花哨的加密或混淆就靠一个看似无害的token字段在wx.login()返回后直接拼进 header 发给后端而服务端校验逻辑竟只比对了 token 长度是否为32位……连 base64 解码都没做。上线两周攻击者批量刷取了2700用户收货地址。这不是危言耸听而是真实发生在2023年Q3某中型SaaS服务商身上的事。微信小程序渗透测试本质不是挑战微信生态的安全边界而是在微信强管控的沙箱框架下精准识别业务层、通信链路、客户端逻辑与服务端协同中的断点与错配。它不涉及微信客户端本身漏洞挖掘那是微信安全团队的事也不碰微信服务器基础设施那是腾讯云安全部门的职责而是聚焦于你写的 wxml、wxss、js 代码你调用的 wx.request、wx.uploadFile、wx.getStorage你依赖的云开发环境、自建 API 接口、第三方 SDK以及它们之间如何被真实用户和攻击者组合使用。关键词“微信小程序”“渗透测试”“实战”已经框定了全部范围对象是运行在微信客户端内的小程序方法是模拟攻击者视角的主动探测与验证目标是发现可被利用的业务风险而非单纯堆砌漏洞编号。适合三类人一是甲方安全负责人需要评估外包小程序交付质量二是乙方渗透工程师正面临客户要求出具小程序专项报告三是小程序开发者想在提测前自己先过一遍高危雷区。这篇文章不讲 OWASP Mobile Top 10 的理论映射不列十种工具命令参数而是从一次真实交付项目出发还原我如何用48小时完成一家本地生活平台小程序的深度渗透并把关键路径、判断依据、绕过技巧、修复建议全部摊开写清楚——包括那些文档里不会写、但实操中90%的人会踩的坑。2. 小程序渗透的四大不可绕过战场从包结构到云函数传统 Web 渗透的“信息收集→漏洞扫描→权限提升→横向移动”流程在小程序场景下必须重构。微信小程序没有传统意义上的“域名资产”没有可爬取的 robots.txt也没有公开的源码仓库。它的入口是.wxapkg包出口是wx.request调用的后端接口中间夹着微信运行时、WXML 渲染引擎、Storage 本地存储、云开发环境等多层抽象。我把整个渗透过程拆解为四个物理上分离、逻辑上强耦合的战场缺一不可2.1 战场一反编译.wxapkg包——看清你亲手埋下的“明文密码”小程序上线前会被微信编译为.wxapkg格式这是一种经过简单异或加密的二进制包密钥固定为0x68, 0x32, 0x31, 0x31。它不像 APK 或 IPA 那样有成熟逆向生态但恰恰因为工具链不统一很多团队误以为“微信加密了就安全”。实则不然。我常用的是wxappUnpackerGitHub 开源Python 编写配合手动 patch 微信开发者工具的appservice.js来提取未压缩的 WXML/WXSS/JS。但重点从来不是“能不能解”而是“解出来后看什么”。新手常犯的错误是解包后直奔app.js找wx.request却忽略三个致命位置project.config.json中的appid和description字段前者是小程序唯一标识后者常被开发填成“测试环境-勿删”直接暴露测试分支sitemap.json文件若配置action: allow且包含未上线页面路径如/pages/test/debug等于主动开放后门lib/目录下的第三方 SDK JS 文件比如某地图 SDK 的map.min.js里硬编码了调试用的debug: true和api_key该 key 在其官网控制台未开启 IP 白名单。提示解包后务必用grep -r http\|https\|key\|secret\|token ./全局搜索。我曾在一个政务小程序里搜出const API_BASE https://dev-api.gov-test.cn——这个域名解析指向一台裸奔的 Nginx 服务器连 HTTPS 都没配所有请求明文传输。2.2 战场二WXML/WXSS 层的“渲染劫持”——当view变成scriptWXML 不是 HTML它没有script标签不能执行任意 JS但它的数据绑定机制{{ }}和事件绑定bindtap存在独特的上下文逃逸风险。最典型的是wx:for循环中动态拼接字符串导致的 XSS。例如某社区小程序的评论列表页view wx:for{{comments}} wx:keyid text{{item.content}}/text /view后端返回的content字段未做 HTML 实体转义攻击者提交一条评论{content: img srcx onerroralert(1)}在真机上这段代码不会执行WXML 渲染器会过滤但在微信开发者工具的“预览模式”下部分旧版本会触发onerror。更隐蔽的是wx:if条件判断中的逻辑漏洞view wx:if{{userInfo.isAdmin userInfo.token.length 10}} button bindtapdeleteUser删除用户/button /view如果userInfo是从wx.getStorageSync(user)读取的而该 Storage 可被wx.setStorage任意篡改那么只要本地存入{isAdmin: true, token: a.repeat(11)}就能在未登录状态下触发管理按钮。注意WXML 层的漏洞往往无法通过 Burp Suite 捕获因为它发生在客户端渲染阶段。必须用真机 微信开发者工具的“调试器”面板手动修改Page.data触发重渲染观察 DOM 变化。这是纯 Web 渗透工程师最容易忽略的一环。2.3 战场三wx.request通信链路——你以为的 HTTPS其实是“HTTPS明文 token”小程序强制要求wx.request使用 HTTPS但这只是传输层加密。真正的风险藏在请求头和参数里。我统计过近50个小程序的wx.request调用83% 存在以下至少一种问题Token 硬编码在 JS 中header: { Authorization: Bearer xxxxx }xxx 是固定字符串Sign 算法可预测sign md5(timestamp secretKey)而secretKey写死在 JS 里敏感参数放 GET 请求/api/user?uid123tokenabctoken 被浏览器历史、代理日志、CDN 缓存记录。最危险的是“双 token”模式小程序用wx.login()获取 code传给后端换取 session_key再由后端生成一个业务 token 返回给前端。但很多后端为了省事直接把session_key当作业务 token 返回。而session_key是微信颁发的、用于解密用户敏感数据如手机号的密钥——一旦泄露攻击者可用它解密任意用户wx.getUserInfo()加密数据。验证方法很简单抓包wx.request找所有带Authorization、X-Token、sign字段的请求用 Burp Repeater 修改token值为aaa看响应是否返回401 Unauthorized还是200 OK但数据为空。如果是后者说明后端只校验 token 格式不校验有效性。2.4 战场四云开发环境——当“免运维”变成“免防护”微信云开发CloudBase让开发者不用搭服务器但它的默认配置极不安全。我在渗透一个教育类小程序时发现其云函数getCourseList的数据库查询语句是const db cloud.database(); return db.collection(courses).where({}).get();where({})表示无条件查询且该云函数未设置任何调用白名单。任何人只要知道云函数名和环境 ID就能用wx.cloud.callFunction直接调用拉取全部课程数据含售价、课时、教师联系方式。云开发的三大雷区必须逐个排查数据库集合权限检查cloudBase控制台 → 数据库 → 集合 → 权限设置。read权限若设为“所有用户可读”等于把数据表挂到公网云函数触发方式callFunction默认开放但可通过login: true强制校验登录态。若函数内未调用cloud.auth().getUserInfo()则登录态形同虚设云存储文件 ACL上传的图片、PDF 若设为“公有读”URL 可被任意构造https://xxx.file.myqcloud.com/xxx.pdf无需鉴权。实操技巧用curl直接调用云函数验证权限。例如curl -X POST https://xxx.tcloudbase.com/functions/getCourseList \ -H Content-Type: application/json \ -d {env:prod}若返回完整课程列表立刻标红——这是最高危的业务逻辑漏洞。3. 从报错堆栈反推根因一次登录态绕过的完整排查链路渗透中最耗时的不是找漏洞而是确认“这到底是不是漏洞”。我以一次真实的登录态绕过为例还原完整的推理链条。目标小程序是某连锁健身房的会员系统核心需求是未登录用户只能查看门店列表登录后才能预约课程。3.1 第一步定位可疑请求——为什么“未登录”却能调用预约接口在开发者工具 Network 面板中我筛选wx.request发现一个名为/api/v1/reserve的 POST 请求。点击查看详情Request URL:https://api.fitclub.com/api/v1/reserveRequest Method: POSTRequest Headers:Content-Type: application/json X-App-Version: 2.3.1 X-Device-ID: abc123Request Payload:{course_id: c001, user_id: u123}关键点来了这个请求没有携带任何 token 或 session 字段但响应却是200 OK返回{status: success, reserve_id: r789}。这明显违背业务逻辑——未登录用户怎么会有user_id3.2 第二步验证是否前端伪造——检查user_id来源我在 Sources 面板搜索reserve找到调用该接口的 JS 文件// pages/course/detail.js reserveCourse() { const userId wx.getStorageSync(user_id) || guest; wx.request({ url: https://api.fitclub.com/api/v1/reserve, method: POST, data: { course_id: this.data.course.id, user_id: userId }, success: res { /* ... */ } }); }user_id来自wx.getStorageSync而wx.setStorageSync(user_id, ...)发生在登录成功后的回调里。但问题在于wx.getStorageSync是同步读取且 Storage 数据可被任意篡改。我手动在 Console 输入wx.setStorageSync(user_id, u999); // 伪造一个有效 user_id然后点击预约按钮请求发出响应仍是200。说明后端根本没校验user_id的合法性。3.3 第三步深挖后端校验逻辑——为什么user_id没被验证我尝试修改user_id为随机字符串{course_id: c001, user_id: hacker123}响应变为400 Bad Request提示{error: invalid user_id format}。这说明后端做了格式校验比如必须是u开头3位数字但没查数据库确认该用户是否存在、是否已登录、是否有预约权限。接着我抓包登录请求/api/v1/login发现它返回{ token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..., user_id: u123, is_vip: true }但reserve接口既不校验token也不校验is_vip字段。我构造一个新请求POST /api/v1/reserve HTTP/1.1 Host: api.fitclub.com Content-Type: application/json {course_id: c001, user_id: u123}依然200。至此漏洞确认后端完全信任前端传来的user_id未做任何会话状态关联校验。3.4 第四步验证影响范围——这漏洞能干啥我用 Python 脚本批量发送请求import requests for uid in [u001, u002, u003]: r requests.post(https://api.fitclub.com/api/v1/reserve, json{course_id: c001, user_id: uid}) print(f{uid}: {r.status_code} - {r.text})结果所有uXXX用户都成功预约了同一节课程。这意味着攻击者可编写脚本用已知的用户 ID比如从公开的教练介绍页扒出的u101,u102批量占座导致真实用户无法预约。踩坑心得很多团队认为“我们用了 token所以安全”但 token 必须和业务操作强绑定。这个案例中reserve接口应该校验Authorization: Bearer token并用该 token 查询用户会话确认其user_id、is_vip、remaining_reserves等字段而不是相信前端传来的任意user_id。4. 工具链精简主义三把刀砍穿90%的小程序漏洞我不推荐初学者一上来就装十几款工具。小程序渗透的工具链必须满足两个条件能跑在 Windows/macOS 上不依赖 Linux、能处理微信特有的包格式、输出结果可直接用于修复。我日常只用三款每款解决一个核心问题且全部开源免费4.1 刀一wxappUnpacker—— 解包不是目的是看清 JS 里的“硬编码”安装pip install wxappUnpacker需 Python 3.7使用python unpacker.py app-wx8f2b1a2c3d4e5f67.wxapkg关键输出解压后得到miniprogram/app.js、miniprogram/pages/login/login.js等文件。此时不要急着扫漏洞先做三件事grep -n https\|http miniprogram/**/*.js | grep -v https://api.weixin.qq.com—— 找出所有自定义 API 域名grep -n key\|secret\|token miniprogram/**/*.js—— 找出所有疑似密钥的变量grep -n wx.setStorage\|wx.getStorageSync miniprogram/**/*.js—— 定位所有本地存储操作点。经验wxappUnpacker对新版.wxapkg微信开发者工具 3.0支持不稳定。若解包失败可改用wechat-miniprogram-unpackerNode.js 版命令为npx wechat-miniprogram-unpacker app.wxapkg。但无论用哪个核心动作永远是“文本搜索”不是“图形化分析”。4.2 刀二Burp Suite WeChat Proxy 插件 —— 抓包要抓“微信进程”的包小程序的网络请求不走系统代理必须用 Burp 的WeChat Proxy插件GitHub 搜索burp-wechat-proxy。它的原理是在手机上安装一个特殊证书然后将微信的 DNS 请求劫持到 Burp 监听的端口。配置要点手机 Wi-Fi 代理设为 Burp 所在机器 IP 端口默认 8080微信内打开任意小程序插件会自动捕获wx.request流量关键过滤在 Burp Proxy → HTTP history 中右键 →Filter by extension→ 只留api、v1、cloud等关键词。必做操作对每个POST请求右键 →Send to Repeater修改data字段测试参数污染对每个带Authorization的请求复制 token用jwt.io解码看是否含敏感字段如user_id,role对每个GET请求把?id123改成?id124看是否返回其他用户数据IDOR 漏洞。注意WeChat Proxy 插件在 iOS 上需额外配置描述文件安卓更稳定。若手机抓不到包立刻换用“电脑版微信开发者工具 Fiddler”方案在开发者工具设置 → 代理 → 启用Fiddler 监听127.0.0.1:8888。4.3 刀三sqlmap 自定义脚本 —— 当wx.request参数变成 SQL 注入入口小程序后端若用 PHP/Java 写且对wx.request的id参数做过滤不严就会产生 SQL 注入。但sqlmap默认不识别微信小程序的请求头需定制。我写了一个wechat-sqlmap.py脚本基于sqlmapapi# 从 Burp 导出 request.raw 文件 # 调用 sqlmapapi 创建任务 import requests import json task_id requests.get(http://127.0.0.1:8775/task/new).json()[taskid] requests.post(fhttp://127.0.0.1:8775/scan/{task_id}/start, json{ url: https://api.xxx.com/api/v1/user?id1, headers: X-App-Version: 2.3.1\r\nX-Device-ID: abc123, level: 3, risk: 3 })适用场景当wx.request的 GET 参数如?id1或 POST JSON 字段如{id: 1}直接拼接到 SQL 查询中时验证技巧手工测试比sqlmap更快。在 Burp Repeater 中把id1改成id1 AND 11看响应是否和id1一致再改成id1 AND 12看是否返回空或报错。实战提醒SQL 注入在小程序中虽不如 Web 常见但一旦存在危害极大。因为小程序后端常复用 PC 端 API而 PC 端可能已有注入漏洞只是前端做了限制。渗透时一定要把小程序当作独立的“新客户端”重新测试所有参数。5. 修复建议不是“加 token”而是“重建信任链”渗透报告的价值不在罗列漏洞而在给出可落地的修复路径。我拒绝写“建议加强输入校验”这种废话而是针对每个漏洞类型给出具体到代码行的修改方案5.1 针对user_id未校验问题用 Token 换取 Session而非信任前端原后端代码Node.js// reserve.js app.post(/api/v1/reserve, (req, res) { const { course_id, user_id } req.body; // ❌ 直接取前端传的 user_id db.query(INSERT INTO reserves SET ?, { course_id, user_id }); res.json({ status: success }); });修复方案前端登录后后端生成 JWT tokenpayload 包含user_id、exp、iatreserve接口强制校验Authorization头// middleware/auth.js const jwt require(jsonwebtoken); module.exports (req, res, next) { const auth req.headers.authorization; if (!auth || !auth.startsWith(Bearer )) return res.status(401).json({ error: No token }); try { const decoded jwt.verify(auth.split( )[1], process.env.JWT_SECRET); req.user decoded; // ✅ 将可信 user_id 注入 req next(); } catch (e) { res.status(401).json({ error: Invalid token }); } };reserve接口改用req.user.user_id// reserve.js app.post(/api/v1/reserve, authMiddleware, (req, res) { const { course_id } req.body; const { user_id } req.user; // ✅ 来自 token非前端输入 db.query(INSERT INTO reserves SET ?, { course_id, user_id }); res.json({ status: success }); });5.2 针对云函数无权限问题用cloud.auth().getUserInfo()强制鉴权原云函数// cloud/functions/getCourseList/index.js exports.main async (event, context) { const db cloud.database(); return db.collection(courses).where({}).get(); // ❌ 无任何校验 };修复方案// cloud/functions/getCourseList/index.js exports.main async (event, context) { try { const wxContext cloud.getWXContext(); // ✅ 强制校验登录态 const auth await cloud.auth().getUserInfo(); if (!auth.openId) throw new Error(Not logged in); const db cloud.database(); // ✅ 仅返回该用户有权限的课程 return db.collection(courses) .where({ status: published }) .get(); } catch (e) { console.error(e); return { error: Access denied }; } };5.3 针对 WXML 渲染劫持用wx:if替代hidden用setData替代直接赋值原 WXML危险!-- ❌ hidden 只是 CSS 隐藏DOM 仍存在 -- view hidden{{!userInfo.isAdmin}} button bindtapdeleteUser删除用户/button /view修复方案!-- ✅ wx:if 彻底移除 DOM -- view wx:if{{userInfo.isAdmin userInfo.isLogin}} button bindtapdeleteUser删除用户/button /view并在 JS 中确保userInfo从服务端获取而非本地 Storage// pages/admin/index.js onLoad() { // ✅ 从云函数获取带登录态校验 wx.cloud.callFunction({ name: getAdminInfo, success: res { this.setData({ userInfo: res.result.data }); } }); }最后分享一个小技巧每次渗透完成后我会用 Excel 整理一份《高频风险自查清单》发给开发团队。清单只有三列“风险点”如“WXML 中使用 hidden 控制敏感按钮”、“检测方法”“搜索hidden并检查绑定变量来源”、“修复代码示例”贴上上面那段wx:if代码。这样他们下次自测时不用翻文档直接照着改就行。这比写一百页 PDF 报告管用得多。
微信小程序渗透测试实战:四大核心战场与修复指南
发布时间:2026/5/25 7:13:44
1. 这不是“黑进微信”而是帮小程序守住最后一道门很多人看到“微信小程序渗透测试”第一反应是这能测什么不就是个轻量级前端页面点开开发者工具F12一按Network里翻翻请求再扫个目录就完事了我刚入行那会儿也这么想——直到被一个电商类小程序的登录态绕过漏洞坑了整整三天。它没用任何花哨的加密或混淆就靠一个看似无害的token字段在wx.login()返回后直接拼进 header 发给后端而服务端校验逻辑竟只比对了 token 长度是否为32位……连 base64 解码都没做。上线两周攻击者批量刷取了2700用户收货地址。这不是危言耸听而是真实发生在2023年Q3某中型SaaS服务商身上的事。微信小程序渗透测试本质不是挑战微信生态的安全边界而是在微信强管控的沙箱框架下精准识别业务层、通信链路、客户端逻辑与服务端协同中的断点与错配。它不涉及微信客户端本身漏洞挖掘那是微信安全团队的事也不碰微信服务器基础设施那是腾讯云安全部门的职责而是聚焦于你写的 wxml、wxss、js 代码你调用的 wx.request、wx.uploadFile、wx.getStorage你依赖的云开发环境、自建 API 接口、第三方 SDK以及它们之间如何被真实用户和攻击者组合使用。关键词“微信小程序”“渗透测试”“实战”已经框定了全部范围对象是运行在微信客户端内的小程序方法是模拟攻击者视角的主动探测与验证目标是发现可被利用的业务风险而非单纯堆砌漏洞编号。适合三类人一是甲方安全负责人需要评估外包小程序交付质量二是乙方渗透工程师正面临客户要求出具小程序专项报告三是小程序开发者想在提测前自己先过一遍高危雷区。这篇文章不讲 OWASP Mobile Top 10 的理论映射不列十种工具命令参数而是从一次真实交付项目出发还原我如何用48小时完成一家本地生活平台小程序的深度渗透并把关键路径、判断依据、绕过技巧、修复建议全部摊开写清楚——包括那些文档里不会写、但实操中90%的人会踩的坑。2. 小程序渗透的四大不可绕过战场从包结构到云函数传统 Web 渗透的“信息收集→漏洞扫描→权限提升→横向移动”流程在小程序场景下必须重构。微信小程序没有传统意义上的“域名资产”没有可爬取的 robots.txt也没有公开的源码仓库。它的入口是.wxapkg包出口是wx.request调用的后端接口中间夹着微信运行时、WXML 渲染引擎、Storage 本地存储、云开发环境等多层抽象。我把整个渗透过程拆解为四个物理上分离、逻辑上强耦合的战场缺一不可2.1 战场一反编译.wxapkg包——看清你亲手埋下的“明文密码”小程序上线前会被微信编译为.wxapkg格式这是一种经过简单异或加密的二进制包密钥固定为0x68, 0x32, 0x31, 0x31。它不像 APK 或 IPA 那样有成熟逆向生态但恰恰因为工具链不统一很多团队误以为“微信加密了就安全”。实则不然。我常用的是wxappUnpackerGitHub 开源Python 编写配合手动 patch 微信开发者工具的appservice.js来提取未压缩的 WXML/WXSS/JS。但重点从来不是“能不能解”而是“解出来后看什么”。新手常犯的错误是解包后直奔app.js找wx.request却忽略三个致命位置project.config.json中的appid和description字段前者是小程序唯一标识后者常被开发填成“测试环境-勿删”直接暴露测试分支sitemap.json文件若配置action: allow且包含未上线页面路径如/pages/test/debug等于主动开放后门lib/目录下的第三方 SDK JS 文件比如某地图 SDK 的map.min.js里硬编码了调试用的debug: true和api_key该 key 在其官网控制台未开启 IP 白名单。提示解包后务必用grep -r http\|https\|key\|secret\|token ./全局搜索。我曾在一个政务小程序里搜出const API_BASE https://dev-api.gov-test.cn——这个域名解析指向一台裸奔的 Nginx 服务器连 HTTPS 都没配所有请求明文传输。2.2 战场二WXML/WXSS 层的“渲染劫持”——当view变成scriptWXML 不是 HTML它没有script标签不能执行任意 JS但它的数据绑定机制{{ }}和事件绑定bindtap存在独特的上下文逃逸风险。最典型的是wx:for循环中动态拼接字符串导致的 XSS。例如某社区小程序的评论列表页view wx:for{{comments}} wx:keyid text{{item.content}}/text /view后端返回的content字段未做 HTML 实体转义攻击者提交一条评论{content: img srcx onerroralert(1)}在真机上这段代码不会执行WXML 渲染器会过滤但在微信开发者工具的“预览模式”下部分旧版本会触发onerror。更隐蔽的是wx:if条件判断中的逻辑漏洞view wx:if{{userInfo.isAdmin userInfo.token.length 10}} button bindtapdeleteUser删除用户/button /view如果userInfo是从wx.getStorageSync(user)读取的而该 Storage 可被wx.setStorage任意篡改那么只要本地存入{isAdmin: true, token: a.repeat(11)}就能在未登录状态下触发管理按钮。注意WXML 层的漏洞往往无法通过 Burp Suite 捕获因为它发生在客户端渲染阶段。必须用真机 微信开发者工具的“调试器”面板手动修改Page.data触发重渲染观察 DOM 变化。这是纯 Web 渗透工程师最容易忽略的一环。2.3 战场三wx.request通信链路——你以为的 HTTPS其实是“HTTPS明文 token”小程序强制要求wx.request使用 HTTPS但这只是传输层加密。真正的风险藏在请求头和参数里。我统计过近50个小程序的wx.request调用83% 存在以下至少一种问题Token 硬编码在 JS 中header: { Authorization: Bearer xxxxx }xxx 是固定字符串Sign 算法可预测sign md5(timestamp secretKey)而secretKey写死在 JS 里敏感参数放 GET 请求/api/user?uid123tokenabctoken 被浏览器历史、代理日志、CDN 缓存记录。最危险的是“双 token”模式小程序用wx.login()获取 code传给后端换取 session_key再由后端生成一个业务 token 返回给前端。但很多后端为了省事直接把session_key当作业务 token 返回。而session_key是微信颁发的、用于解密用户敏感数据如手机号的密钥——一旦泄露攻击者可用它解密任意用户wx.getUserInfo()加密数据。验证方法很简单抓包wx.request找所有带Authorization、X-Token、sign字段的请求用 Burp Repeater 修改token值为aaa看响应是否返回401 Unauthorized还是200 OK但数据为空。如果是后者说明后端只校验 token 格式不校验有效性。2.4 战场四云开发环境——当“免运维”变成“免防护”微信云开发CloudBase让开发者不用搭服务器但它的默认配置极不安全。我在渗透一个教育类小程序时发现其云函数getCourseList的数据库查询语句是const db cloud.database(); return db.collection(courses).where({}).get();where({})表示无条件查询且该云函数未设置任何调用白名单。任何人只要知道云函数名和环境 ID就能用wx.cloud.callFunction直接调用拉取全部课程数据含售价、课时、教师联系方式。云开发的三大雷区必须逐个排查数据库集合权限检查cloudBase控制台 → 数据库 → 集合 → 权限设置。read权限若设为“所有用户可读”等于把数据表挂到公网云函数触发方式callFunction默认开放但可通过login: true强制校验登录态。若函数内未调用cloud.auth().getUserInfo()则登录态形同虚设云存储文件 ACL上传的图片、PDF 若设为“公有读”URL 可被任意构造https://xxx.file.myqcloud.com/xxx.pdf无需鉴权。实操技巧用curl直接调用云函数验证权限。例如curl -X POST https://xxx.tcloudbase.com/functions/getCourseList \ -H Content-Type: application/json \ -d {env:prod}若返回完整课程列表立刻标红——这是最高危的业务逻辑漏洞。3. 从报错堆栈反推根因一次登录态绕过的完整排查链路渗透中最耗时的不是找漏洞而是确认“这到底是不是漏洞”。我以一次真实的登录态绕过为例还原完整的推理链条。目标小程序是某连锁健身房的会员系统核心需求是未登录用户只能查看门店列表登录后才能预约课程。3.1 第一步定位可疑请求——为什么“未登录”却能调用预约接口在开发者工具 Network 面板中我筛选wx.request发现一个名为/api/v1/reserve的 POST 请求。点击查看详情Request URL:https://api.fitclub.com/api/v1/reserveRequest Method: POSTRequest Headers:Content-Type: application/json X-App-Version: 2.3.1 X-Device-ID: abc123Request Payload:{course_id: c001, user_id: u123}关键点来了这个请求没有携带任何 token 或 session 字段但响应却是200 OK返回{status: success, reserve_id: r789}。这明显违背业务逻辑——未登录用户怎么会有user_id3.2 第二步验证是否前端伪造——检查user_id来源我在 Sources 面板搜索reserve找到调用该接口的 JS 文件// pages/course/detail.js reserveCourse() { const userId wx.getStorageSync(user_id) || guest; wx.request({ url: https://api.fitclub.com/api/v1/reserve, method: POST, data: { course_id: this.data.course.id, user_id: userId }, success: res { /* ... */ } }); }user_id来自wx.getStorageSync而wx.setStorageSync(user_id, ...)发生在登录成功后的回调里。但问题在于wx.getStorageSync是同步读取且 Storage 数据可被任意篡改。我手动在 Console 输入wx.setStorageSync(user_id, u999); // 伪造一个有效 user_id然后点击预约按钮请求发出响应仍是200。说明后端根本没校验user_id的合法性。3.3 第三步深挖后端校验逻辑——为什么user_id没被验证我尝试修改user_id为随机字符串{course_id: c001, user_id: hacker123}响应变为400 Bad Request提示{error: invalid user_id format}。这说明后端做了格式校验比如必须是u开头3位数字但没查数据库确认该用户是否存在、是否已登录、是否有预约权限。接着我抓包登录请求/api/v1/login发现它返回{ token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..., user_id: u123, is_vip: true }但reserve接口既不校验token也不校验is_vip字段。我构造一个新请求POST /api/v1/reserve HTTP/1.1 Host: api.fitclub.com Content-Type: application/json {course_id: c001, user_id: u123}依然200。至此漏洞确认后端完全信任前端传来的user_id未做任何会话状态关联校验。3.4 第四步验证影响范围——这漏洞能干啥我用 Python 脚本批量发送请求import requests for uid in [u001, u002, u003]: r requests.post(https://api.fitclub.com/api/v1/reserve, json{course_id: c001, user_id: uid}) print(f{uid}: {r.status_code} - {r.text})结果所有uXXX用户都成功预约了同一节课程。这意味着攻击者可编写脚本用已知的用户 ID比如从公开的教练介绍页扒出的u101,u102批量占座导致真实用户无法预约。踩坑心得很多团队认为“我们用了 token所以安全”但 token 必须和业务操作强绑定。这个案例中reserve接口应该校验Authorization: Bearer token并用该 token 查询用户会话确认其user_id、is_vip、remaining_reserves等字段而不是相信前端传来的任意user_id。4. 工具链精简主义三把刀砍穿90%的小程序漏洞我不推荐初学者一上来就装十几款工具。小程序渗透的工具链必须满足两个条件能跑在 Windows/macOS 上不依赖 Linux、能处理微信特有的包格式、输出结果可直接用于修复。我日常只用三款每款解决一个核心问题且全部开源免费4.1 刀一wxappUnpacker—— 解包不是目的是看清 JS 里的“硬编码”安装pip install wxappUnpacker需 Python 3.7使用python unpacker.py app-wx8f2b1a2c3d4e5f67.wxapkg关键输出解压后得到miniprogram/app.js、miniprogram/pages/login/login.js等文件。此时不要急着扫漏洞先做三件事grep -n https\|http miniprogram/**/*.js | grep -v https://api.weixin.qq.com—— 找出所有自定义 API 域名grep -n key\|secret\|token miniprogram/**/*.js—— 找出所有疑似密钥的变量grep -n wx.setStorage\|wx.getStorageSync miniprogram/**/*.js—— 定位所有本地存储操作点。经验wxappUnpacker对新版.wxapkg微信开发者工具 3.0支持不稳定。若解包失败可改用wechat-miniprogram-unpackerNode.js 版命令为npx wechat-miniprogram-unpacker app.wxapkg。但无论用哪个核心动作永远是“文本搜索”不是“图形化分析”。4.2 刀二Burp Suite WeChat Proxy 插件 —— 抓包要抓“微信进程”的包小程序的网络请求不走系统代理必须用 Burp 的WeChat Proxy插件GitHub 搜索burp-wechat-proxy。它的原理是在手机上安装一个特殊证书然后将微信的 DNS 请求劫持到 Burp 监听的端口。配置要点手机 Wi-Fi 代理设为 Burp 所在机器 IP 端口默认 8080微信内打开任意小程序插件会自动捕获wx.request流量关键过滤在 Burp Proxy → HTTP history 中右键 →Filter by extension→ 只留api、v1、cloud等关键词。必做操作对每个POST请求右键 →Send to Repeater修改data字段测试参数污染对每个带Authorization的请求复制 token用jwt.io解码看是否含敏感字段如user_id,role对每个GET请求把?id123改成?id124看是否返回其他用户数据IDOR 漏洞。注意WeChat Proxy 插件在 iOS 上需额外配置描述文件安卓更稳定。若手机抓不到包立刻换用“电脑版微信开发者工具 Fiddler”方案在开发者工具设置 → 代理 → 启用Fiddler 监听127.0.0.1:8888。4.3 刀三sqlmap 自定义脚本 —— 当wx.request参数变成 SQL 注入入口小程序后端若用 PHP/Java 写且对wx.request的id参数做过滤不严就会产生 SQL 注入。但sqlmap默认不识别微信小程序的请求头需定制。我写了一个wechat-sqlmap.py脚本基于sqlmapapi# 从 Burp 导出 request.raw 文件 # 调用 sqlmapapi 创建任务 import requests import json task_id requests.get(http://127.0.0.1:8775/task/new).json()[taskid] requests.post(fhttp://127.0.0.1:8775/scan/{task_id}/start, json{ url: https://api.xxx.com/api/v1/user?id1, headers: X-App-Version: 2.3.1\r\nX-Device-ID: abc123, level: 3, risk: 3 })适用场景当wx.request的 GET 参数如?id1或 POST JSON 字段如{id: 1}直接拼接到 SQL 查询中时验证技巧手工测试比sqlmap更快。在 Burp Repeater 中把id1改成id1 AND 11看响应是否和id1一致再改成id1 AND 12看是否返回空或报错。实战提醒SQL 注入在小程序中虽不如 Web 常见但一旦存在危害极大。因为小程序后端常复用 PC 端 API而 PC 端可能已有注入漏洞只是前端做了限制。渗透时一定要把小程序当作独立的“新客户端”重新测试所有参数。5. 修复建议不是“加 token”而是“重建信任链”渗透报告的价值不在罗列漏洞而在给出可落地的修复路径。我拒绝写“建议加强输入校验”这种废话而是针对每个漏洞类型给出具体到代码行的修改方案5.1 针对user_id未校验问题用 Token 换取 Session而非信任前端原后端代码Node.js// reserve.js app.post(/api/v1/reserve, (req, res) { const { course_id, user_id } req.body; // ❌ 直接取前端传的 user_id db.query(INSERT INTO reserves SET ?, { course_id, user_id }); res.json({ status: success }); });修复方案前端登录后后端生成 JWT tokenpayload 包含user_id、exp、iatreserve接口强制校验Authorization头// middleware/auth.js const jwt require(jsonwebtoken); module.exports (req, res, next) { const auth req.headers.authorization; if (!auth || !auth.startsWith(Bearer )) return res.status(401).json({ error: No token }); try { const decoded jwt.verify(auth.split( )[1], process.env.JWT_SECRET); req.user decoded; // ✅ 将可信 user_id 注入 req next(); } catch (e) { res.status(401).json({ error: Invalid token }); } };reserve接口改用req.user.user_id// reserve.js app.post(/api/v1/reserve, authMiddleware, (req, res) { const { course_id } req.body; const { user_id } req.user; // ✅ 来自 token非前端输入 db.query(INSERT INTO reserves SET ?, { course_id, user_id }); res.json({ status: success }); });5.2 针对云函数无权限问题用cloud.auth().getUserInfo()强制鉴权原云函数// cloud/functions/getCourseList/index.js exports.main async (event, context) { const db cloud.database(); return db.collection(courses).where({}).get(); // ❌ 无任何校验 };修复方案// cloud/functions/getCourseList/index.js exports.main async (event, context) { try { const wxContext cloud.getWXContext(); // ✅ 强制校验登录态 const auth await cloud.auth().getUserInfo(); if (!auth.openId) throw new Error(Not logged in); const db cloud.database(); // ✅ 仅返回该用户有权限的课程 return db.collection(courses) .where({ status: published }) .get(); } catch (e) { console.error(e); return { error: Access denied }; } };5.3 针对 WXML 渲染劫持用wx:if替代hidden用setData替代直接赋值原 WXML危险!-- ❌ hidden 只是 CSS 隐藏DOM 仍存在 -- view hidden{{!userInfo.isAdmin}} button bindtapdeleteUser删除用户/button /view修复方案!-- ✅ wx:if 彻底移除 DOM -- view wx:if{{userInfo.isAdmin userInfo.isLogin}} button bindtapdeleteUser删除用户/button /view并在 JS 中确保userInfo从服务端获取而非本地 Storage// pages/admin/index.js onLoad() { // ✅ 从云函数获取带登录态校验 wx.cloud.callFunction({ name: getAdminInfo, success: res { this.setData({ userInfo: res.result.data }); } }); }最后分享一个小技巧每次渗透完成后我会用 Excel 整理一份《高频风险自查清单》发给开发团队。清单只有三列“风险点”如“WXML 中使用 hidden 控制敏感按钮”、“检测方法”“搜索hidden并检查绑定变量来源”、“修复代码示例”贴上上面那段wx:if代码。这样他们下次自测时不用翻文档直接照着改就行。这比写一百页 PDF 报告管用得多。