0

    渗透测试之微信小程序那些事儿

    2023.07.30 | admin | 196次围观

    0x00 简介

    当你面对一个目标束手无策的时候,不放试试小程序,可能会有意想不到的惊喜。

    0x01 资产搜集--获取子域名

    (1)在日常生活中,我们可以使用微信自带的小程序搜索功能轻松的找到我们想要的小程序。在搜索结果中,微信会依次判断小程序的 “名称”、“简介”、“开发者”中是否含有用户所搜索的关键字,并按照匹配度返回相关的小程序。通过一个一个搜索的方式来寻找目标小程序,未免效率太低。在渗透测试中我们可以使用脚本批量高效的获取小程序的搜索结果。

    首先,我们进入微信小程序搜索界面【发现-小程序】抓去数据包并分析。

    构造脚本如下:

    #!/usr/bin/env python
    # -*- encoding: utf-8 -*-
    import requests,json,sys
    def Get_Apps(query,number,cookie):
        headers={"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/7.0.11(0x17000b21) NetType/WIFI Language/fr"}
        url = "https://mp.weixin.qq.com/wxa-cgi/innersearch/subsearch"
        params = "query=" + query + "&cookie=" + cookie + '&subsys_type=1&offset_buf={"page_param":[{"subsys_type":1,"server_offset":0,"server_limit":' + str(int(number)+30) + ',"index_step":' + number + ',"index_offset":0}],"client_offset":0,"client_limit":' + number + '}'
        response = requests.post(url=url, params=params, headers=headers).text
        Apps_Json = json.loads(response)
        App_Items = Apps_Json['respBody']['items']
        for App_Item in App_Items:
            App_Item_Json = json.loads(json.dumps(App_Item)) #重新加载嵌套内容中的json数据
            App_Id = App_Item_Json['appid']
            App_Name = App_Item_Json['nickName']
            App_Id_List.append(App_Id)
            App_Name_List.append(App_Name)
    if __name__ == '__main__':
        reload(sys)
        sys.setdefaultencoding('utf-8') #解决编码问题
        query = raw_input("请输入要搜的微信小程序名称: ")
        number = raw_input("请指定要返回的小程序的数量: ")
        cookie = raw_input("请输入你获取到的Cookie信息: ")
        App_Id_List = []
        App_Name_List = []
        try:
            Get_Apps(query,number,cookie)
            print "返回的小程序名: " + ",".join(App_Name_List)
            print "返回的小程序ID: " + ",".join(App_Id_List)
        except:
            print "信息获取失败,请检查!"
    

    (2)如何通过微信自带接口获取目标有用信息?每一个小程序展示页内都有“更多资料”这个功能,其中含有“开发者”、“服务及数据网址”等实用信息,下面随便找个小程序进行举例操作:

    我们在 更多资料 处抓取数据包,获取数据包中的X_WECHAT_KEY、X_WECHAT_KEY,利用如下脚本可以批量获取小程序域名接口资产。

    X-WECHAT-UIN和X-WECHAT-KEY这两个参数用来置换cookie,这两个参数是微信那边生成的目前不知道算法而且存在过期时间。

    #!/usr/bin/env python
    # -*- encoding: utf-8 -*-
    import requests,time
    def Get_Domain(X_APP_ID,X_WECHAT_KEY,X_WECHAT_UIN):
        headers={
        "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/7.0.11(0x17000b21) NetType/WIFI Language/fr",
        "X-WECHAT-KEY": X_WECHAT_KEY,
        "X-WECHAT-UIN": X_WECHAT_KEY #微信两个校验值
        }
        url = "https://mp.weixin.qq.com/mp/waverifyinfo"
        params = "action=get&wx_header=1&appid=" + X_APP_ID
        response = requests.get(url=url, params=params, headers=headers).text
        Response_domain_list =  Get_MiddleStr(response,"request_domain_list","request_domain_list.splice")
        Response_domain_list = Get_MiddleStr(Response_domain_list,"= ",";")
        exec("Domain_list.extend(" + Response_domain_list + ")") #添加list数组
        time.sleep(8) #防止访问频繁,自己调节
    def Get_MiddleStr(content,startStr,endStr): #获取中间字符串的一个通用函数
        startIndex = content.index(startStr)
        if startIndex>=0:
            startIndex += len(startStr)
        endIndex = content.index(endStr)
        return content[startIndex:endIndex]
    if __name__ == '__main__':
        X_APP_IDS = raw_input("请输入小程序ID(逗号分隔): ")
        X_WECHAT_UIN = raw_input("请输入自己的X-WECHAT-UIN: ")
        X_WECHAT_KEY = raw_input("请输入自己的X-WECHAT-KEY: ")
        X_APPID_LIST = X_APP_IDS.split(",")
        Domain_list = []
        for X_APP_ID in X_APPID_LIST:
            try:
                Get_Domain(X_APP_ID,X_WECHAT_KEY,X_WECHAT_UIN)
            except:
                print X_APP_ID + "的信息获取失败,请检查!"
        Domain_list = list(set(Domain_list)) #list数组去重
        Domain_list = filter(None,Domain_list) #list数组去空
        print "收集到的域名: " + str(Domain_list)
    

    0x02 微信小程序源代码提取 & 分析

    由于手机权限的原因,我们使用安卓模拟器(夜神)登录微信提取微信小程序包。(微信对于Mac和Windows的小程序包都做了不同程度的加密,所以现在从安卓/iOS系统中提取小程序更为方便。)

    安卓保存路径:/data/data/com.tencent.mm/MicroMsg/{用户ID}/appbrand/pkg/
    iOS保存路径:/var/mobile/Containers/Data/Application/{程序UUID}/Library/WechatPrivate/{用户ID}/WeApp/LocalCache/release/{小程序ID}/
    

    复制三个文件到夜神模拟器共享目录中

    模拟器路径:/mnt/shared/Other

    Mac OS路径:/Users/apple/Library/ApplicationSupport/NoxAppPlayer/Nox_share/Other可成功将小程序包从模拟器提取至电脑。

    使用小程序逆向还原工具(依赖node.js)进行解包。

    命令:node wuWxapkg.js -o -d 目标文件.wxapkg
    

    还原后的文件形式大致如下:

    有经验的会发现,解压出来的文件比小程序开发时的原项目文件少了许多文件微信小测试h5源代码,这是因为微信服务器会将小程序源码中所有的js文件压入app-service.js文件中,将所有的json文件压入app-config.json中,将所有的wxml文件压入page-frame.html文件中,wxss则在处理之后以html文件的形式存留在对应页面目录之下。

    获取到小程序源码之后,假如小程序在与后端服务器进行了加密传输微信小测试h5源代码,可以根据传输中的加密参数值进行跟踪,逐步分析参数是进行什么样的加密方式。知道了整个加密过程,整个小程序对我们来说就是形同没有做任何加密似的。因此可以进行其他漏洞的测试,如尝试进行越权查询、账号爆破等漏洞的测试。

    0x03 获取本地微信Mac客户端聊天记录

    微信聊天记录储存在~/Library/Containers/com.tencent.xinWeChat/Data/Application Support/com.tencent.xinWeChat/{微信版本号}/{用户ID}/Message/目录下,文件名命名方式为msg_{数字}.db他是一个使用SQLite 3(SQLCipher)的加密数据库。

    其解密密码提取方式如下,这里我们需要用到Xcode自带的LLDB调试器:

    (1)打开微信Mac版进入登录界面但不要登录;

    (2)打开终端输入lldb -p $(pgrep WeChat)进入LLDB调试器开始调试微信客户端;

    (3)使用breakpoint set --name sqlite3_key命令在微信客户端调用数据库解密函数上下断点;

    (4)使用breakpoint list命令看到已经成功下了两处断点;

    (5)输入continue命令让微信接着运行;接着便可以执行微信登录操作,可以看到成功触发断点;

    (6)输入memory read --size 32 --format x --count 1 $rsi提取内存中的解密密码;

    (7)于是我们可以得到的类似“0x6000028a6c00: 0x1e2233159e583bbe1d46805c4d9bd9ff0817851003e929af05474f84e769bc1d”的内容,我们需要将数据使用Python做如下处理:

    key = "获取到的值"
    print("0x" + "".join(list(reversed([key.partition(":")[2].replace(" ",  "").replace("0x", "")[i:i+2] for i in xrange(0, len(key)-18, 2)]))))
    例如最终输出:0x1dbc69e7844f4705af29e90310851708ffd99b4d5c80461dbe3b89e1533221e
    

    (8)此时可以使用exit命令退出LLDB调试器,让微信正常运行。使用DB Browser for SQLite输入刚刚获取到的raw_key,成功打开微信Mac客户端的本地聊天数据库。

    0x04 CMRF漏洞(跨小程序请求伪造)4.1 攻击流程:

    (1)获取小程序的源码 - 分析小程序功能点源码 - 存在修改密码功能

    (2)获取本地微信聊天记录数据库 - 修改数据表msg内容 - 自定义修改后的密码

    (3)导入修改后的本地微信聊天数据库 - 转发小程序,造成CMRF- 成功修改成自己构造的密码。

    危害与CSRF类似,修改密码、添加恶意信息等。

    4.2 代码审计小技巧:

    有CMRF漏洞存在的小程序页面,其页面必然有被页面跳转功能的函数所引用过,那么只要在项目中找到哪些代码片段引用了这些函数便可快速判定是否有CMRF漏洞的存在。微信小程序JS文件中有如下三个可切换页面的函数:

    wx.reLaunch — 关闭所有页面,打开到应用内的某个页面
    wx.navigateTo — 保留当前页面,跳转到应用内的某个页面
    wx.redirectTo — 关闭当前页面,跳转到应用内的某个页面
    

    0x05 小程序的安全防御

    源码安全:使用代码混淆和类似Webpack打包工具。在发布小程序时在设置中勾选“上传代码时自动压缩混淆”和“上传时进行代码保护”。

    接口安全:推荐使用AES+RSA的方式对数据进行加密,并且结合时间戳对数据包进行签名。

    后台安全:绝不使用弱口令;及时升级插件;后台地址不对外访问。

    平台安全:微信开发平台账号的密码定期更换。

    0x06 总结

    以上分析都是站在Poc Sir大佬的肩膀上进行的学习操作,感兴趣的可以自己深入研究。

    版权声明

    本文仅代表作者观点。
    本文系作者授权发表,未经许可,不得转载。

    发表评论