学工系统做得真丑,所以我对学校从登录到晚打卡的api调用流程,进行了抓包逆向的研究分析。

我们学校使用了SSO方案进行统一登录认证,也就是在访问学校各个系统之前,需要在一个 统一身份认证平台(即融合门户) 进行统一登录。只要一次登录就能访问各个系统(比如教务系统、学工系统)。要晚打卡,则需要先保证用户在CAS系统已登录的情况下,才能在学工系统登录并打卡。

本文还未编辑完全

因为我的账号开始总是不需要短信验证了,所以就不知道(丢了抓包日志)二重验证相关的API的响应完整该长啥样了。以后有机会短信验证了再补上。

开始分析

由于学工系统的定位功能似乎调用了微信sdk,所以电脑浏览器是定位不了的,而且不成功定位就不能打卡,也就不能抓包打卡api,也就意味着我必须用实体手机在微信内置浏览器里进行打卡。我试过这样一种方法:

  1. 电脑开启Charles抓包工具
  2. 手机安装movecerts这个Magisk模块安装Charles的SSL证书
  3. 手机LSPosed开启TrustMeAlready和JustTrustMe模块强制令软件信任Charles的证书

坑爹的是,手机其他任何程序都能抓包,只有微信内置浏览器不能抓包。后来发现可能是因为微信内置浏览器使用了SSL Pinning机制,Charles的自签名SSL证书无法绕过这个机制。

因此,我使用了DevTools的inspector工具对微信内置浏览器进行远程调试,在电脑Chrome的DevTools通过网络面板查看api调用情况。

首先,手机微信访问http://debugxweb.qq.com/?inspector=true激活微信的网页调试,然后用usb连接手机,手机打开usb调试,就可以在电脑上面使用chrome访问chrome://inspect/#devices进行远程调试了。

image-20251229201738302

(图为晚打卡API请求体)

然而,在inspector下,某些请求只显示预配标头(后来发现,与真实请求头的区别只是少了Cookie字段):

image-20251228145039972

按网上说的禁用缓存等方法依旧没啥用。幸运的是,打卡的关键api不会这样。所以,对于登录流程的逆向,还是得用Chrome的DevTools才行。

另外,我也依旧用了Charles进行抓包辅助,还通过Claude Code接入了chrome-devtools-mcp帮忙分析。

登录和打卡的api并不复杂,所以具体过程就不细说了。经过抓包逆向分析,API的流程大概如下。

API总体流程概述

登录融合门户,大体流程是:先调用/cas/mfa/detect检查是否需要双因素验证(一般是手机验证码) -> 调用/cas/mfa/initByType/securephone获取gid -> 调用/attest/api/guard/securephone/send发送短信验证码(传入gid) -> 调用/attest/api/guard/securephone/valid验证短信验证码(传入gid) -> 调用/cas/login登录

登录学工系统,大体流程是:访问学工系统 -> 302至CAS(附加查询参数service,值为来源url) -> CAS系统获取Cookie的TOC字段,判断是否登录 -> 再次302到学工系统(目标url追加ticket这个查询参数)-> 学工系统写入JSESSIONID这个cookie字段 -> 成功登录学工系统

进行晚打卡,则是直接POST https://xgyd.mku.edu.cn/acmc-weichat/wxapp/swkjjksb/mrdk_save.do即可。

具体原理

登录 - cas.mku.edu.cn

登录的具体逻辑,直接位于登录页面body里的script标签中。

image-20251229210424715

POST /cas/mfa/detect

Content-Type: application/x-www-form-urlencoded

请求体

1
username=用户名&password=RSA加密后的密码

这里的密码,是通过/cas/jwt/publicKey提供的公钥使用PKCS1_v1_5算法加密的,并使用base64编码,之后在前面加上__RSA__前缀。

响应

1
{"code":0,"data":{"mfaTypeSecurePhone":true,"mfaTypeQrCode":false,"need":false,"mfaTypeAppPush":false,"mfaTypeFaceVerify":false,"mfaEnabled":true,"state":"TtMomI","mfaTypeSecureEmail":true}}

如果”need”:true,则需要手机短信验证码验证。如果”need”:false,就可以直接登录,跳过以下的短信验证部分。

GET /cas/mfa/initByType/securephone

查询参数

state: 在前面/cas/mfa/detect获取到的data.state

响应

1
(TODO)

如果不需要短信验证,响应

1
{"code":-1,"error":{"message":"fail_0"}}

POST /attest/api/guard/securephone/send

Content-Type: application/json

请求体

1
2
3
{
"gid": "前面获取到的gid"
}

响应

1
(TODO)

POST /attest/api/guard/securephone/valid

请求体

1
2
3
4
{
"code":"短信验证码",
"gid":"前面获取到的gid"
}

响应

1
(TODO)

POST /cas/login

进行正式的登录

Content-Type: application/x-www-form-urlencoded

请求体(虽然是表单格式,但是为了看着清晰,这里改用字典表示):

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"username": 用户名,
"password": RSA加密后密码,
"captcha": "",
"currentMenu": "1",
"failN": "-1",
"mfaState": 在前面`/cas/mfa/detect`获取到的`data.state`,
"execution": 在`/cas/login`获取到的execution,
"_eventId": "submit",
"geolocation": "",
"fpVisitorId": ""(浏览器指纹,可留空),
"submit1": "Login1"
}

execution的值在/cas/login登录页面(也就是说得先获取一下网页源码)的name为execution的表单字段里:

1
<input type="hidden" name="execution" value="c5087400-a375-41ca-9118-100d9d1d284c_ZXlKaGJHY2lPaUpJVXpVeE1pSjkueXpaeG55NWh。。。此处省略一大坨。。。">

响应为 200 就是登录成功,返回登陆成功的html。

GET /cas/login

参数

service:目标服务的url,比如说要访问的学工系统的URL

响应:302到service指定url上,附加ticket查询参数,例如?ticket=ST-237915-rHwNNRG72-W6VlZknzOE1gjtnO5qU0Du

实际上,直接访问学工系统就会302到当前所述的API上,然后带着ticket302到service指定url,最后写入cookie(JSESSIONID),并不带ticket原地重新302

image-20251230000803686

打卡 - xgyd.mku.edu.cn

POST /acmc-weichat/wxapp/swkjjksb/mrdk_save.do

Content-Type: application/x-www-form-urlencoded

请求体(虽然是表单格式,但是为了看着清晰,这里改用字典表示):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"id": "",
"xsid": 获取的xsid,
"jd": 118.47673,
"wd": 25.03694,
"dqszd": 350583,
"drsfzxid": 1,
"sbrq": "2025-12-29",
"dqszdmc": "福建省泉州市南安市",
"tw": 36.5,
"dqszdxxdz": "康美校区",
"ycms": "",
"twid": 1,
"jzkid": 1
}

xsid得先获取/acmc-weichat/wxapp/swkjjksb/mrdk_edit打卡页面的网页源码,在name为xsid的表单字段里:

1
<input type="hidden" id="xsid" value="F8C8743F99********28D9831DCD81FF" autocomplete="off">

响应

{"ret":"ok"}:打卡成功

{"ret":"more"}:重复打卡

打卡系统后端存在的问题

逆向后发现了这些毛病,特别是第一点,也是奇异搞笑。

  1. 后端没有对打卡时间做合法性验证,只有前端做了。导致可以做到一整天24h随时打卡,甚至还能给明天、后天、以后打卡
  2. 历史遗留疫情时期的体温相关字段,没啥意义
  3. 请求字段全是拼音缩写,毫不直观,全靠猜