学校更新了校园网之后,用的宽带就需要每天早上进行一遍网页登录才能有网,非常的麻烦,我就萌生出了想写个模拟登录的脚本的想法。

抓包获得请求方式

这里我用wireshark抓包,只抓从登录到登录成功这个时间段的包,这里主要分析我们发送的http的流量包。

可以发现主要有两个流量包出现了username字段,那么主要分析这两个包内容的参数。第一个流量包内容如下

请求api为http://10.110.74.99/cgi-bin/get_challenge

主要有四个参数,callbackusernameip,和_。callback参数不太确定,但是可以确定username是自己登录校园网的账号,ip就是自己本机在这个局域网下的ip,而_很明显就是时间戳。而可以看到call_back后面也有一个类似时间戳的参数。这个暂且不确定,但是后面反复抓包可以发现,这个参数就是固定的。至于本次请求是返回了什么我们可以照着参数打进去看看返回了什么数据,本人很菜,不是打web的,不会用burpsuite只能用这种办法了。

可以发现返回了一串json数据,里面主要有一个challenge字段,感觉是一个类似token的东西,其它的就没什么了,客户端ip,服务端ip,请求状态,产品版本和时间戳。而这里也方便了我们,可以直接从这里获取自己的ip了,但是网页端做这个估计主要还是为了这个token。但是这里并没有密码字段,还没有登录请求。

剩下的就看看另一个流量包

第二个流量包请求的api为http://10.110.74.99/cgi-bin/srun_portal

请求的参数很多,首先依旧是callback,跟之前一样,action参数估计是标识本次请求类型为登录,第四个参数开始出现了密码字段,而这个很明显进行了md5加密,os指示操作系统类型,name为大概也是操作系统名称的意思。还有一个double_stack,虽然不懂但是反复抓包发现这个参数也是恒定不变是0,所以也不用去理解它的意思了。chksum参数是一串可能经过某种hash算法得到的值。info同样如此,只是它括起来的这个加密算法着实没听过。然后剩下的参数都是固定的就没必要去纠结了。

所以想要成功模拟登录,必须得到这些参数,需要计算的就只有password,chksum,info三个参数,那么去分析一下网页源码看看这三个参数是怎么得到的。

分析网页js

F12查看,主要寻找登录按钮点击之后干干了什么

password加密

可以看到只对前端进行了基本的校验之后调用了portal的login方法去登录,并且输入的用户名和密码也都传进了portal相应的变量当中,那么就去portal.js文件中分析。

发现文件很大,选择拷贝一份放ide中分析,寻找login函数,

可以发现这里主要是进行登录类型判断,进行选择,那就接着追踪。

可以发现是用一个_loginAccount函数去登录的,而这个函数就在下面,我们看看它的逻辑。

这里主要有一个getToken函数,然后密码用password和token进行了md5加盐的hash加密,因此password字段就很容易得出来了,那么我们接着看看token怎么获取的,之前我们通过手动请求猜测challenge字段是可能是token,这次我们跟踪_getToken函数看看。

发现这里返回了res.challenge,那么就可以大胆推定刚刚的challenge就是token了。那么登录的流程应该就是,先去请求一个token,然后用token进行加密登录。这里先给出md5加密的脚本,这里参考了别的师傅的博客的做法,在这里---->传送门,我也一直没理解它盐是怎么加的。

1
2
3
4
5
#srun_md5.py
import hmac
import hashlib
def get_md5(password,token):
return hmac.new(token.encode(), password.encode(), hashlib.md5).hexdigest()

chksum和info加密

翻到后面可以直接看到url的请求参数。

url可以直接抓包获得,这个没必要去分析它的url是啥,主要看我们之前需要知道的三个字段,这里第二点不太明白了,虽然看到它的挡路方式好像是OTP,但是抓包获得的一直是{md5},也不太懂为啥,但是我们姑且选择后者,毕竟实践才是检验真理的唯一标准嘛。

这里password的hmd5我们前面已经可以算了,接下来这个info的参数是i,chksum的参数是sha1(str),对str进行了sha1加密。那么我们往上看看这个i和这个str是怎么获得的。

这里可以看到str只是对一些参数进行了相加,最后sha1散列。

那么如此一来我们只需要得到i就可以把整个url的参数构造完成了。

可以看到是调用了_encodeUserInfo函数去加密得到i的,那么我们进一步搜索这个函数的加密方式。

这里我直接贴出来这个加密的函数了。

加密实现脚本

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
_encodeUserInfo.set(_assertThisInitialized(_this), {
writable: true,
value: function value(info, token) {
// 克隆自 $.base64,防止污染
var base64 = _this.clone($.base64); // base64 设置 Alpha


base64.setAlpha('LVoJPiCN2R8G90yg+hmFHuacZ1OWMnrsSTXkYpUq/3dlbfKwv6xztjI7DeBE45QA'); // 用户信息转 JSON

info = JSON.stringify(info);

function encode(str, key) {
if (str === '') return '';
var v = s(str, true);
var k = s(key, false);
if (k.length < 4) k.length = 4;
var n = v.length - 1,
z = v[n],
y = v[0],
c = 0x86014019 | 0x183639A0,
m,
e,
p,
q = Math.floor(6 + 52 / (n + 1)),
d = 0;

while (0 < q--) {
d = d + c & (0x8CE0D9BF | 0x731F2640);
e = d >>> 2 & 3;

for (p = 0; p < n; p++) {
y = v[p + 1];
m = z >>> 5 ^ y << 2;
m += y >>> 3 ^ z << 4 ^ (d ^ y);
m += k[p & 3 ^ e] ^ z;
z = v[p] = v[p] + m & (0xEFB8D130 | 0x10472ECF);
}

y = v[0];
m = z >>> 5 ^ y << 2;
m += y >>> 3 ^ z << 4 ^ (d ^ y);
m += k[p & 3 ^ e] ^ z;
z = v[n] = v[n] + m & (0xBB390742 | 0x44C6F8BD);
}

return l(v, false);
}

function s(a, b) {
var c = a.length;
var v = [];

for (var i = 0; i < c; i += 4) {
v[i >> 2] = a.charCodeAt(i) | a.charCodeAt(i + 1) << 8 | a.charCodeAt(i + 2) << 16 | a.charCodeAt(i + 3) << 24;
}

if (b) v[v.length] = c;
return v;
}

function l(a, b) {
var d = a.length;
var c = d - 1 << 2;

if (b) {
var m = a[d - 1];
if (m < c - 3 || m > c) return null;
c = m;
}

for (var i = 0; i < d; i++) {
a[i] = String.fromCharCode(a[i] & 0xff, a[i] >>> 8 & 0xff, a[i] >>> 16 & 0xff, a[i] >>> 24 & 0xff);
}

return b ? a.join('').substring(0, c) : a.join('');
}

return '{SRBX1}' + base64.encode(encode(info, token));
}
});

这里也得感谢这位大佬将这个js转成了python脚本,我就直接贴结果了,里面还有一个很明显的base64,这里我也不自己写了,直接贴吧哈哈哈。

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
#srun_xencode.py
import math
def force(msg):
ret = []
for w in msg:
ret.append(ord(w))
return bytes(ret)
def ordat(msg, idx):
if len(msg) > idx:
return ord(msg[idx])
return 0
def sencode(msg, key):
l = len(msg)
pwd = []
for i in range(0, l, 4):
pwd.append(
ordat(msg, i) | ordat(msg, i + 1) << 8 | ordat(msg, i + 2) << 16
| ordat(msg, i + 3) << 24)
if key:
pwd.append(l)
return pwd
def lencode(msg, key):
l = len(msg)
ll = (l - 1) << 2
if key:
m = msg[l - 1]
if m < ll - 3 or m > ll:
return
ll = m
for i in range(0, l):
msg[i] = chr(msg[i] & 0xff) + chr(msg[i] >> 8 & 0xff) + chr(
msg[i] >> 16 & 0xff) + chr(msg[i] >> 24 & 0xff)
if key:
return "".join(msg)[0:ll]
return "".join(msg)
def get_xencode(msg, key):
if msg == "":
return ""
pwd = sencode(msg, True)
pwdk = sencode(key, False)
if len(pwdk) < 4:
pwdk = pwdk + [0] * (4 - len(pwdk))
n = len(pwd) - 1
z = pwd[n]
y = pwd[0]
c = 0x86014019 | 0x183639A0
m = 0
e = 0
p = 0
q = math.floor(6 + 52 / (n + 1))
d = 0
while 0 < q:
d = d + c & (0x8CE0D9BF | 0x731F2640)
e = d >> 2 & 3
p = 0
while p < n:
y = pwd[p + 1]
m = z >> 5 ^ y << 2
m = m + ((y >> 3 ^ z << 4) ^ (d ^ y))
m = m + (pwdk[(p & 3) ^ e] ^ z)
pwd[p] = pwd[p] + m & (0xEFB8D130 | 0x10472ECF)
z = pwd[p]
p = p + 1
y = pwd[0]
m = z >> 5 ^ y << 2
m = m + ((y >> 3 ^ z << 4) ^ (d ^ y))
m = m + (pwdk[(p & 3) ^ e] ^ z)
pwd[n] = pwd[n] + m & (0xBB390742 | 0x44C6F8BD)
z = pwd[n]
q = q - 1
return lencode(pwd, False)



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
#srun_base64.py
_PADCHAR = "="
_ALPHA = "LVoJPiCN2R8G90yg+hmFHuacZ1OWMnrsSTXkYpUq/3dlbfKwv6xztjI7DeBE45QA"
def _getbyte(s, i):
#print(s,' ',i)
x = ord(s[i]);
if (x > 255):
print("INVALID_CHARACTER_ERR: DOM Exception 5")
exit(0)
return x
def get_base64(s):
i=0
b10=0
x = []
imax = len(s) - len(s) % 3;
if len(s) == 0:
return s
for i in range(0,imax,3):
b10 = (_getbyte(s, i) << 16) | (_getbyte(s, i + 1) << 8) | _getbyte(s, i + 2);
x.append(_ALPHA[(b10 >> 18)]);
x.append(_ALPHA[((b10 >> 12) & 63)]);
x.append(_ALPHA[((b10 >> 6) & 63)]);
x.append(_ALPHA[(b10 & 63)])
i=imax
if len(s) - imax ==1:
b10 = _getbyte(s, i) << 16;
x.append(_ALPHA[(b10 >> 18)] + _ALPHA[((b10 >> 12) & 63)] + _PADCHAR + _PADCHAR);
elif len(s) - imax == 2:
b10 = (_getbyte(s, i) << 16) | (_getbyte(s, i + 1) << 8);
x.append(_ALPHA[(b10 >> 18)] + _ALPHA[((b10 >> 12) & 63)] + _ALPHA[((b10 >> 6) & 63)] + _PADCHAR);
return "".join(x)

1
2
3
4
#srun_sha1.py
import hashlib
def get_sha1(value):
return hashlib.sha1(value.encode()).hexdigest()

逻辑这么分析到位了之后接下来就开始写两次抓包的脚本吧,这里也直接给了,毕竟逻辑就真的这么点,主要的工作量都在js转python上面,而已经有人转好了我就直接拿过来用了,这里再次鸣谢huxiaofan1223大佬,本篇博客也贴在上面的传送门当中了。

模拟登录

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import requests
import time
import re
from urllib.parse import quote
from encryption.srun_md5 import *
from encryption.srun_sha1 import *
from encryption.srun_base64 import *
from encryption.srun_xencode import *
header={
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36'
}
init_url="http://10.110.74.91"
get_challenge_api="http://10.110.74.91/cgi-bin/get_challenge"

srun_portal_api="http://10.110.74.91/cgi-bin/srun_portal"
n = '200'
type = '1'
ac_id='4'
enc = "srun_bx1"
def get_chksum():
chkstr = token+username
chkstr += token+hmd5
chkstr += token+ac_id
chkstr += token+ip
chkstr += token+n
chkstr += token+type
chkstr += token+i
return chkstr
def get_info():
info_temp={
"username":username,
"password":password,
"ip":ip,
"acid":ac_id,
"enc_ver":enc
}
i=re.sub("'",'"',str(info_temp))
i=re.sub(" ",'',i)
return i
def init_getip():
global ip
init_res=requests.get(init_url,headers=header)
print("初始化获取ip")
#print(init_res.text)
ip=re.search('ip : "(.*?)"',init_res.text).group(1)
print("ip:",ip)
def get_token():
# print("获取token")
global token
get_challenge_params={
"callback": "jQuery112406608265734960486_"+str(int(time.time()*1000)),
"username":username,
"ip":ip,
"_":int(time.time()*1000),
}
get_challenge_res=requests.get(get_challenge_api,params=get_challenge_params,headers=header)
token=re.search('"challenge":"(.*?)"',get_challenge_res.text).group(1)
print(get_challenge_res.text)
print("token为:"+token)
def do_complex_work():
global i,hmd5,chksum
i=get_info()
i="{SRBX1}"+get_base64(get_xencode(i,token))
hmd5=get_md5(password,token)
chksum=get_sha1(get_chksum())
print("所有加密工作已完成")
def login():
srun_portal_params={
'callback': 'jQuery11240645308969735664_'+str(int(time.time()*1000)),
'action':'login',
'username':username,#username,
'password':'{MD5}'+hmd5,
'ac_id':ac_id,
'ip':ip,
'chksum':chksum,
'info':i,
'n':n,
'type':type,
'os':'windows+10',
'name':'windows',
'double_stack':0,
'_':int(time.time()*1000)
}
print(srun_portal_params)
srun_portal_res=requests.get(srun_portal_api,params=srun_portal_params,headers=header)

if 'ok' in srun_portal_res.text:
print('登陆成功')
else:
error_msg=eval(re.search('\((.*?)\)',srun_portal_res.text).group(1))
#输出错误信息
print('error_type:'+error_msg['error'])
print(error_msg['error_msg'])


if __name__ == '__main__':
global username,password
username=""#你的用户名和密码,注意加上@cmcc(移动) 或者@chinanet(电信),联通是啥就忘了。。
password=""
init_getip()
get_token()
do_complex_work()
login()

本学生来自嘉兴学院,同校校友可以换上自己的用户名和密码直接用,如果不是的话把url替换一下应该问题也不大。

以后上网终于不用再输用户名密码了,只能说爽(狗头