逻辑分析
请求数量
上面是登录时发的所有请求,可以看出有三个主要的:
- 获取时间(不知道干嘛的)
- 登录
- 获取验证码图片
登录
url:https://user.wangxiao.cn/apis//login/passwordLogin
请求头如下,主要看cookie,有两个比较可疑,其他是百度和微信的,和网校没啥关系:
这是登录的参数负载,内容为:验证码,密码(已经加密),用户名:
获取验证码图片
url:https://user.wangxiao.cn/apis//common/getImageCaptcha
请求头如下:还是cookie有一些奇怪的地方:
验证码返回的数据很明显:
- 返回值
- 图片的base64
- 不知道,暂时忽略
- 时间
获取时间
url:https://user.wangxiao.cn/apis//common/getTime
请求头如下:还是cookie有一些奇怪的地方,到这里其实可以看出来这两个值都携带:
验证码返回的数据很明显:
- 返回值
- 数据(标准时间)
- 不知道,暂时忽略
- 时间
这里其实可以分析一下,那个一串数字的cookie感觉和时间很像,然后那个pc的cookie一看就是电脑的一个什么参数,这里重新走一遍流程
再走一次流程
刷新页面 -> 登录
登录请求的resp里携带了Set-Cookie:
注意,设置Cookie有且只有两种方式:
- 请求返回
- JS本地设置
很明显这里是请求返回的Cookie!
看到这里,除了不知道时间是干什么的其他都问题不大,先放一放,来逆向一下JS的加密过程。
逆向JS
经过对函数调用栈的调试,发现了下图的内容:
可以看到这里的加密内容是 密码+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
函数发送异步请求的例子。从代码内容可以分析出,它可能是用于实现登录功能的一部分。以下是代码的中文解释:
zdAjax(paramss, (ress) => { ... })
:zdAjax
是一个自定义的异步请求函数,接收两个参数:paramss
:请求的配置参数(未在代码中具体展示内容)。- 回调函数
(ress) => { ... }
:请求成功后执行的回调函数,ress
是服务器返回的响应数据。
回调函数内部的逻辑:
定义了一个名为
param
的对象,用于存储新的请求参数。param.url
:目标请求的接口地址,为/login/passwordLogin
,表示这是一个登录接口。param.data
:请求的主体数据,包含以下字段:userName
:用户名,由变量username
提供。password
:密码,这里通过一个加密函数encryptFn
对密码进行处理,加密逻辑是将密码pwd
与服务器返回的某些数据(ress.data
)拼接后进行加密。imageCaptchaCode
:图形验证码,由变量imgCode
提供。
可能的用途:
这段代码看起来是在进行登录时的二次验证。用户需要输入用户名、密码以及图形验证码,密码在发送到服务器前会结合动态返回的
ress.data
进行加密处理,以提升安全性。
需要注意:
encryptFn
的具体实现没有展示,但它应该是一个加密函数,用于保障密码的传输安全。- 图形验证码(
imgCode
)的使用可以防止恶意脚本进行暴力破解。
逆向 JS 第二种方法
第二种方法是快捷方法,因为 POST 请求的载荷是可以看到的,所以可以直接搜索,其实还是挺容易搜到的:
回到正题,现在打断点调试:
点到这里的加密函数里面,可以看到这里的加密过程和公钥:
那我们先把他在 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,如下图:
这里我们需要手动维护 Cookie,因为他不是 resp 设置的,如果是 resp 设置的 Cookie 那么 session 会维护,但是通过 JS 设置的我们只能自己维护了。
那么到这里思路就基本有了:
- 使用 session 请求登录页面,拿到 sessionId
- 请求图片获取验证码
- 请求时间获取时间戳
- 组织 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
成功!
完整代码
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)
验证码识别平台使用的是:图鉴
评论