depy

It is a long and beautiful life.

小程序开发之微信授权登陆[附上一些安全思考]

项目

前言

之前挖过小程序的漏洞,有任意用户登陆的,一直没有深刻理解过那个漏洞。直到最近想开发一个内部文档系统,涉及到用户鉴权才认真得学习了一下。

实验室的学妹们都不学习,只有我学习,就当记录下学习笔记了。

授权登陆流程

其实流程很简单。

首先第一步,触发登陆事件,我们可以给一个按钮bindtap绑定一个登陆事件。

607d6e28e122a.png

登陆之后就是获取用户信息的过程了,微信官方给了一个方法wx.login,使用这个方法先通过res,然后使用success: function (res)来触发接下来的方法。

此时res是一个数组,res.code可以理解成授权码,我们在oauth的认证过程中其实也涉及到了。

在oauth的认证体系中,首先用户扫码获取一个临时id,此id可以是字符串,然后发送给后端接口,接口捕获这个id然后通过appid和secret去调用官方api获取用户信息的list。

而这个code是临时的无法伪造的,我并不知道官方接口对于id的定义,但是可以想到的是,这样的认证方法非常安全。

所以,res.code也是,获取到这个res.code后,可以通过wx.getUserInfo方法获取用户的头像、性别、昵称等信息。然后通过wx.request向后台接口发送我们的数据。

此时后端接口接收到了code,和一些用户信息。

我们的接口可以这么写:

其他语言相似,我们的接口通过设置小程序后台常量(appid、secret),然后接口拼接使用code获取到用户信息。

607d708246109.png

可以发现每次的code都是不一样的,并且很长很复杂无法伪造。我们可以把结果打出来看看

607d718129744.png

发现只有两个东西,一个session_key一个openid。

存在的安全问题

可能大家不知道session_key有什么用,因为我这个是一种比较简单粗暴微信小程序的授权方式(我只需要比对openid后台是否存在)。其实还有另一种认证方法(可以使用手机号一键登陆)。

另一种首先通过js生成iv、encrydata(加密内容)、code,然后发送后端,后端获取到iv、encrydata、code。使用code获取到session_key,session_key解密encrydata获取到最后的json格式数据,然后进行认证通过并返回给前端。

所以攻击者可以使用下列方法来进行攻击:

1.正常登陆查看解密后的json 数据 [例如:{"phone":"1788888888","sex":"男"}]

2.所以我们这时候可以知道encrydata其实就是{"phone":"1788888888","sex":"男"},当使用session_key解密成功后,当前请求自动认证为手机号1788888888的用户

3.发现session_key泄漏,ok,使用session_key和iv以上述的json格式{"phone":"1788888881","sex":"男"},进行加密生成新的encrydata

4.发起新的认证请求,替换encrydat

5.后端使用iv和session_key解密,发现手机号已经被替换成1788888881,实现任意用户登陆

所以我们可以知道的是,session_key起到至关重要的作用。

1.有些开发者(甚至一些大厂)会把session_key和解密结果一起放到响应包中,而session_key是在一段时间有效的,所以只要写python脚本重发脚本就可以实现攻击。

2.再回到我们之前获取session_key的方法,通过微信的官方接口,所以如果泄漏了appid和app_secret,那么也会泄漏session_key。而小程序有相关的源码获取过程,通过一些脚本插件可以获取小程序的源代码,可以翻阅js文件找到开发者疏忽而定义的secret。

后记

了解这么多之后,我在接口返回数据中不会把session_key生成返回,仅仅返回openid(其实即使返回了也无法伪造,因为根据openid做鉴权)。

前端获取openid后,拼接page访问文章,而获取文章内容又是一个新接口,会带上我们的openid,当后端获取openid后先进行鉴权校验,不在数据库的默认不返回文章内容。

但是如果有内鬼的话,就完蛋了,只需要把openid获取下来,发给其他用户,通过接口返回文章内容就行了。所以这个认证虽然简单无法伪造,但是还是有缺陷的。

所以我现在设想的是,认证通过后设置一个session给请求,还没有学到,下次再说了。