逻辑分析

请求数量

image.png

上面是登录时发的所有请求,可以看出有三个主要的:

  1. 获取时间(不知道干嘛的)
  2. 登录
  3. 获取验证码图片

登录

url:https://user.wangxiao.cn/apis//login/passwordLogin

请求头如下,主要看cookie,有两个比较可疑,其他是百度和微信的,和网校没啥关系:

image.png

这是登录的参数负载,内容为:验证码,密码(已经加密),用户名:

image.png

获取验证码图片

url:https://user.wangxiao.cn/apis//common/getImageCaptcha

请求头如下:还是cookie有一些奇怪的地方:

image.png

验证码返回的数据很明显:

  1. 返回值
  2. 图片的base64
  3. 不知道,暂时忽略
  4. 时间

image.png

获取时间

url:https://user.wangxiao.cn/apis//common/getTime

请求头如下:还是cookie有一些奇怪的地方,到这里其实可以看出来这两个值都携带:

image.png

验证码返回的数据很明显:

  1. 返回值
  2. 数据(标准时间)
  3. 不知道,暂时忽略
  4. 时间

image.png

这里其实可以分析一下,那个一串数字的cookie感觉和时间很像,然后那个pc的cookie一看就是电脑的一个什么参数,这里重新走一遍流程

再走一次流程

刷新页面 -> 登录

登录请求的resp里携带了Set-Cookie:

image.png

注意,设置Cookie有且只有两种方式:

  1. 请求返回
  2. JS本地设置

很明显这里是请求返回的Cookie!

看到这里,除了不知道时间是干什么的其他都问题不大,先放一放,来逆向一下JS的加密过程。

逆向JS

经过对函数调用栈的调试,发现了下图的内容:

image.png

可以看到这里的加密内容是 密码+getTime(),所以知道 getTime() 的作用了吧,他其实是用来做一个加密。

zdAjax

这里是一个知识点,暂且叫他 死亡回调

zdAjax(paramss, (ress) => {
  var param = {
    url: '/login/passwordLogin',
    data: {
      userName: username,
      password: encryptFn(pwd + '' + ress.data),
      imageCaptchaCode: imgCode,
    },
  }
  // let param = {
  //    url: '/login/passwordLogin',
  //    data: {
  //        userName: username,
  //        password:encryptFn(pwd),
  //        imageCaptchaCode: imgCode
  //    }
  // } ;

  //去请求接口
  zdAjax(param, (res) => {
    if (res.code == 0) {
      if ($('#auto-login').is(':checked')) {
        //自动登录
        keepOurCookie12('autoLogin', true, 30)
        keepOurCookie12('userInfo', JSON.stringify(res.data), expiresDay)
        keepOurCookie12('token', res.data.token, expiresDay)
        syncLogin(res.data, expiresDay)
      } else {
        keepOurCookie12('autoLogin', null)
        keepOurCookie12('userInfo', JSON.stringify(res.data))
        keepOurCookie12('token', res.data.token)
        syncLogin(res.data)
      }

      login.jump(res.data.isBindingMobile)
    } else if (res.code == '9') {
      //密码错误达到了两次
      login.getImgCode($('#nimg-code .img-code-click'))
      $('#nimg-code').addClass('show')
      layer.msg(res.msg)
    } else {
      layer.msg(res.msg)
    }
  })
})

这里直接让 AI 解释一下,它讲的肯定详细:

这段代码看起来是一个使用 zdAjax 函数发送异步请求的例子。从代码内容可以分析出,它可能是用于实现登录功能的一部分。以下是代码的中文解释:

  1. zdAjax(paramss, (ress) => { ... })

  2. zdAjax是一个自定义的异步请求函数,接收两个参数:

    • paramss:请求的配置参数(未在代码中具体展示内容)。
    • 回调函数 (ress) => { ... }:请求成功后执行的回调函数,ress是服务器返回的响应数据。
  3. 回调函数内部的逻辑:

  4. 定义了一个名为param的对象,用于存储新的请求参数。

  5. param.url:目标请求的接口地址,为/login/passwordLogin,表示这是一个登录接口。

  6. param.data:请求的主体数据,包含以下字段:

    • userName:用户名,由变量username提供。
    • password:密码,这里通过一个加密函数encryptFn对密码进行处理,加密逻辑是将密码pwd与服务器返回的某些数据(ress.data)拼接后进行加密。
    • imageCaptchaCode:图形验证码,由变量imgCode提供。
  7. 可能的用途:

  8. 这段代码看起来是在进行登录时的二次验证。用户需要输入用户名、密码以及图形验证码,密码在发送到服务器前会结合动态返回的ress.data进行加密处理,以提升安全性。

需要注意:

  • encryptFn的具体实现没有展示,但它应该是一个加密函数,用于保障密码的传输安全。
  • 图形验证码(imgCode)的使用可以防止恶意脚本进行暴力破解。

逆向 JS 第二种方法

第二种方法是快捷方法,因为 POST 请求的载荷是可以看到的,所以可以直接搜索,其实还是挺容易搜到的:

image.png

回到正题,现在打断点调试:

点到这里的加密函数里面,可以看到这里的加密过程和公钥:

image.png

那我们先把他在 JS 里复现一下,再继续吧:

var JSEncrypt = require("node-jsencrypt")  


function mi(e) {  
    var o = new JSEncrypt;  
    return o.setPublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA5Zq6ZdH/RMSvC8WKhp5gj6Ue4Lqjo0Q2PnyGbSkTlYku0HtVzbh3S9F9oHbxeO55E8tEEQ5wj/+52VMLavcuwkDypG66N6c1z0Fo2HgxV3e0tqt1wyNtmbwg7ruIYmFM+dErIpTiLRDvOy+0vgPcBVDfSUHwUSgUtIkyC47UNQIDAQAB"),  
        o.encrypt(e)  
}

其实很简单,就是这样。

参考文章:[[关于对称加密]]

这里我正常登录后,JS 会设置一些 Cookie,如下图:

image.png

image.png

这里我们需要手动维护 Cookie,因为他不是 resp 设置的,如果是 resp 设置的 Cookie 那么 session 会维护,但是通过 JS 设置的我们只能自己维护了。

那么到这里思路就基本有了:

  1. 使用 session 请求登录页面,拿到 sessionId
  2. 请求图片获取验证码
  3. 请求时间获取时间戳
  4. 组织 post 请求并提交登录请求,设置 Cookie

爬虫

第一步,请求登录页获取 sessionId:

import requests  
import base64  

headers = {  
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",  
    "accept-encoding": "gzip, deflate, br, zstd",  
    "accept-language": "zh-CN,zh;q=0.9,en-AS;q=0.8,en;q=0.7",  
    "cache-control": "no-cache",  
    "connection": "keep-alive",  
    "cookie": "Hm_lvt_86efc728d941baa56ce968a5ad7bae5f=1741101680; _bl_uid=8gmOO7I4uypmUtz1mivvo73tCUC1; mantis6894=20a09360db574f56bf40616d091ad7cc@6894; safedog-flow-item=",  
    "host": "user.wangxiao.cn",  
    "pragma": "no-cache",  
    "referer": "https://www.wangxiao.cn/",  
    "sec-ch-ua": "\"Not(A:Brand\";v=\"99\", \"Google Chrome\";v=\"133\", \"Chromium\";v=\"133\"",  
    "sec-ch-ua-mobile": "?0",  
    "sec-ch-ua-platform": "\"Windows\"",  
    "sec-fetch-dest": "document",  
    "sec-fetch-mode": "navigate",  
    "sec-fetch-site": "same-site",  
    "sec-fetch-user": "?1",  
    "upgrade-insecure-requests": "1",  
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"  
}  

login_url = "https://user.wangxiao.cn/login?url=https%3A%2F%2Fwww.wangxiao.cn%2F"  

session = requests.session()  

resp = session.get(url=login_url)  

print(resp.cookies)

第二步,请求图片验证码:

captcha_url = "https://user.wangxiao.cn/apis//common/getImageCaptcha"  

resp = session.post(url=captcha_url, headers={  
    "content-type": "application/json;charset=UTF-8"  
})  

image_b64 = base64.b64decode(resp.json()["data"].split(",")[1])  

with open("tu.png", "wb") as f:  
    f.write(image_b64)  
    f.close()

第三步,获取时间戳:

time_url = "https://user.wangxiao.cn/apis//common/getTime"  

resp = session.post(url=time_url, headers={  
    "content-type": "application/json;charset=UTF-8"  
})  

time_data = resp.json()["data"]

第四步,组织并提交请求,设置 Cookie:

time_data = resp.json()["data"]  

ck_num = base64_api("用户名", "密码", "tu.png", 3)  

f = open("jiami.js", mode="r")  
js_code = f.read()  
f.close()  

js = execjs.compile(js_code)  

pwd_mi = js.call("mi", "xxxxxxxxxxxxxx"+time_data)  

payload = {  
    "imageCaptchaCode": ck_num,  
    "password": pwd_mi,  
    "userName": "xxxxxxxxxxx"  
}
login_url = "https://user.wangxiao.cn/apis//login/passwordLogin"  

resp = session.post(url=login_url, data=json.dumps(payload, separators=(',', ':')), headers={  
    "content-type": "application/json;charset=UTF-8"  
})  

data_dir = resp.json()["data"]  

userName = data_dir["userName"]  
userNameCookies = data_dir["userNameCookies"]  
passwordCookies = data_dir["passwordCookies"]  

userInfo = json.dumps(data_dir, separators=(',', ':'))  
token = data_dir["token"]  
autoLogin = "null"  
sign = data_dir["sign"]  

session.cookies["autoLogin"] = autoLogin  
session.cookies["userInfo"] = userInfo  
session.cookies["token"] = token  
session.cookies["UserCookieName"] = userName  
session.cookies["OldUsername2"] = userNameCookies  
session.cookies["OldUsername"] = userNameCookies  
session.cookies["OldPassword"] = passwordCookies  
session.cookies["UserCookieName_"] = userName  
session.cookies["OldUsername2_"] = userNameCookies  
session.cookies["OldUsername_"] = userNameCookies  
session.cookies["OldPassword_"] = passwordCookies  
session.cookies[userName+"_exam"] = sign

image.png

成功!

完整代码

import requests  
import base64  
import json  
import execjs  

def base64_api(uname, pwd, img, typeid):  
    with open(img, 'rb') as f:  
        base64_data = base64.b64encode(f.read())  
        b64 = base64_data.decode()  
    data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64}  
    result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text)  
    if result['success']:  
        return result["data"]["result"]  
    else:  
        #!!!!!!!注意:返回 人工不足等 错误情况 请加逻辑处理防止脚本卡死 继续重新 识别  
        return result["message"]  
    return ""  

headers = {  
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",  
    "accept-encoding": "gzip, deflate, br, zstd",  
    "accept-language": "zh-CN,zh;q=0.9,en-AS;q=0.8,en;q=0.7",  
    "cache-control": "no-cache",  
    "connection": "keep-alive",  
    "cookie": "Hm_lvt_86efc728d941baa56ce968a5ad7bae5f=1741101680; _bl_uid=8gmOO7I4uypmUtz1mivvo73tCUC1; mantis6894=20a09360db574f56bf40616d091ad7cc@6894; safedog-flow-item=",  
    "host": "user.wangxiao.cn",  
    "pragma": "no-cache",  
    "referer": "https://www.wangxiao.cn/",  
    "sec-ch-ua": "\"Not(A:Brand\";v=\"99\", \"Google Chrome\";v=\"133\", \"Chromium\";v=\"133\"",  
    "sec-ch-ua-mobile": "?0",  
    "sec-ch-ua-platform": "\"Windows\"",  
    "sec-fetch-dest": "document",  
    "sec-fetch-mode": "navigate",  
    "sec-fetch-site": "same-site",  
    "sec-fetch-user": "?1",  
    "upgrade-insecure-requests": "1",  
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"  
}  

login_url = "https://user.wangxiao.cn/login?url=https%3A%2F%2Fwww.wangxiao.cn%2F"  

session = requests.session()  

resp = session.get(url=login_url)  

captcha_url = "https://user.wangxiao.cn/apis//common/getImageCaptcha"  

resp = session.post(url=captcha_url, headers={  
    "content-type": "application/json;charset=UTF-8"  
})  

image_b64 = base64.b64decode(resp.json()["data"].split(",")[1])  

with open("tu.png", "wb") as f:  
    f.write(image_b64)  
    f.close()  

time_url = "https://user.wangxiao.cn/apis//common/getTime"  

resp = session.post(url=time_url, headers={  
    "content-type": "application/json;charset=UTF-8"  
})  

time_data = resp.json()["data"]  

ck_num = base64_api("用户名", "密码", "tu.png", 3)  

f = open("jiami.js", mode="r")  
js_code = f.read()  
f.close()  

js = execjs.compile(js_code)  

pwd_mi = js.call("mi", "xxxxxxxxxxxxxx"+time_data)  

payload = {  
    "imageCaptchaCode": ck_num,  
    "password": pwd_mi,  
    "userName": "xxxxxxxxxxx"  
}
login_url = "https://user.wangxiao.cn/apis//login/passwordLogin"  

resp = session.post(url=login_url, data=json.dumps(payload, separators=(',', ':')), headers={  
    "content-type": "application/json;charset=UTF-8"  
})  

data_dir = resp.json()["data"]  

userName = data_dir["userName"]  
userNameCookies = data_dir["userNameCookies"]  
passwordCookies = data_dir["passwordCookies"]  

userInfo = json.dumps(data_dir, separators=(',', ':'))  
token = data_dir["token"]  
autoLogin = "null"  
sign = data_dir["sign"]  

session.cookies["autoLogin"] = autoLogin  
session.cookies["userInfo"] = userInfo  
session.cookies["token"] = token  
session.cookies["UserCookieName"] = userName  
session.cookies["OldUsername2"] = userNameCookies  
session.cookies["OldUsername"] = userNameCookies  
session.cookies["OldPassword"] = passwordCookies  
session.cookies["UserCookieName_"] = userName  
session.cookies["OldUsername2_"] = userNameCookies  
session.cookies["OldUsername_"] = userNameCookies  
session.cookies["OldPassword_"] = passwordCookies  
session.cookies[userName+"_exam"] = sign

# 尝试加载一些数据  

data_url = "https://ks.wangxiao.cn/practice/listQuestions"  
pay_data = '{"practiceType":"2","sign":"jz1","subsign":"8cc80ffb9a4a5c114953","examPointType":"","questionType":"","top":"30"}'  

data_resp = session.post(data_url, data=pay_data, headers={  
    "content-type": "application/json; charset=UTF-8",  
    "host": "ks.wangxiao.cn",   # 杀死骆驼的最后一根稻草  
})  

print(data_resp.text)  
print(data_resp.request.headers)

验证码识别平台使用的是:图鉴