微信小程序云开发数据库渗透
一次微信小程序云开发数据库的渗透测试
前言
某日在一个小程序闲逛时,发现有一些请求抓不到,这引起了我的注意,经过一番研究后发现这是微信提供的云开发
能力,包括云函数
和云数据库
等,本文主要是对云数据库
的一个渗透尝试记录,微信小程序渗透基础可以参考这篇微信小程序的渗透五脉,写的很好
微信抓包
Root环境就不说了,我给出一套自己使用的工具组,能解决大部分抓包问题
TrustMeAlready(1.11) + mitmweb(9.0) + MITMProxy cert
这组工具解决了三个问题:
- 单向证书认证
- 抓包数据可视化
- Android6以上应用不再信任用户证书
其中MITMProxy cert可以在启动mitmweb后,手机配置代理后通过mitm.it下载到Magisk模块,直接刷入即可
小程序代码包获取
这时我们已经有了微信的部分抓包能力,这时可以通过在微信里把目标小程序长按-拖动-删除,来删掉这个小程序的缓存,然后就可以再打开目标小程序抓包了,你会发现抓到了一个包含res.servicewechat.com/weapp/release_encrypt/
的链接。当然,也可能是多个,比如小程序分包和微信小程序运行环境基础包
把这个链接复制一下,下载下来就是小程序代码的主包了,但还要经过一些处理,比如图中这个就是zstd格式的,要先解压一下,它就是wxapkg格式了,然后用unveilr解包就可以了
wx.cloud.database() 的出现
在解包的代码中有一段代码如下:
1 | var s = n(56), |
一眼看上去就是类mongodb的collection以及它的where查询语句
一眼我就觉得它可能有问题。众所周知,微信小程序是前端代码,前端中直接查数据库是非常危险的,一般这种情况下都伴随着前端用用户名密码直连数据库,但这里微信包了一层,通过小程序appid绑定的env来在小程序运行环境中使用一些变量,同时微信限制了env的使用权限,只有绑定了该appid的微信账号才能使用对应的env,看起来比前端直连数据库好了很多。但是,我还是觉得有越权查询的风险,当小程序执行 i.where({userId:1}) 时,是不是查询到的就是用户1的信息?接下来就是验证这个想法的过程
wx.cloud.database() 安全性官方介绍
官方文档可以参考 权限控制|微信开放文档
总结一下就是:
微信早期有一个简易权限配置,其有以下几个特点
在小程序中创建的每个数据库记录都会带有该记录创建者(即小程序用户)的信息,以
_openid
字段保存用户的openid
在每个相应用户创建的记录中以下按照权限级别从宽到紧排列如下:
- 仅创建者可写,所有人可读:数据只有创建者可写、所有人可读;比如文章。
- 仅创建者可读写:数据只有创建者可读写,其他用户不可读写;比如用私密相册。
- 仅管理端可写,所有人可读:该数据只有管理端可写,所有人可读;如商品信息。
- 仅管理端可读写:该数据只有管理端可读写;如后台用的不暴露的数据。
微信开发者工具 1.02.1911252 起支持配置安全规则,其在简易权限配置的基础上,进一步精细化的控制权限:
灵活自定义集合记录的读写权限:获得比基础的四种基础权限设置更灵活、强大的读写权限控制,让读写权限控制不再强制依赖于
_openid
字段和用户openid
防止越权访问和越权更新:用户只能获取通过安全规则限制的用户所能获取的内容,越权获取数据将被拒绝
限制新建数据的内容:让新建数据必须符合规则,如可以要求权限标记字段必须为用户
openid
比如定义一个读写访问规则是
auth.openid == doc._openid
,则表示访问时的查询条件(doc
)的openid
必须等于当前用户的openid
(由系统赋值的不可篡改的auth.openid
给出),如果查询条件没有包含这项,则表示尝试越权访问_openid
字段不等于自身的记录,会被后台拒绝访问。在新的安全规则体系下,要求显式传入
openid
,假如以上述checkUser
函数为例子,应用了新安全规则时,该段代码应该改为1
2
3
4
5
6
7checkUser: function () {
var t = this;
i.where({ userId: this.userInfo.id, _openid: '{openid}'})
.get()
.then(function (e) {
e.data.length > 0 && (t.isCheckUser = !0);
});
通过官方的文档介绍,我们可以看出来目标小程序应用的还是简易权限配置
。同时也知道了微信官方对这个前端可以调用的云开发数据库
是有安全防范的,本身的安全性没有问题,那么安全问题就有可能出在开发者身上,比如设置了错误的权限管理规则
如何实现 i.where({userId:1})
?
首先我尝试了微信官方的微信开发者工具
,想通过更改为目标小程序的appid,直接init目标的env,然后写段代码直接调用wx.cloud.database,但不出意外的失败了
然后我打算从微信小程序本身入手,既然我已经有了源代码,那么我可以直接更改这段查询代码,比如改成这样
1 | checkUser: function () { |
这样当小程序执行到这个函数的时候,就会去查UserId为1的数据,然后把结果通过wx.showToast显示出来。为了实现这个目标,我需要把代码重打包,然后替换掉目标小程序的原始包
代码重打包与包体替换
代码重打包很简单,用微信开发者工具
直接导入解包出的代码,然后登录自己的微信账号,修改好代码,点预览
进行编译运行即可,小程序会在手机微信上自动打开。需要注意的是编译有可能会不通过,一般是因为少了代码,可能这个小程序还有分包,需要把分包也下载下来再手动合并进主包目录中
包体替换比较复杂,我尝试了两种方式,但都失败了
- 新编译的包在手机上运行后,去手机的小程序缓存目录(/data/data/com.tencent.mm/MicroMsg/appbrand/pkg/general/)下会看到它的wxapkg包,把这个包重命名替换目标小程序的原始缓存包-> 微信启动小程序时会校验本地缓存的小程序包的hash,当发现被替换后会再去下载一次原版包并覆盖
- zstd压缩新编译的包,用抓包软件替换对应下载链接的返回内容,让微信下载下来的就是我编译过的包-> 用
微信开发者工具
生成的包是debug包,同时zstd的压缩好像也不太对,微信并不认这个包,替换返回内容后微信会报小程序运行环境异常
的错误
这两种方式都有改进的思路,比如方式一可以尝试干掉微信的校验,方式二可以去上架一个正式环境的小程序再抓包替换试试
这些方式都太麻烦了,于是我找到了一个新思路:在微信校验小程序的hash后,打开小程序前这个时间点,新包替换原包。命令如下:
1 | /data/data/com.termux/files/usr/bin/inotifywait -m -e open /data/data/com.tencent.mm/MicroMsg/appbrand/pkg/general/_1718118489_117.wxapkg | awk '{if(++count==2) system("cp /data/local/tmp/11.wxapkg /data/data/com.tencent.mm/MicroMsg/appbrand/pkg/general/_1718118489_117.wxapkg");}' |
当inotify监听到open操作被执行到第二次时就会去替换这个包,可能因为命令执行时间的问题有时没成功,但一般最多试两三次就可以了
代码注入效果
这时已经可以看到我写的这段代码已经生效了,但wx.showToast显示的内容有限
1 | wx.showToast({ |
数据外带方案
本来试了试能不能嵌入一个vConsole,把数据打到console里,但vConsole嵌入后小程序直接白屏了。。。
2023-06-08更新:
换了个方法把数据带出来:
- 本地启动一个WebServer,把POST的数据打印出来
- 小程序内使用wx.request函数把数据POST到WebServer上
最终效果如下
总结
大致过程就是这样了,这个测试的方法实操起来很复杂,但这次测试也算有所收获,希望这篇文章能抛砖引玉,期待大佬们能找到更方便的测试方法