记录一次小程序挖洞经历,感谢小程序,不然这单得黄!!!
原创
是老A
是老A
老A搞安全
2026年7月23日 07:58
贵州
在小说阅读器读本章
去阅读
一位深耕网络安全的老兵,目前是一家安全公司的技术分管,擅长渗透测试及安全培训方向,老A的愿望是大家没烦恼,一切顺心!
接了个电商企业的授权项目,客户给的范围是\"尽量测,别打挂就行\"。Web端搞了一圈,WAF+云防护,基本没什么缝
这下不好交差了……/笑哭
客户说:\"我们小程序应该没啥问题,就几个商品展示页面。\"
好好好(心想,小程序争取搞个交差咯)
确实!
小程序往往比Web站更容易出问题,因为开发资源都堆在前端UI上了,后端逻辑经常是\"能跑就行\"。
打开微信开发者工具,连上Burp的代理,开始抓包
所有请求都指向一个域名:
https://xxx.cloudfunctions.net/(示例,数据已脱敏)
云函数,微信小程序的云开发环境
这说明整个小程序的后端逻辑都跑在云函数上,没有自己的服务器。好处是开发快,坏处是,如果你在云函数里写了什么敏感逻辑,或者依赖前端传什么参数来做鉴权,那就等于把保险柜的密码贴在了保险柜外面
但是第一步就卡住了。我只知道一个根域名,不知道有哪些云函数路径,也没地方去看代码。总不能盲猜吧?
虽然有时候真的能猜中,但那是撞大运,不是正经打法/笑哭
于是开始搞小程序源码
流程是这样的,拿了一台安卓手机,从微信的缓存目录里把小程序的wxapkg包扒出来。路径大概是:
/data/data/com.tencent.mm/MicroMsg/[32位hash]/appbrand/pkg/
微信会把运行过的小程序包缓存到这里,直接复制出来就行。然后用到wxappUnpacker解包:
node wuWxapkg.js xxx.wxapkg
解出来一堆文件,js、json、wxml混在一起。大部分js都被混淆过,变量名全是_、a、b这种,读起来头疼。但好消息是,字符串常量是明文的,不会被混淆
我开始一个文件一个文件地翻。在app-service.js里搜admin,在cloudfunctions.js里搜openid,心里默念:让我看到一个硬编码的openid,求求了,不然真不好交差了,总不能说,领导,你们的系统\"灰常安全\"吧
在一个叫adminConfig.js的文件里,我看到了这样的代码
const ADMIN_LIST = { 'oXXXXXXXXXXXXXXXXXXXXXXXXXXX': '超级管理员', 'oYYYYYYYYYYYYYYYYYYYYYYYYYYY': '运营管理员'};if (ADMIN_LIST[event.openid]) { return await getAllUsers();}
写死了。管理员的openid直接硬编码在前端代码里。我盯着屏幕,脑子里有一个念头:\"这跟把管理员密码贴在登录框下面有什么区别?\"
更离谱的是,云函数接收openid的方式是这样的:
exports.main = async (event, context) => { const { openid } = event; // 直接用了前端传过来的openid去做权限判断}
没有任何二次校验。意味着什么?意味着服务端完全信任了客户端传来的openid。你传什么,它就认什么
而且别忘了,小程序是跑在用户手机上的。任何人都可以反编译,拿到这些硬编码的openid。然后伪造请求,以管理员的身份调用任何云函数
我当时的第一反应是:赶紧翻一翻还有哪些云函数
在一个叫cloudfunctionMap.js的文件里,找到了所有云函数的映射表,整整写了二十多个:
module.exports = { getAllUsers: 'getAllUsers_prod_v2', getOrders: 'getOrders_prod_v2', getUserInfo: 'getUserInfo_prod_v2', getSensitiveLogs: 'getSensitiveLogs_prod_v2', batchExport: 'batchExport_prod_v2', // ... 还有十几个}
我打开了Postman
先试getAllUsers_prod_v2:
POST https://xxx.cloudfunctions.net/getAllUsers_prod_v2{ \"openid\": \"oXXXXXXXXXXXXXXXXXXXXXXXXXXX\"}
返回了千多条用户数据。手机号、收货地址、订单记录、积分余额,全都有。我随手翻了几条,有个用户的收货地址精确到了门牌号,还有个用户半年下了26单
再试getSensitiveLogs_prod_v2:
POST https://xxx.cloudfunctions.net/getSensitiveLogs_prod_v2{ \"openid\": \"oXXXXXXXXXXXXXXXXXXXXXXXXXXX\"}
返回了管理员的操作日志,里面包含了内部系统的接口地址和参数格式
再试batchExport_prod_v2:
POST https://xxx.cloudfunctions.net/batchExport_prod_v2{ \"openid\": \"oXXXXXXXXXXXXXXXXXXXXXXXXXXX\", \"type\": \"all_users\"}
返回了一个下载链接,点开就是全量用户数据的Excel。我当时甚至有点心虚,这要是客户看到他们小程序后台长这样,怕是要连夜开除整个技术团队
后面和运维团队确认了漏洞,一起分析了漏洞是怎么造成的
云函数的鉴权逻辑分两层:
第一层:微信自带的身份认证。微信会在请求里带上调用者的openid,这是可信的,伪造不了
第二层:开发者自己的鉴权逻辑。开发者从请求里取出openid,判断它是不是管理员
问题出在哪里?出在开发者混淆了这两个来源
正确做法应该是:云函数内部通过wxContext.OPENID或者云函数自带的变量去拿微信传递的openid,这是可信的。但这位开发者用了event.openid——这个字段是前端传进来的,任何人都能伪造
也就是说,他只是\"看起来\"做了鉴权,实际上这个鉴权是空气。你传超级管理员的openid,你就是管理员;你传普通用户的openid,你就是普通用户。一个openid校验,硬生生被玩成了选择题
好好好……
这里补充几个挖小程序云函数的习惯动作:
1、优先看app-service.js。这是小程序的业务逻辑大本营,虽然被混淆,但关键词搜索依然有效。变量名变了,字符串常量不会变。
2、搜关键词的顺序:先搜admin(权限判断的核心)、再搜cloud(调用云函数的入口)、然后搜openid(硬编码的管理员标识)、最后搜云函数的名字(getAllUsers、getOrders、batchExport这种)
3、碰到硬编码的openid,不要只试一个。把所有硬编码的管理员openid都找出来,然后用Burp Intruder批量去调所有云函数。经常能发现不同的openid对应不同的权限等级
4、云函数的命名规律:很多开发者喜欢加后缀区分环境,_prod、_v2、_test是常见的。如果你找到了一个getAllUsers_prod,大概率也存在一个getAllUsers_prod_v2
5、没找到源码怎么办?有些小程序做了加固,解包解不出来。这时候走抓包路线,把所有云函数的请求都抓下来,观察URL的命名规律,然后自己拼路径盲测。虽然效率低,但也常有意外收获
6、别忘了云存储:很多小程序会把图片、配置文件传到云存储里,如果云存储的权限配置是public,直接就能遍历下载
收工收工!!!
感谢小程序,不然这单得黄
⚠️ 法律声明:以上内容仅供安全学习和授权测试使用。未经授权的漏洞利用属于违法行为,请勿对未经授权的目标进行测试。
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论