抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >
本文章仅用作于学习

背景:最近期末到来,想第一时间看到新出成绩的,但教务系统处必须得等到学期结束才能看到,于是就有了想到爬取学校教务系统来获取自己的成绩,目前完成了模拟登录的阶段。
因为大部分网站都可以通过CAS进行授权登录,所以只要登录了,就可以方便地完成一些机械化的操作。可以导出课表、成绩单、抢课。
之后有时间的话,可以补充一些真正有用的应用。

目录

- 新教务系统
- 旧教务系统

新教务系统

分析登录请求

首先进入新教务系统首页,打开F12,输入账号密码进行测试。
提交的表单数据如下:屏幕截图_20230827_135910

可以看到,username就是学号,password不是密码的明文,可能是经过了哈希或者加密,authcode是图片验证码,_eventId 应该是提交的意思,而最长的execution看不出有什么含义。
通过查看网页源代码,可以看到登录表单中有一项屏幕截图_20230827_140451

就是execution的内容,可以看出,每次请求页面的时候会返回execution,在登录时带着这一项提交,作用可能是防止csrf攻击(猜的)。

那么就只剩下密码这一个参数了。
如果前端通过js加密密码,必然要先从input框中先获取密码的内容,于是先找到密码框的html代码
20200816183653644
然后在source中全文搜索password关键字,发现login.js中有一段这样的代码
屏幕截图_20230827_141326

屏幕截图_20230827_141436 可以看到这段代码先进行一些字段检查,再把密码加密填写回`password`,最后再执行submit提交到后台。 继续搜索加密中用到的`RSAUtils`

再观察到201行,需要传入public_exponentModulus作为参数,继续搜索屏幕截图_20230827_142314
也找到了来源,是向v2/getPubKey发送请求后得到。

然而多次尝试后都无法登录,遂暂时放弃此方法。


8.30更新

在对整个登录过程完整抓包分析后,发现了部分遗漏的参数,即post提交时v的值。
同样使用搜索后,发现也是在网页源代码中屏幕截图_20230910_210226
同时,在每次发送请求时都会携带第一次访问时返回的cookies


9.2更新

多次尝试后,都会重定向到学校的信息门户,而不是强智的教务系统屏幕截图_20230915_110559
又经过一番分析,重定向的url收到最开始的service影响屏幕截图_20230915_111340
所以需要加上params。最后从response.history里找到最后的重定向url。

代码实现

至此,整个登录的流程已经梳理清楚了,开始使用代码实现

1.先访问页面并提取execution和v

1
2
3
4
5
6
7
8
url = "https://cas.xxx.edu.cn/lyuapServer/login"
params = {
"service": "https://jw.xxx.edu.cn/"
}
response = requests.get(url,params=params)
execution = re.search(r'name="execution" value="(.*?)"', response.text).group(1)
v_value = re.search(r'action=".*\?v=(.*)"\s', response.text).group(1)
cookies = response.cookies.get_dict()

2.获取密码加密需要的参数及加密密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def get_public_key():
url = "https://cas.xxx.edu.cn/lyuapServer/v2/getPubKey"
try:
response = requests.get(url,cookies=cookies)
modulus = response.json()['modulus']
public_exponent = response.json()['exponent']
cookies_pv0 = response.cookies.get_dict()
return modulus, exponent, cookies_pv0
except requests.exceptions.RequestException as e:
print("Error: ", e)

def encrypt_password(public_exponent, modulus, password):
password_bytes = bytes(password, 'ascii')
password_int = int.from_bytes(password_bytes, 'big')
e_int = int(public_exponent, 16)
m_int = int(modulus, 16)
result_int = pow(password_int, e_int, m_int)
encrypted_password = hex(result_int)[2:].rjust(128, '0')
return encrypted_password


modulus, public_exponent, cookies_pv0 = get_public_key()
encrypted_password = encrypt_password(public_exponent, modulus, password)

3.获取图片验证码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def get_captcha():
timestamp = int(time.time()* 1000)
url = "https://cas.xxx.edu.cn/lyuapServer/kaptcha"
params = {
"_": timestamp,
}
try:
response = requests.get(url, params=params, cookies=cookies)
if response.status_code == 200:
save_path = "captcha.png"
with open(save_path, 'wb') as file:
file.write(response.content)
print("图片已保存:", save_path)
authcode = input("请输入验证码:")
return authcode

else:
print("无法获取图片:", url)
except Exception as e:
print("错误:", e)

4.发送登录请求

1
2
3
4
5
6
7
8
9
10
11
12
13
cookies = {**cookies, **cookies_pv0}
url = "https://cas.xxx.edu.cn/lyuapServer/login"
params = {
"v": v_value,
}
data = {
'username': username,
'password': encrypted_password,
'authcode': authcode,
'execution': execution,
'_eventId': 'submit'
}
response = requests.post(url, params=params, data=data, cookies=cookies)

5.重定向到教务系统

1
2
3
4
5
6
cookies = response.history[3].cookies
url = "https://jw.xxx.edu.cn/jsxsd/framework/xsMain_new.jsp"
params = {
"t1" : "1"
}
response = requests.get(url, cookies=cookies, params=params)

完整代码

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
78
79
80
81
82
import requests
import re
import time


url = "https://cas.xxx.edu.cn/lyuapServer/login"
params = {
"service": "https://jw.xxx.edu.cn/"
}
response = requests.get(url,params=params)
execution = re.search(r'name="execution" value="(.*?)"', response.text).group(1)
v_value = re.search(r'action=".*\?v=(.*)"\s', response.text).group(1)
cookies = response.cookies.get_dict()

def get_public_key():
url = "https://cas.xxx.edu.cn/lyuapServer/v2/getPubKey"
try:
response = requests.get(url,cookies=cookies)
modulus = response.json()['modulus']
public_exponent = response.json()['exponent']
cookies_pv0 = response.cookies.get_dict()
return modulus, public_exponent, cookies_pv0
except requests.exceptions.RequestException as e:
print("Error: ", e)

def encrypt_password(public_exponent, modulus, password):
password_bytes = bytes(password, 'ascii')
password_int = int.from_bytes(password_bytes, 'big')
e_int = int(public_exponent, 16)
m_int = int(modulus, 16)
result_int = pow(password_int, e_int, m_int)
encrypted_password = hex(result_int)[2:].rjust(128, '0')
return encrypted_password

def get_captcha():
timestamp = int(time.time()* 1000)
url = "https://cas.xxx.edu.cn/lyuapServer/kaptcha"
params = {
"_": timestamp,
}
try:
response = requests.get(url, params=params, cookies=cookies)
if response.status_code == 200:
save_path = "captcha.png"
with open(save_path, 'wb') as file:
file.write(response.content)
print("图片已保存:", save_path)
authcode = input("请输入验证码:")
return authcode
else:
print("无法获取图片:", url)
except Exception as e:
print("错误:", e)

username = '' # 填入你的用户名
password = '' # 填入你的密码
modulus, public_exponent, cookies_pv0 = get_public_key()
authcode = get_captcha()
encrypted_password = encrypt_password(public_exponent, modulus, password)


cookies = {**cookies, **cookies_pv0}
url = "https://cas.xxx.edu.cn/lyuapServer/login"
params = {
"v": v_value,
}
data = {
'username': username,
'password': encrypted_password,
'authcode': authcode,
'execution': execution,
'_eventId': 'submit'
}
response = requests.post(url, params=params, data=data, cookies=cookies)

# 强智
cookies = response.history[3].cookies
url = "https://jw.xxx.edu.cn/jsxsd/framework/xsMain_new.jsp"
params = {
"t1" : "1"
}
response = requests.get(url, cookies=cookies, params=params)

旧教务系统

偶然发现在教务系统退出后会被重定向到旧版的教务系统处,似乎所有强智教务都有这个入口。屏幕截图_20230827_161246.png
输入账号密码进行登录
————失败。提示 用户名或密码错误,
————忘记密码也不可使用。提示 该帐号没有设置密码找回信息,无法完成该操作!


8.30更新
尝试了众多密码后,我意识到,也许旧教务系统内储存的密码可能和OA系统内的不一致,于是我去学校网站上搜索新教务系统,有了一些眉目。
关于征集正方教务系统使用意见的通知
软件集成服务-信息化办公室
教务处召开学期选课工作视频协调会
根据这些信息,我推测我校在16年之前都是使用的正方教务,在17年更换了强智教务,随后又在20年引入OA系统。


9.3更新
根据上述推测,我认为在引入OA系统后,旧教务系统可能没有及时更新,储存的是默认密码。
而教职工及学校的学工号都是进校年份,我觉得20年之后入学的都为默认密码。
经测试,的确和我想的一样,以2020打头的学工号都可以用默认密码登录。

分析登录请求

不妨大胆猜测,旧教务系统的默认密码就是学号。
屏幕截图_20230915_134033
成功登入,并进入修改密码页面。的确,在修改密码之后,可以绕过OA系统,直接进入教务系统。

至此,剩下的内容就比较简单了。同样打开F12,输入账号密码进行测试。
会看见userAccount和encoded被提交到了一个LoginToXk的接口,提交的数据也就只有账号密码,账号明文而encoded暂不知,猜测是某种加密,好在加密方式比较好找,源代码里直接就能找到。
屏幕截图_20230915_134854
定位到函数位置。

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
var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
function encodeInp(input) {
var output = "";
var chr1, chr2, chr3 = "";
var enc1, enc2, enc3, enc4 = "";
var i = 0;
do {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64
} else if (isNaN(chr3)) {
enc4 = 64
}
output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4);
chr1 = chr2 = chr3 = "";
enc1 = enc2 = enc3 = enc4 = ""
} while (i < input.length);
return output
}

明显,这是一个base64编码。然后encoded也在下方做了说明var encoded = account + "%%%" + passwd;,

代码实现

所以直接写个函数,将encoded进行base64编码,然后返回。

1
2
3
4
5
6
7
8
9
10
11
12
import base64

def encodeInp(input):
encoded_bytes = base64.b64encode(input.encode('utf-8'))
output = encoded_bytes.decode('utf-8')
return output

def encode(xh, psw):
account = encodeInp(xh)
passwd = encodeInp(psw)
encoded = account + "%%%" + passwd
return encoded

接下来简单写个请求就完成了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
url = "https://jw.xxx.edu.cn/jsxsd/"
response = requests.get(url)
cookies = response.cookies.get_dict()

xh = input("账号:")
psw = input("密码:")
encoded = encode(xh, psw)

url = "https://jw.xxx.edu.cn/jsxsd/xk/LoginToXk"
data = {
"userAccount": xh,
"userPassword": "",
"encoded": encoded,
"pwdstr1": "",
"pwdstr2": "",
}
response = requests.post(url, data=data, cookies=cookies)

至此,教务系统的模拟登录就结束了。

评论