在用数据包的形式进行登录之前,其实我是用selenium已经实现了网站登录后跳转至服务界面,拿到服务界面的JSESSIONID,虽然选用了anaconda进行环境管理在环境移植方面有很大的便利,但是还必须确保具有Chrome以及ChromeDriver的驱动版本也要与Chrome本身的版本相适应,综上来看当前的解决方案是不够优雅,所以想用发送数据的形式来获得最终的JSESSIONID

通过发数据包的形式模拟学校信息化门户网站的登录,这里主要是为了下游的任务做准备,这个任务没有什么技术含量,这里只对模拟登录进行简单的记录。

查看请求登录页面的数据包

登录页面请求表头

这是请求的表头,可以看到第一次请求,没有cookie情况下不用发送任何cookie信息,(accept这一部分没有截图)

image-20220501082930425

登陆页面请求响应

image-20220501083140177

这里set了一个JSESSIONIDCookie,肯定不是我们最终要的,只是在进行验证之前的一个会话的状态。

JS

同时,还在查看数据流的时候发现了一系列js的加载。可以看到jQuery这必须要用到不用看,以及qrcode是二维码相关的也可以不用看,可以看一下des.jslogin.js

image-20220501083412162

image-20220501083425113

可以看到des.js应该是用于加解密的。

image-20220501083728977

查看登录页面前端

前端表单

首先进入到登录页面,找到向后端传递数据的前端表单。

1
2
3
4
5
<form id="loginForm" action="/cas/login" method="post">
<div class="floor2">
<div class="content_login_box">
<div class="login_box_tab">

其实这里有用的东西不太多,还是要向下看

1
2
3
<input class="login_box_input" id="un" tabindex="1"  value="" autocomplete="off" placeholder="用户名"/>

<input class="login_box_input" id="pd" tabindex="2" type="password" autocomplete="off" placeholder='密码'/>

接着向下看,可以看到类似于payload中所携带的数据字段一样, 其中 id="lt"这个字段会随着页面的重新请求而发生变化。

这里犯了一个错误,因为我请求了好几次都发现name="execution"value="e1s1",所以我就在数据包中写死了,但是他是变化的。

1
2
3
4
5
6
<input type="hidden" id="rsa" name="rsa"/>
<input type="hidden" id="ul" name="ul"/>
<input type="hidden" id="pl" name="pl"/>
<input type="hidden" id="lt" name="lt" value="LT-2995996-cLwZrOclQLw6zgkmNgc4vslKRCGXk7-cas" />
<input type="hidden" name="execution" value="e1s1" />
<input type="hidden" name="_eventId" value="submit" />

分析js

因为在前端的登录按钮 button上没有看到onClick,所以应该是通过刚才的login.js向后传递的数据。

1
<input type="button" class="login_box_landing_btn"  id="index_login_btn">账号登录

打开数据流的login.js查看,发现了login的函数,这个函数的主要作用就是,拿到前端id为unpd的值,然后进行计算到,ulpl以及最重要的rsa

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
function login(){
var $u = $("#un") , $p=$("#pd");

var u = $u.val();
if(u==""){
$u.focus();
$u.parent().addClass("login_error_border");
return ;
}

var p = $p.val();
if(p==""){
$p.focus();
$p.parent().addClass("login_error_border");
return ;
}

$u.attr("disabled","disabled");
$p.attr("disabled","disabled");

var lt = $("#lt").val();

$("#ul").val(u.length);
$("#pl").val(p.length);
$("#rsa").val(strEnc(u+p+lt , '1' , '2' , '3'));

$("#loginForm")[0].submit();

}

可以看到rsa的计算通过了strEnc的函数,很眼熟,刚刚在dec.js里看到了。

JS断点调试

image-20220501085429285

前端可以存储的js有这几个,没有login.js, 所以退而求其次,只能在dec.js中打断点进行调试,我是打在strEnc这个函数返回的地方,这样他肯定会往回跳到login.js.

image-20220501085649853

接着点击账号登录的button

看到加密的datausername + password + lt,加密后大概是这个样子(有删减)

1
'81C9A0B423FD7EA55FB64886E9AAA673641A9FA451F32121FB7D22140B14CA95F1AF2D2FC79C19EFF92299C3C06290A516A61AB18534BC42E3ABCBD0E31E94065CB216DA1096824A2F30CCD8C358E72DE68780116D0D15388928E199B233C54B84168C9D93D0997407A3BEA4415AE18BD66399FA40DABD1D7C58AB359'

继续往下跳,果然回到login.js

image-20220501090101772

接下来也不过多的进行分析,直接跳过整个过程,向后端发送计算的数据,查看数据包。

分析认证的请求数据

post数据流分析

image-20220501090248876

request header

image-20220501090328275

request data

image-20220501090435203

首先这个包进行了重定向,另外请求包中携带了第一次请求时服务器传来的JSESSIONID,可以看到数据包的字段基本与前面分析的一致。

response header

image-20220501091013339

这里包含了重定向的地址,有几个重要的Cookie用于跨域请求。

用python实现第一个认证

输入认证的简单信息

image-20220501091220888

dec.js

这个我起初是想用python直接运行这个js文件的,但是需要nodejs环境,也不说我没有,但是把又要有这个环境就显得很不优雅,所以我用python实现了这个js里面的所有的功能, 大概有800多行代码也不是很多。但是一步一步验证关键数据的过程是很复杂的,在浏览器断点调试与python一起,借用operator.eq()判断两个值是不是相同,写倒是没花多长时间,主要验证的时间比较多。起初我以为这是最麻烦的一步了,但是我还是太天真了。

image-20220501091435220

第一次访问登陆页面以及数据获取

request header

直接复制就可以,这里有一个小技巧,就是可以在chrome复制到postman里面,然后转python代码。

image-20220501092028775

这是一个get请求,也不用什么特别的。

1
response = requests.get(url=self.url, headers=headers, data=payload)

这个请求最重要的是response

要拿到里面的cookie、lt、execution、_enventId这些东西

  • cookie是最好拿到的
1
2
3
cookies = list(response.cookies)
self.cookie_adx = cookies[1].name + "=" + cookies[1].value
self.jsessionid = cookies[2].name + "=" + cookies[2].value
  • lt

在返回的html页面中,需要用正则

1
2
regex = re.search(r'LT-.*-cas', str(response.content.decode('utf-8')))
self.lt = regex.group(0)
  • execution、_enventId

也是在html页面,很相似

1
2
3
4
5
6
7
excure = re.search(r'name="execution".*"', str(response.content.decode('utf-8')))
excall = excure.group(0)
self.execution = excall[-5:-1]

envent = re.search(r'name="_eventId".*"', str(response.content.decode('utf-8')))
enventall = envent.group(0)
self._eventId = enventall[-7:-1]

登录数据包的构造与发送

  • header

直接复制, 但是要修改其中的cookie部分。

image-20220501132833128

  • username+password+lt进行加密

image-20220501093135196

  • 构造payload data
1
payload = f'rsa={self.rsa}&ul={self.ul}&pl={self.pl}&lt={self.lt}&execution={self.execution}&_eventId={self._eventId}'

发送数据包

1
response = requests.post(self.url, data=payload, headers=headers, verify=False)

从以上请求的response拿数据

首先,通过浏览器的数据已经明确的知道这是一个重定向的请求,另外已经知道设置了4个cookie其中有两个用于跨域的身份认证。

但是在这里出现了问题,我一直得到的是response status 200而不是302,同时有302response但是收到的cookie的数量不对,也不包含Location,这里反过头来去检查加密以及数据的问题,发现可能存在的两个问题。

  • execution、_enventId的可能变化
  • data构造,起初我使用的是字典,后来变成了字符串直接构造

这里还是会收到200状态码,但是history中是存在302的这种就可以

image-20220501093940926

image-20220501094001196

同时我们只关注history中的数据就可以了

response.headers._store 中看到拿到了所有想要的cookie

image-20220501094302394

Cookie保留下来

image-20220501094400746

到这里就拿到了所有想要的身份认证的信息。

分析对下游服务的请求和身份认证

还是通过浏览器先对包进行分析, 但是这个跨域请求存在页面跳转,所以使用了抓包软件fiddle classic进行抓包分析。

发现要携带一个ticket

当然这是重定向得到的。

1
apply.html?ticket=ST-23367-GdGrhNopWhjmXSUL-cas

大概过程是这样:

通过携带我们上面身份信息,去请求服务。而这个服务是通过下面的方式进行。通过这个拿到一个类似令牌一样的ticket实现身份认证的内容,然后拿到一个固定的cookie进行接下来的认证。

1
?service=http://XXX.XXX

python获取服务认证ID

  • header

其中 self.formCookies[3]是上一次拿到的最重要的一个身份认证的信息。

image-20220501100031581

  • 发送数据包
1
serviceResponse = self.session.get(url=serviceUrl, headers=simpleHeader, allow_redirects=True, verify=False)

因为这也是一个重定向的,所以不用考虑中间拿到的ticket的过程,只要在history中的302的信息中拿到这个服务的JSESSIONID就可以。

1
2
appCookie = dict(serviceResponse.history[1].cookies)
finalCookie = "JSESSIONID=" + appCookie.get("JSESSIONID")

然后通过finalCookie就可以完成下游任务了。