前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >微信小程序的修炼五脉(如意篇上)

微信小程序的修炼五脉(如意篇上)

作者头像
公众号爱国小白帽
发布2021-06-16 15:00:42
2K0
发布2021-06-16 15:00:42
举报
文章被收录于专栏:爱国小白帽的原创专栏

声明

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,雷神众测以及文章作者不为此承担任何责任。 雷神众测拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经雷神众测允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。

No.1

前言

如意如意顺我⼼意,若不能顺我⼼意,我将竭尽全⼒,⽤⾃⼰的技术改变其意。

笔者是⼀位极其“不听话”的⼈,我不想要程序它觉得,我要我⾃⼰觉得;⾃⼰重新定义程序的逻辑以及游戏规则,掌控这其中的数据,让每⼀个环节都顺⾃⼰的⼼意,直到看到⾃⼰想要的结果为⽌。在这篇⽂章中,作者将会带领⼤家寻找微信⼩程序中特有且常⻅的任意⼿机号登录漏洞,并为读者们带来⼀种你从未听说以及利⽤过 并且只有在⼩程序中才会存在的类似于CSRF的漏洞。

此专题由于篇幅较⻓将会拆分为“如意篇 (上)”、“如意篇(下)”两篇⽂章,还请您⻅谅。

No.2

都是sessionkey惹的祸

不知读者们是否发现有些微信⼩程序有“微信⼿机号快捷登录”的功能,轻轻⼀点即会弹出所以保存在微信之中已经被信任的⼿机号,⽆需再接收验证码即可实现⼀键登录已经经过微信验证的⼿机号,⾮常之便捷。

这个功能在微信⼩程序中名为“获取⼿机号”,⽬前只向以中国⼤陆为主体的企业认证账户开放使⽤,并 在越来越多的企业级⼩程序中得到了完美的运⽤,其官⽅说明⽂档如下:

_

获取微信⽤户绑定的⼿机号,需先调⽤wx.login接⼝。 因为需要⽤户主动触发才能发起获取⼿机号接⼝,所以该功能不由 API 来调⽤,需⽤ button 组件 的点击来触发。 使⽤⽅法:需要将 button 组件 open-type 的值设置为 getPhoneNumber ,当⽤户点击并同意之 后,可以通过 bindgetphonenumber 事件回调获取到微信服务器返回的加密数据, 然后在第三 ⽅服务端结合 session_key 以及 app_id 进⾏解密获取⼿机号。

获取微信⽤户绑定的⼿机号,需先调⽤wx.login接⼝。 因为需要⽤户主动触发才能发起获取⼿机号接⼝,所以该功能不由 API 来调⽤,需⽤ button 组件 的点击来触发。 使⽤⽅法:需要将 button 组件 open-type 的值设置为 getPhoneNumber ,当⽤户点击并同意之 后,可以通过 bindgetphonenumber 事件回调获取到微信服务器返回的加密数据, 然后在第三 ⽅服务端结合 session_key 以及 app_id 进⾏解密获取⼿机号。

_

获取微信⽤户绑定的⼿机号,需先调⽤wx.login接⼝。 因为需要⽤户主动触发才能发起获取⼿机号接⼝,所以该功能不由 API 来调⽤,需⽤ button 组件 的点击来触发。 使⽤⽅法:需要将 button 组件 open-type 的值设置为 getPhoneNumber ,当⽤户点击并同意之 后,可以通过 bindgetphonenumber 事件回调获取到微信服务器返回的加密数据, 然后在第三 ⽅服务端结合 session_key 以及 app_id 进⾏解密获取⼿机号。

按照官⽅的开发⼿册,想使⽤“获取⼿机号”功能⾸先须在⼩程序内调⽤“wx.login”接⼝,示例代码如下:

wx.login({

success (res) {

if (res.code) {

//发起⽹络请求

wx.request({

url:'https://demo.c-est.cool/Login', console.log('成功将code传给⼩程序后端!')

data: {

code: res.code }

} else {

console.log('登录失败!' + res.errMsg)

}

}

})

当微信⼩程序运⾏⾄“wx.login”代码处时,会调⽤微信服务器的接⼝返回“code”参数:

_

code:⽤户登录凭证(有效期五分钟)。开发者需要在开发者服务器后台调⽤ auth.code2Session,使⽤ code 换取 openid 和 session_key 等信息

code:⽤户登录凭证(有效期五分钟)。开发者需要在开发者服务器后台调⽤ auth.code2Session,使⽤ code 换取 openid 和 session_key 等信息

_

code:⽤户登录凭证(有效期五分钟)。开发者需要在开发者服务器后台调⽤ auth.code2Session,使⽤ code 换取 openid 和 session_key 等信息

可以将“code”参数理解为⼀次性校验⽤的token,未使⽤的code有5分钟的有效期,⼀但使⽤⽆论调⽤成 功与否,此code便会失效。当⼩程序成功获取到code时,开发者便可使⽤预留代码将对应的code值传 回⼩程序后端服务器以便接下来调⽤“auth.code2Session”接⼝:

_

登录凭证校验(本接⼝应在服务器端调⽤)。通过 wx.login 接⼝获得临时登录凭证 code 后传到 开发者服务器调⽤此接⼝完成登录流程。请求地址:GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET &js_code=JSCODE&grant_type=authorization_code

登录凭证校验(本接⼝应在服务器端调⽤)。通过 wx.login 接⼝获得临时登录凭证 code 后传到 开发者服务器调⽤此接⼝完成登录流程。请求地址:GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET &js_code=JSCODE&grant_type=authorization_code

_

登录凭证校验(本接⼝应在服务器端调⽤)。通过 wx.login 接⼝获得临时登录凭证 code 后传到 开发者服务器调⽤此接⼝完成登录流程。请求地址:GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET &js_code=JSCODE&grant_type=authorization_code

通过使⽤“appId”、“appSecret”和登录时获取到的“code”(js_code)内容来换取:“openid”(⽤户唯⼀ 标识)、“session_key”(会话密钥)等值。如下图所示,可以看到微信拥有⼀套⾮常成熟的⼩程序登录流程:

在这此流程中“session_key”起到了⾄关重要的作⽤,只要能获取此key的内容,便可以控制回调凭证内容,使整套登录安全体系破产。咋们的微信爸爸(滴,5Q币到账)肯定考虑到了这个问题,所以给出了如下提醒:

1.开发者服务器可以根据⽤户标识来⽣成⾃定义登录态,⽤于后续业务逻辑中前后端交互时识别⽤户身份;

2.会话密钥 session_key 是对⽤户数据进⾏ 加密签名的密钥。为了应⽤⾃身的数据安全,开发者 服务器不应该把会话密钥下发到⼩程序,也不应该对外提供这个密钥。

总结⼀下就是:从微信这边获取到的“session_key”打死也不能直接返回给⽤户,但你实在要返回⽤于校 验⽤户身份的话我也拦不住你,但求求你⾏⾏好使⽤⾃⼰⽣成的第三⽅key返回吧,将“session_key”和 ⾃⼰⽣成的key在数据库中做个关联就⾏。

然⽽,这毕竟只是个提醒,总会有开发者“看不⻅”?或者不听取的,你懂的。接着我们回到“获取⼿机号”功能,在顺利调⽤“wx.login”和“auth.code2Session”接⼝之后便可轻松得到 加密之后的⽤户⼿机号数据和加密使⽤的初始向量(iv),将这些数据传递给微信⼩程序后端并使 ⽤“session_key”作为解密key可得到如下json数据:

(本段删除)最后我们来到⼩程序包的数据段,数据段的构造⾮常简单仅有“内容块”这么⼀个数据块,⾥⾯储存了每⼀个索引段中存在索引的⽂件的实际内容。

{

"phoneNumber": "+33711123333", "purePhoneNumber": "711123333", "countryCode": "33",

"watermark":

{

"appid":"APPID",

"timestamp": TIMESTAMP

}

}

♥phoneNumber:⽤户绑定的⼿机号(国外⼿机号会有区号);

♥purePhoneNumber:没有区号的⼿机号;♥watermark:内含对应⼩程序的appid和数据加密时的时间戳。

可以看到这些参数都是可以轻易伪造的,并且⼩程序后端并不会对得到的⼿机号做⼆次校验例如短信验 证码校验(不然这个快捷登录功能便失去了意义),故只要“session_key”泄露我们便可以伪造登录凭证 实现任意⼿机号登录。

那便先来聊⼀聊⼩程序数据的加解密吧:微信使⽤的是AES加密,CBC模式采⽤PKCS7填充,数据块⻓ 度为128位,输出使⽤base64编码。AES是⽤于替代DES加密的对称加密算法,对称加密算法最⼤的特 性便是加、解密使⽤的同是⼀串密钥,只要获取到key便可加解⾃如(CBC加密模式下还需要 iv 偏移量参数,⼀般固定不变或作为变量内容传输)

上图为微信服务器加密⽤户开放数据的具体流程,使⽤的加密key与“session_key”为同⼀个,偏移量iv 则直接与加密后的数据⼀起明⽂返回给了⼩程序后端。有细⼼的读者可能会看到此流程中有⼀个⽤于防 ⽌内容被篡改的签名过程,这个签名仅在“获取⽤户信息”功能中才会⽣成,在“获取⼿机号”功能中并⽆, 但这并不是微信的漏洞,因为签名是采⽤ sha1( rawData + sessionkey ) ⽅式⽣成的,若 session_key 泄露则⼀切验证签名的操作均是⽆济于事。

根据微信⼩程序对数据加密的⽅式,我们可以写出如下数据解密脚本:

<?php

echo "请输⼊SessionKey: ";

$sessionKey = fgets(STDIN);

echo "请输⼊本次加密IV: ";

$iv = fgets(STDIN);

echo "请输⼊待解密内容: ";

$encryptedData = fgets(STDIN);

function decryptData( encryptedData, iv, sessionKey ) { aesIV = base64_decode(iv); aesCipher = base64_decode(

aesKey = base64_decode(sessionKey); result = openssl_decrypt(aesCipher, "AES-128-CBC", aesKey, 1, aesIV); dataObj = json_decode(result);

return $result;

}

result = decryptData(encryptedData, iv, sessionKey);

echo sprintf("最终的解密结果为: %s\n", $result);

以及对应的数据加密脚本:

<?php

echo "请输⼊SessionKey: ";

$sessionKey = fgets(STDIN);

echo "请输⼊本次解密IV: ";

$iv = fgets(STDIN);

echo "请输⼊待加密内容: ";

$decryptedData = fgets(STDIN);

function encryptData( decryptedData, iv,

{

aesIV = base64_decode(iv); aesCipher = decryptedData; aesKey = base64_decode(sessionKey);

result = openssl_encrypt(aesCipher, "AES-128-CBC", aesKey, 0, aesIV); dataObj = json_decode(result);

return $result;

}

result = encryptData(decryptedData, iv, sessionKey);

echo sprintf("最终的加密结果为: %s\n", $result);

最终加、解密脚本运⾏效果如下

巧妇难为⽆⽶之炊,有了解密之巧⼿,便得开始花式寻找“session_key”了。笔者为⼤家总结了如下三类 常⻅造成“session_key”泄漏的场景供⼤家参考:

第⼀类:微信⼩程序AppSecret泄露

有⼀种看不⻅叫做开发觉得你看不⻅,你可以在⼩程序包内的配置⽂件中、Ta的某个博客某篇⽂章中、 GitHub⼩仓库中等等地⽅找到被泄露的AppSecret

之后通过微信官⽅API的 jscode2session 功能便可直接获取⽬标⼩程序的 SessionKey (此接⼝调⽤没 有IP⽩名单限制)。有读者可能会问,请求参数 code 如何获取?其实⾮常简单,在⼩程序内找⼀个有登 录功能的地⽅(会触发 wx.login 的地⽅),然后开始抓包并⼈⼯触发登录功能,之后⼩程序便会把获 取到的code值传送回后端服务器,这时候只要拦截⼀下数据包即可获取 code 参数。

第⼆类:在请求登录或获取openid时直接返回SessionKey

在许多⼩程序中当⽤户执⾏登录操作时,会将获取到的code值传送回服务器后端以便后端执⾏ jscode2session 操作来⽣成 SessionKey 。但许多程序开发者由于安全意识淡薄,当成功获取 SessionKey 值会将其内容直接返回给⽤户⽽不是与第三⽅key做关联处理返回第三⽅key。例如下图为 ⾮常典型的案例,使⽤code换取session_key:

另外在⼀些⼩程序中需要获取⽤户在此⼩程序中的 openid 来识别⽤户的身份,但由于获取openid与获 取session_key使⽤的api为同⼀个均为 jscode2session ,所以在许多案例中在返回openid的同时也返 回了session_key的值。例如下图案例中,⼩程序只想要获取openid的值,但开发者没有对使⽤API获取 到的数据做过滤,连带着session_key的值⼀起返回了:

这两个例⼦的程序后端部分代码可归纳如下,可以看⻅直接将通过API获取到的内容返回给了⽤户,并未 对获取到的数据做任何⼲预处理:

public void GetCode(string js_code)

{

string serviceAddress = "https://api.weixin.qq.com/sns/jscode2session?appid=XXX&secret=XXX"

+ "&js_code=" + js_code + "&grant_type=authorization_code"; HttpWebRequest

request = (HttpWebRequest)WebRequest.Create(serviceAddress);

request.Method = "GET"; request.ContentType = "text/html"; HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Stream myResponseStream = response.GetResponseStream(); StreamReader myStreamReader = new StreamReader(myResponseStream, System.Text.Encoding.UTF8); string retString = myStreamReader.ReadToEnd(); myStreamReader.Close(); myResponseStream.Close();

var obj = new

{

data = retString,

msg = "success"

};

Formatting microsoftDataFormatSetting = default(Formatting);

string result =JsonConvert.SerializeObject(obj, microsoftDataFormatSetting); HttpContext.Current.Response.Write(result);

}

第三类:在查询第三⽅key等功能中返回SessionKey 当开发者⾮常规范的使⽤第三⽅key来关联session_key,并且⼀切操作查询均只使⽤第三⽅key,那么 是不是就没法获取到原本的session_key值了?其实也不尽然,例如如下图的⼩程序中整个登录流程⼗分 的规范,乍⼀看⼗分的安全,攻击者⽆法在登录流程中获取到session_key的值:

但问题就出在这第三步操作中,⼩程序在执⾏完登录流程之后,使⽤对应⽤户的第三⽅sessionID查询⽤ 户信息。但在这个api中,开发者将数据库中对应sessionID的数据全部返回了,这其中包含有与其关联 的⼀个或多个session_key的值:

以上这三类就是常⻅的造成session_key泄露的场景,有了它你就可以在各种⼿机号之间来去⾃如了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱国小白帽 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
访问管理
访问管理(Cloud Access Management,CAM)可以帮助您安全、便捷地管理对腾讯云服务和资源的访问。您可以使用CAM创建子用户、用户组和角色,并通过策略控制其访问范围。CAM支持用户和角色SSO能力,您可以根据具体管理场景针对性设置企业内用户和腾讯云的互通能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档