记录一次莫心理健康平台的登录分析

查看 31|回复 3
作者:daijunhao   
前言
最近我校突然让我们制作心理测评网站,然后登录中发现有滑块验证码等操作(之前都是直接登录无需验证码,现在界面变的很整洁以及引入了验证码登录)
本人目前初二(八年级)第一次写文章,所以文章可能会有些地方写的不清楚,请谅解!
网站:aHR0cHM6Ly96eXpmeC5wc3l5dW4uY29tLw==
开始分析
验证码获取
验证流程(失败处理):

base64图像示例:
originalImageBase64:

jigsawImageBase64:

可以看到是滑块验证码
这个是比较好解的(ddddocr可以解决)
重点是在请求
接下来是验证成功的请求流程:

解析:
获取验证码:
Get https://zyzfx.psyyun.com/code?userName=[U]
// UserName为用户名
Headers:
Authorization: Basic xxx //Auth必备条件
TENANT-ID: 440 // 租户ID(相当于学校ID)

Authorization固定的值:

TENANT-ID生成方法:
Get https://zyzfx.psyyun.com/admin/tenant/detailByCode?code=zyzfx
// 其中code为学校的代码(网址前缀)
Result:

很好,关键的一些请求头都获取到了
接下来是编写Python代码(获取验证码):
import requests
# Disable SSL Verification
requests.packages.urllib3.disable_warnings()
# Init
session = requests.Session()
session.verify = False
session.headers = {
  "Authorization": "Basic xxx", // 获取到的Authorization(可直接写进里面)
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0",
  "Accept": "application/json, text/plain, */*",
  "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", // 默认请求方式
  "TENANT-ID": "440", // 租户ID
}
UserName = "xxx" // 用户名
captcha = session.get(f"https://zyzfx.psyyun.com/code?userName={UserName}&captchaType=blockPuzzle").json() // 获取验证码
imageOriginalBase64 = captcha["data"]["repData"]["originalImageBase64"] // 背景图
imageOriginal = base64.b64decode(imageOriginalBase64)
imagePuzzleBase64 = captcha["data"]["repData"]["jigsawImageBase64"] // 拼图
imagePuzzle = base64.b64decode(imagePuzzleBase64)
token = captcha["data"]["repData"]["token"] // token
secretKey = captcha["data"]["repData"]["secretKey"] // 安全密钥
验证码验证流程
先分析请求


Post请求,内容为空
Query String:
userName: 1 // 用户名
captchaType: blockPuzzle // 验证码类型:滑块验证码
pointJson: c+NDG2sxh8ntCz9nlBvmf3/0T4Z/I1PwkvcC9D2KBlg=
// 加密内容 secretKey:aGvrWRMPxKoeuGMe
token: c62b362137b542fcab00ff073a1932a1 // code中获取到的token

js分析:



解析(pointJson)
使用AES中的ECB模式加密,填充方式为Pkcs7方式,secretKey为加密密钥
在o.a函数中,t为key(获取arguments,获取第二项作为key密钥(没有默认使用密钥:XwKsGlMcdPMEhR1B))

返回内容分析:
失败:
{
    "code": 0,
    "msg": "成功",
    "data": {
        "repCode": "6111",
        "repMsg": "验证失败",
        "repData": null,
        "success": false  // 验证失败的判断
    },
    "success": true
}
成功:
{
    "code": 0,
    "msg": "成功",
    "data": {
        "repCode": "0000",
        "repMsg": null,
        "repData": {
            "captchaId": null,
            "projectCode": null,
            "captchaType": "clickWord",
            "captchaOriginalPath": null,
            "captchaFontType": null,
            "captchaFontSize": null,
            "secretKey": null,
            "originalImageBase64": null,
            "point": null,
            "jigsawImageBase64": null,
            "wordList": null,
            "pointList": null,
            "pointJson": "c+NDG2sxh8ntCz9nlBvmf3/0T4Z/I1PwkvcC9D2KBlg=", // 请求里面的pointJson
            "token": "c62b362137b542fcab00ff073a1932a1", // token值
            "result": true,
            "captchaVerification": null,
            "clientUid": null,
            "ts": null,
            "browserInfo": null
        },
        "success": true // 验证成功的判断
    },
    "success": true
}
这个请求更像是在验证是否正确滑到指定位置(无其他返回参数)
登录部分


Query String:
randomStr: blockPuzzle // 默认滑块验证码
code: grILnYI7HmW41fTsrP7O/WrHm3qTRbHLt0Rq1KVrvLzdfKCSGGKWwGLnuGB6OHfqN5/1jFY457Zrz+LFL0MGG3RE5ka9Y04xQKpe06cmkOs= // 特殊加密的code,其里面包含了获取验证码的token与PointJson的内容
grant_type: password // 粗略推测应该是使用密码类型登录
username: 1 //用户名
POST内容(表单数据):
username: 1 // 用户名
password: Hgn8K/2pda3c5rEnoZIcpA== // 特殊加密的密码
Headers:
isToken: false // 默认为false
TENANT-ID: 440 // 前文提到的学校ID
Authorization: Bearer xxx // 前文提到的Authorization验证部分
Content-Type: application/x-www-form-urlencoded;charset=utf-8 // 默认请求内容的方式:form格式

js代码分析:


先看到key值的部分:

        var i = "r"
          , r = "u"
          , a = "i"
          , o = "g"
          , c = "e";
        function s() {
            return "".concat(i).concat(r).concat(a).concat(o).concat(c).concat(i).concat(r).concat(a).concat(o).concat(c).concat(i).concat(r).concat(a).concat(o).concat(c).concat(c)
                                                // 使用拼接的方式拼凑出key,即为ruigeruigeruigee
        }


通过这段代码可得知密码使用的是AES CBC的模式加密(nopadding)且key和iv均为固定密钥:ruigeruigeruigee

接下来是Code部分
我们回到刚刚验证验证码验证成功后的部分

captchaVerification值是code的证据:

总结:password使用AES CBC nopadding加密,其iv和key均为ruigeruigeruigee
code为AES ECB Pkcs7加密,密钥为secretKey,内容为:
---
俩个加密的解决了,接下来是编写python代码逻辑:
def Login(UserName,Password):
    while True:
        # Get Captcha
        session.headers.update({"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"})
        session.headers.update({"Authorization": "Basic xxx"})
        captcha = session.get(f"https://zyzfx.psyyun.com/code?userName={UserName}&captchaType=blockPuzzle").json()
        imageOriginalBase64 = captcha["data"]["repData"]["originalImageBase64"]
        imageOriginal = base64.b64decode(imageOriginalBase64)
        imagePuzzleBase64 = captcha["data"]["repData"]["jigsawImageBase64"]
        imagePuzzle = base64.b64decode(imagePuzzleBase64)
        token = captcha["data"]["repData"]["token"]
        secretKey = captcha["data"]["repData"]["secretKey"]
        # DDDOCR
        res = ocr.slide_match(imagePuzzle, imageOriginal, simple_target=True)
        # Encrypt pointJson
        # AES ECB Encrypt
        originalJsonData = "{" + f"\"x\":{res['target'][0]}.27272727272727,\"y\":5" + "}"
        cipher = AES.new(secretKey.encode(), AES.MODE_ECB)
        encrypted_data = cipher.encrypt(pad(originalJsonData.encode(), AES.block_size))
        encrypted_code = cipher.encrypt(pad((token + "---" +  originalJsonData).encode(), AES.block_size))
        encryptedCodeBase64 = base64.b64encode(encrypted_code).decode().replace(" ","+")
        encrypted = base64.b64encode(encrypted_data).decode()
        encrypted = encrypted.replace(" ", "+")
        # Submit Captcha
        buildPostJson = {
            "userName": UserName,
            "captchaType": "blockPuzzle",
            "pointJson": encrypted,
            "token": token
        }
        #Json To QueryString
        import urllib.parse
        queryString = ""
        for key, value in buildPostJson.items():
            # 对查询参数进行URL编码以避免特殊字符问题
            encoded_value = urllib.parse.quote(str(value), safe='')
            queryString += f"{key}={encoded_value}&"
        queryString = queryString[:-1]
        captchaResult = session.post(f"https://zyzfx.psyyun.com/code/check?{queryString}").json()
        if captchaResult["data"]["success"] == True:
            # # AES CBC Encrypt Password
            encrypted_password_b64 = aes_cbc_zero_padding_encrypt(Password, "ruigeruigeruigee")
            # Login
            # 将URL参数转换为JSON格式再编码为查询字符串
            params = {
                "randomStr": "blockPuzzle",
                "code": encryptedCodeBase64,
                "grant_type": "password",
                "username": UserName
            }
            # 手动构建查询字符串,确保特殊字符被正确编码
            query_string = "&".join([f"{k}={urllib.parse.quote(str(v), safe='')}" for k, v in params.items()])
            url = f"https://zyzfx.psyyun.com/auth/oauth/token?{query_string}"
            session.headers.update({"Content-Type":"application/x-www-form-urlencoded","ignore":"1","origin":"https://zyzfx.psyyun.com"})
            response = session.post(url, data={"username":UserName,"password": encrypted_password_b64})
            loginResult = response.json()
            print(f"登录成功,用户名:{loginResult['user_info']['username']}")
            session.headers.update({"Authorization":f"Bearer {response.json()['access_token']}"})
            break
返回内容:
登录失败:

{"code":1,"msg":"登录失败,用户名或密码错误","data":"unauthorized"}

登录成功:


其中需要添加请求头Authorization: Bearer access_token内容
添加这个请求头后即可请求接下来的如获取心理测评表等API操作

之后就是答题部分(抓包即可抓下来),Python代码(自动完成题目):
DefaultSelectIndex = 0 # 默认选择选项
Login("UserName","Password") // 用户名与密码
# 心理测评表获取(空代表完成)
getSacleTable = session.get("https://zyzfx.psyyun.com/plantest/userPlan/getMyUpscomingScaleTabByPage?source=2&type=1¤t=1&size=10").json()
if getSacleTable["data"]["records"] != []:
                print(f"正在测评: {i['studentName']}")
                for j in getSacleTable["data"]["records"]:
                                print(f"测评名称: {j['planTitle']}")
                                print(f"测评ID: {j['id']}")
                                # Get Test List
                                getTestList = session.get("https://zyzfx.psyyun.com/plantest/userPlan/getMyPlanTestScaleByPageAndPlanId?planId=3329").json()
                                for k in getTestList["data"]:
                                                print(f"测评名称:{k['otherName']}")
                                                print(f"测评ID: {k['testId']}")
                                                print(f"ScaleID: {k['scaleId']}")
                                                getQuestionCount = int(k["questionCount"])
                                                page = getQuestionCount / 10
                                                print("开始答题")
                                                CommitList = []
                                                for m in range(int(page + 1)):
                                                                getQuestionList = session.get(f"https://zyzfx.psyyun.com/scale/question/getMyQuestionAndOptionByscaleId?scaleId={k['scaleId']}&testId={k['testId']}&pushId=0¤t={str(m)}").json()
                                                                for n in getQuestionList["data"]["records"]:
                                                                                getDefaultAnswer = n["optionList"][DefaultSelectIndex]
                                                                                CommitList.append({"questionId":int(getDefaultAnswer["questionId"]),"answerId":int(getDefaultAnswer["id"]),"answerContent":getDefaultAnswer["answerContent"],"answerType":1,"score":int(getDefaultAnswer["score"])})
                                                buildCommitJson = {
                                                                "scaleId": int(k["scaleId"]),
                                                                "testId": int(k["testId"]),
                                                                "studentId": 0,
                                                                "useTime": 450,
                                                                "isFinish": True,#m == page - 1,
                                                                "current": int(CommitList[-1]["questionId"]),
                                                                "pushId": 0,
                                                                "answerList": CommitList,
                                                                "visitorId": None
                                                }
                                                # Commit
                                                session.headers.update({"Content-Type":"application/json"})
                                                commitResult = session.post("https://zyzfx.psyyun.com/plantest/userPlan/commitAnswer",json=buildCommitJson).json()
                                                print(f"提交结果: {commitResult}")
                                                print(f"Complete: {k['otherName']}")
效果展示

验证码, 密钥

KaliHt   

初中都接触逆向,大牛级别呀
xgw9097   

大神支持666
yangzc999   

新力量!
您需要登录后才可以回帖 登录 | 立即注册

返回顶部