0%

网络爬虫逆向(中大网校登录)

目标网址:https://user.wangxiao.cn/login?url=https%3A%2F%2Fwww.wangxiao.cn%2F

案例目标:网页账号密码模拟登录,获取对应的题库内容。

image-20230508160812483

网页分析

问题复现,随便输入账号、验证码和密码,然后点击登录。

image-20230508162341501

通过抓包查看请求内容。

image-20230508180848295

image-20230508180641558

从返回的结果可以看出,用于处理登录的数据包传输的password为加密格式,发送的是POST请求。

我们想要模拟这个登录请求,必须要做的一个事情就是分析该密码的加密逻辑,将自己登录传输的密码按照相同的加密逻辑进行加密,然后传输过去进行验证。

接下来开始分析其加密逻辑,点击启动器。前三个请求都是使用jquery发送。

image-20230508181104556

我们直接从第四个开始分析,点进去,打上断点。

image-20230508181152930

再次点击登录按钮,让其在断点处暂停。

image-20230508181248335

e中的url可以看出,这个时候请求的地址为/common/getTime。我们释放此次断点。

image-20230508181450915

这个时候的url地址才是正常的。

image-20230508181520715

查看发送的Ajax请求中的data值。

image-20230508181605296

通过观察发现data的值是通过i赋值得到,在此处的密码已经被加密,我们需要往前找。

image-20230508182047236

在此处可以知道i是通过e.data来赋值得到,并且e中的password已经的被加密的,而e是通过函数传输过来的,这个时候就需要往前找调用这个函数的地方,传输过来的e是什么,查看前一步操作。这个时候就需要借助右下角的调用堆栈。

image-20230508181723715

image-20230508182211409

在这个地方是通过zdAjax接口对其进行调用,并且传入接口中有两个参数,第一个是param其作用就是将加密的数据传输过去;第二个是一个箭头函数,其作用暂时不清楚。再次利用右下角的调用堆栈,回到之前的调用栈,搞清楚箭头函数传输过去是做了什么事情。

image-20230508185205112

经过分析可以看到,这个箭头函数传输过来只在Ajax传输后对其进行了使用。

小结,zdAjax(param, res => {})作用为将param作为Ajax请求的参数发送到服务器的"/apis/" + param.url处,然后将服务器接收param参数后返回的数据,再传输作用再第二个箭头函数中。

再回到后一个调用栈,现在可以知道,e是通过该数据包中的param参数赋值得到,并且在这个地方param也是被加密的,继续向上找。

image-20230508182433102

往上找发现param参数中的password是通过encryptFn(pwd + '' + ress.data)该方式对密码进行加密,此处pwd即为实际输入的未加密的密码,而param中已经被加密了,说明encryptFn即为加密方法。

其中ress.data的值为ress传输过来的,而经过前面的分析zdAjax接口的作用是将paramss作为参数发送到服务器中的某个url地址,然后将返回的数据传输给第二个箭头函数。

换句话说,ress的值为paramss发送给服务器后的响应值。再网上找paramss看看是什么。

image-20230508184909724

可以看出,服务器接收参数的地址为/common/getTime,并且没有传输任何参数。

接下来看看加密方法encryptFn

image-20230508185541368

点击来就可以看到关键字setPublicKey

image-20230508185615112

说明此处密码的加密方式就是RSA加密,并且公钥就再"xxxx"中。

PS:在分析网页的时候我们可以很明显的看到很多keepOurCookie12关键词,这说明该网址很有可能对网页的Cookie校验非常严格,可能调用JS对Cookie进行处理,这可能导致我们使用传统的session会话去保存登录状态无法解决登录问题。

现在加密方式和公钥都有了,顺利的话密码的加密问题应该已经可以解决。

除了账号和密码,在登录的时候还有一个验证码图片需要处理。查看验证码的请求方式。

image-20230508190339303

图片的请求是使用的POST请求方式,

API地址为https://user.wangxiao.cn/apis//common/getImageCaptcha

接下来查看其返回的内容,点击预览。

image-20230508190559681

可以看到图片的传输使用的是base64进行编码传输的。 我们可以将其获取下来,将其反向转化为字节后存储到本地即可看到正常图片内容。接下来可以人工识别或者调用第三方工具识别。

现在验证码和密码问题都已分析完成,开始进行代码编写。

代码编写

验证码识别

验证码识别问题相对简单点,我们先解决该问题。

直接请求API发现无法得到正常的图片数据。

image-20230508191120812

给其增加常见的请求头。

image-20230508191432398

从结果可以看到,加了常见的请求头参数仍然无法解决问题。

通过在其发送Ajax请求时可以看到,在发送Ajax请求时有一个很特殊的参数contentType也被传输过去了。

image-20230508191653759

然后我们观察在请求头中也有这个参数,尝试把它加到请求头中进行访问。

image-20230508191807039

这个时候发现能够得到JSON数据了,但是服务器的响应时间明显增加了,并且数据还是不对,显示未知错误。

image-20230508191931829

这个时候时候通过观察cookie我们发现在其中有一个参数为sessionId值为1683532405248,这很有可能是一个与时间有关的参数,时间间隔太长导致请求失效了。

这个cookie是从哪里来的呢?我们关闭断点,直接刷新整个页面(注意:需要先清楚网页缓存),观察下在请求首页时候的cookie

image-20230508192407095

可以看到在请求首页的时候,响应头中有个Set-Cookie:sessionId=1683545006624; path=/参数,其中就有我们想要的sessionId,该参数的作用就是给后面的访问设置Cookie

这个时候我们可以点击密码登录查看验证码数据包Cookie中的sessionId值看看是否一致。

image-20230508192729712

通过比较可以发现确实是一致的。

所以要想请求到正确的图片数据,需要先请求首页让其设置Cookie后,再请求图片API。这个时候就需要使用到会话Session机制。

image-20230508193136587

这个时候发现请求结果正常。使用base64对其进行解码存储到本地。

image-20230508193538657

image-20230508193554207

图片显示正常。有两种常见的方式对其进行识别:

  1. 人工识别后输入
  2. 调用第三方工具识别

在这里我们采用调用第三方工具的方式进行识别。

调用的是图鉴API。在其官网有对应的调用示例,我们直接复制代码即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import base64
import json
import requests

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 ""

其中:

  • uname:用户账号
  • pwd:用户密码
  • img:用户图片地址
  • typeid:识别的图片种类,在官网可以查看,我们这里是数字和英文的组合即为3。

image-20230508194142454

按照要求填入内容。

image-20230508194435182

成功返回结果,验证码识别完成。

密码RSA加密

经过前面的分析,再进行密码加密的时候不是单纯的对密码加密,会加上一个ress.data这个参数是通过对服务器中的/common/getTime地址进行请求返回的结果。在抓包工具中也可以看到该数据包。

image-20230508195049221

使用POST请求该数据包,同样在请求头中加上Content-Type

image-20230508195306764

结果返回正常。

筛选出get_time值,并为了后面代码编写,先定义好账号和密码(自己填写)。

image-20230508195743060

拼接好待加密参数

image-20230508200009632

经过前面的分析我们已经拿到RSA公钥,接下来使用RSA利用公钥对其进行加密,并将加密后的数据转化为Base64

1
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA5Zq6ZdH/RMSvC8WKhp5gj6Ue4Lqjo0Q2PnyGbSkTlYku0HtVzbh3S9F9oHbxeO55E8tEEQ5wj/+52VMLavcuwkDypG66N6c1z0Fo2HgxV3e0tqt1wyNtmbwg7ruIYmFM+dErIpTiLRDvOy+0vgPcBVDfSUHwUSgUtIkyC47UNQIDAQAB

image-20230508200702897

加密成功,现在已经解决了在请求时需要使用到的所有参数。

开始发送登录请求

在发送请求时,注意将data表单转化为JSON格式。这个在JS源码中也可以看到,在获取到e.data时调用了一个JSON.stringify()方法。

image-20230508201306167

image-20230508201144745

这里登录成功了,我们是否可以直接使用session会话请求到其他页面内容呢?

例如我们请求某个模拟考试(http://ks.wangxiao.cn/TestPaper/getPaperRuleQuestions),看是否能够拿到试题。

image-20230508202315609

image-20230508202523643

image-20230508202719573

发现无法正常请求该地址,又自动跳转到了登录页面。

image-20230508202827218

说明我们在此处通过session保存的cookie并没有保存到登录状态。这个就是我们前面说的该网站在处理Cookie时调用了JS对Cookie进行进一步处理。

对于这种情况只能通过分析网页JS后手动补全Cookie

image-20230508213612934

image-20230508213627352

按照JS中写的手动补全Cookie

image-20230508214746537

数据获取成功。

image-20230508214813242

完整代码

完整代码如下,其中账号密码需要手动填入。

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import json
import requests
import base64
from tujianAPI import base64_api # 导入图鉴调用api的函数
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA


# 处理验证码识别问题
login_page_url = 'https://user.wangxiao.cn/login'
img_api = 'https://user.wangxiao.cn/apis//common/getImageCaptcha'
session = requests.session()
session.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
}
session.get(login_page_url)
img_resp = session.post(img_api, headers={"Content-Type": "application/json;charset=UTF-8"})
base64_img_code = img_resp.json()['data'].split(',')[-1]
base64_decode_img = base64.b64decode(base64_img_code)
with open('tu.png', 'wb') as f:
f.write(base64_decode_img)
img_code = base64_api("", "", "tu.png", 3) # 这里也需要补全图鉴网的账号和密码
# print(img_code)

# 处理密码加密问题
get_time_api = "https://user.wangxiao.cn/apis//common/getTime"
get_time_resp = session.post(get_time_api, headers={"Content-Type": "application/json;charset=UTF-8"})
get_time_code = get_time_resp.json()['data']

# # 定义账号密码
account = '' # 中大网校账号
password = '' # 中大网校密码

# # 拼接待加密参数
password_concat = password + get_time_code
# print(password_concat)

# ## 将待加密参数进行RSA加密
public_key_bs64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA5Zq6ZdH/RMSvC8WKhp5gj6Ue4Lqjo0Q2PnyGbSkTlYku0HtVzbh3S9F9oHbxeO55E8tEEQ5wj/+52VMLavcuwkDypG66N6c1z0Fo2HgxV3e0tqt1wyNtmbwg7ruIYmFM+dErIpTiLRDvOy+0vgPcBVDfSUHwUSgUtIkyC47UNQIDAQAB"
public_key = RSA.import_key(base64.b64decode(public_key_bs64)) # 将bs64格式的公钥转化为加密器所需格式
rsa_ = PKCS1_v1_5.new(key=public_key) # 使用公钥创建加密器
mi_bytes = rsa_.encrypt(password_concat.encode('UTF-8')) # RSA加密
mi_bs64 = base64.b64encode(mi_bytes).decode()


# 开始发送登录请求
login_api = 'https://user.wangxiao.cn/apis//login/passwordLogin'
data = {
"imageCaptchaCode": img_code,
"password": mi_bs64,
"userName": account
}
login_resp = session.post(login_api, data=json.dumps(data), headers={"Content-Type": "application/json;charset=UTF-8"})
login_info = login_resp.json()
print(login_info)

# 手动补全Cookie
session.cookies['autoLogin'] = 'true'
session.cookies['userInfo'] = json.dumps(login_info['data'])
session.cookies['token'] = login_info['data']['token']
session.cookies['UserCookieName'] = login_info['data']['userName']
session.cookies['OldUsername2'] = login_info['data']['userNameCookies']
session.cookies['OldUsername'] = login_info['data']['userNameCookies']
session.cookies['OldPassword'] = login_info['data']['passwordCookies']
session.cookies['UserCookieName_'] = login_info['data']['userName']
session.cookies['OldUsername2_'] = login_info['data']['userNameCookies']
session.cookies['OldUsername_'] = login_info['data']['userNameCookies']
session.cookies['OldPassword_'] = login_info['data']['passwordCookies']
session.cookies[login_info['data']['userName'] + "_exam"] = login_info['data']['sign']

test_url = "http://ks.wangxiao.cn/TestPaper/getPaperRuleQuestions"
test_data = {
"id": '21F74E2F-44FA-4550-B481-5EF80A4BA09F'
}
test_resp = session.post(test_url, data=json.dumps(test_data), headers={"Content-Type": "application/json;charset=UTF-8"})
print(test_resp.text)

tujianAPI.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import base64
import json
import requests

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 ""
-------------本文结束感谢您的阅读-------------