一、CDU-Lib-Robot项目实现过程
前言
在某个夜晚,图书馆回来的ahao,突然有了新的idea:能不能实现图书馆座位系统全自动预约和签到签退呢?于是回到寝室便开始构思,一直到深夜。。。。。。 最后的结果是:ahao成功失眠了,这里省略十万个'cnm'又失眠了。
不过还是没有丝毫影响到第二天ahao,7点30起床,扛上我的小新pro去图书馆开始各种试探。
耗时三天的CDU-Lib-Robot
项目的构建由此拉开序幕。。。。。。。。。。。。
项目地址:
GitHub链接: https://github.com/ahaox/CDU-Lib-Robot
Gitee链接: https://gitee.com/ahaox/CDU-Lib-Robot
代码仓已关闭,暂时不开放,如有需要请私聊ahao。VX:SystemUA
废话不多说,切入正题。
1. 使用到的工具
小新13pro
+ Burp Suit
+ 火狐浏览器
+ PyCharm
+ Charles
2. 实现思路
登录VPN 获取到Cookie ——> 携带VPN的Cookie访问图书馆座位预约系统 和 签到系统 ——> 构造各种需要的请求参数 ——> 座位预约,签到,签退
二、抓包分析
1. 校园VPN系统
因为图书馆的座位预约系统和签到系统,必须要使用校园网才能访问,非校园网是不能访问的。
好在学校提供的专门的VPN服务,即通过web端的VPN登录以后,可以访问校内资源(图书馆座位系统 和 签到系统)。
先看看学校的VPN系统,盲猜,VPN登录成功后,访问校园内的所有网站都会携带上 认证后返回的Cookie。

①开始抓包

还没有登录,还看不出哪个Cookie有用。
② 开始VPN登录抓包
好像返回信息,也没啥有用的,不过有注意到,请求头Cookie中携带了一个 TWFID,返回的XML中也有TwfID,而且两个的值一模一样。好奇心开始产生,这个TwfID 应该就是我们需要的VPN认证后的Cookie了。
继续分析, 一般登录提交都是POST方式,但是我们刚刚登录抓的包却是GET方式,Why?这是不是感觉非常奇怪。
我想一定是姿势不对,换个姿势。哈哈哈,火狐浏览器的开发工具登上分析舞台。
打开开发工具里的Network,重新登录,果然发现了唯一个POST提交方式,

点开瞧一瞧。一看请求头,又是这个TWFID,我的疑惑似乎已经快要落实了。

继续看请求表单,看到svpn_name是学号,但是这个randcode哪里来的??还有这个密码怎么这么长???
根据多年实战经验,这个svpn_randcode
和 svpn_password
是在POST提交 之前获取到的。
看看响应回来的内容,又看到这个TwfID,还是和之前一模一样,现在已经没有疑惑了,这个TwfID就是我们要的VPN认证标识,访问图书馆座位预约系统 和 签到系统。不需要校园网也能访问了。
现在已经分析出了这个TwfID的重要性了。下面开始用它去访问图书馆座位预约系统。
③ 使用TwfID 访问图书馆
由于VPN有在线时限,又要重新获取新的TwfID,此时的TwfID是重新VPN认证后的Cookie,完美访问成功。
不携带TwfID访问,
携带非VPN认证后获取的TwfID,
已经10000%确定,我们就是需要它。
1.1 继续深入分析VPN系统。
为什么使用Burp suit抓不到这个登录的POST提交呢???根据前面我们登录VPN抓的数据包是GET方式,而且发送后返回的XML内容如下,仔细一读,发现里面存在:CSRF_RAND_CODE
, RSA_ENCRYPT_KEY
, RSA_ENCRYPT_EXP
,还有一个:AES128-SHA
。 看到AES128-SHA这个加密算法,我已经开始怀疑,POST提交的那个svpn_password
是POST执行之前在前端通过 该 加密算法加密后得到的 加密密码。
再一看, RSA_ENCRYPT_KEY
: RSA加密密匙??? RSA_ENCRYPT_EXP
: RSA加密扩展????
现在就非常确定就是 POST请求提交之前,在前端完成加密,组装到POST的表单中的。
<?xml version="1.0" encoding="utf-8"?>
<Auth>
<ErrorCode>1</ErrorCode>
<Message>login auth success</Message>
<CSRF_RAND_CODE>1891159946</CSRF_RAND_CODE>
<StartAuth>1</StartAuth> <!-- return next auth type -->
<clientRunMode>0</clientRunMode>
<TwfID>21381bb00650c40a</TwfID> <!-- twfid from server -->
<RndImg>0</RndImg> <!-- image check -->
<SSLCipherSuite>
<EC>AES128-SHA</EC>
</SSLCipherSuite>
<RSA_ENCRYPT_KEY>EBAC264AB6DC117778018C5D07AFD2C5656C47C0B18840B99313788B68D85CED763BFDB8B167E90F33627FA68CFA505584F684110795F0A3FEF46C085922EF2967DFF47AD86996175B1887309051F31B86B5083B570872429007CEFECF4CC1E87038FCD926CAD4E43CB1D898EE28E16A172FBE021D681E88FE752044EB31B1CA08EACFAD8C68C9D84C0975B7DC7AD41D0E51C95EB36E660E55D15BF82E93D17FFA8DFA1704F0BD2F45A832D7E79EA59C646414B26BF873B9116D59BA4F9A321878F40EB98FB3458E5B5CCCE15E04A8D8E939A2634B456F57FAD5B731F15C4FB44189E32DEE5655E9B8EA17D87B13A0581D30D48BC86BB1A44CA39A2DD846FA57</RSA_ENCRYPT_KEY>
<RSA_ENCRYPT_EXP>65537</RSA_ENCRYPT_EXP>
</Auth>
接下来就无限的尝试。在图书管坐了早上 + 一下午的我,开始尝试另外一种姿势——使用火狐开发者工具的 js调试模式。
打开浏览器的调试界面,查看了下js文件,发现这个auth_psw.js
有点可疑,问号三连???它要验证密码?
点进去看看,果不其然,就是它,一定就是它,在看看流经BrupSuit的数据包,仔细一对比,登录POST提交的地址。就是它!!!熟悉 Ajax 和 Axios 的我,一眼看到这个 data: e
,即异步请求发送到目的path的数据。那么这个e
里面肯定就是携带的 svpn_randcode
、 svpn_password
和 svpn_name
等表单数据了。下面继续读这个js文件。

我去这么多e,读了半天,应该是混淆代码的,读了个大概,盲猜逻辑应该是这样的,先判断randCode是否存在,密码是否加密,如果这些没有,就....... 有点懵啊。不过看到最后也就是图中三个红框,瞬间又兴奋了。这就是表单提交的字段啊。后面分别对应,u, e, t, s 这四个变量,重点是u
和t
, 接下来就开始 打断点,开始登录调试吧。
哎呀,姿势不对,到这里已经被RSA加密了。换个姿势继续肝。
又看了以下其他目录下的js,发现这个rsa.js
, RSA???纳尼?这丫太可疑了,读了代码,发现有个函数RSAEncrypt
翻译为RSA加密, 老规矩,在这里打个断点,重新登录,
经过rsa.js里的断点后,返回到的是一个叫common.js
的文件,而且还发现,监控的变量e
里面的 id
属性是我输入的密码!!!阿这,我感觉高潮就快到了丫, 还有一个extraCode???什么鬼。
再一看调用rsa.js的这个函数里面,new 了一个 RSAKey
,而这RSAkey 就是rsa.js里面的函数。那这个s.encryptID 这个函数一定有料!!!浏览器里不太好读代码,把这个函数搞到PyCharm里面自动整理一下格式。
1.2 加密入口函数分析
哈哈哈,读完代码,坐在图书馆肝了一早上的我整个人都开心了起来。不过还是要吐槽一下,写前端的程序员就这么喜欢用三元运算符 X?A:B
吗?现在我们来捋一捋这个加密逻辑。
首先new了一个RSAKey
,也就是新建一个RSA算法对象(第二行),然后就返回很多数据,我们一个一个的看。
n = '65537' || (o = SF.setting.getGlobal(
[KEY_GLOBAL_ENCRYPT_EXP, KEY_GLOBAL_ENCRYPT_KEY, KEY_GLOBAL_EC_KEY],
[0, ''])) [0],
或||
后面的函数是去获取全局变量,有没有很熟悉呢,没错,ENCRY_EXP
, ENCRY_KEY
, 在最开始我们登录抓包的时候,get了一次响应的xml中有这两个。
再看下面这句,
e.type === c.ENCRYPT_TYPE_PSW && e.extraCode ? e.id = e.id + '_' + e.extraCode : r = o[2],
就是判断e.extraCode 是否存在,如果存在就执行 e.id = e.id + '_' + e.extraCode
即把e.id 拼接字符串加上这个extraCode
, 眼前一亮,这个不就是上面在浏览器调试的时候看到的吗,id恰好是我的密码,仿佛上帝为我打开了一扇窗,那这句就是将我的密码和这个随机码使用下划线进行拼接。并替换掉原来的e.id
继续看下面一句
r && (s.setPublic(r, i), t = s.encrypt(e.id)),
调用加密算法的关键语句了,先调用了setPublic, 然后再调用encrypt加密 e.id (我的密码和随机码拼接后的),那么传的r,i
这两个参数又是什么呢?调用加密算法后加密的结果是返回给t的,重新分析创建RSAKey
之后的代码。
n = '65537' || (o = SF.setting.getGlobal(
[KEY_GLOBAL_ENCRYPT_EXP, KEY_GLOBAL_ENCRYPT_KEY, KEY_GLOBAL_EC_KEY],
[0, ''])) [0],
这里o明显是个数组,而且第一个元素就是KEY_GLOBAL_ENCRYPT_EXP
的值,而这个值我们抓包的时候看到它是等于:65537的,那么就明白了,如果 o 这个数据中的第一个元素KEY_GLOBAL_ENCRYPT_EXP
不存在,那么就将 65537赋值给n,但是我们现在已经明确了KEY_GLOBAL_ENCRYPT_EXP
存在并且为65537,
r = o[1], i = parseInt(n, 0).toString(c.ENCRYPT_LENGTH)
接下来这句就是把KEY_GLOBAL_ENCRYPT_KEY 赋值给 r。 i是通过这个函数计算出来的。我们先不用管它怎么计算的,直接浏览器调试看它的值,它的值为 "10001" , 它应该是个定值,抱着怀疑的态度,我又重新登录调试,果然没错。
到此,我们已经分析出了加密密码的全部过程了。
突然想起,这个e.extraCode
怎么来的呢?现在就差它了。
继续分析。。。。。。

经过多次调试发现,每次登录之前都要去 请求这个 /login_auth.csp?apiversion=1
查看响应回来的xml,惊喜万分,,,,因为这里的CSRF_RAND_CODE
的值就是e.extraCode
。TwfID就是之前分析出来的这次登录请求携带的Cookie。
1.3 总结
现在VPN的登录完全摸透了。下面总结一下登录VPN的流程。
输入账号密码登录 ——> 携带TwfID,GET请求/login_auth.csp?apiversion=1
-——> 响应随机码extraCode
, 请求携带的TwfID
,RSA加密密匙RSA_ENCRYPT_KEY
, 加密扩展RSA_ENCRYPT_EXP
——> 使用extraCode
拼接密码,调用rsa.js加密算法,传入加密密匙,加密扩展,以及拼接好的密码字符串 ——> 完成加密,将加密密码传给 POST登录请求函数 ——> 登录成功 ——> TwfID登录状态生效。
2. 图书馆座位系统
2.1 登录分析
图书管登录,就没玩得那么花了,直接密码学号登录就ok。
2.2 座位预约

参数是通过path传,看到path上面有座位id
,开始时间
,结束时间
,无需多说。真的真的真的比过VPN简单了不知多少。
3. 签到签退系统
3.1 登录分析
因为每次签到都是通过手机扫二维码,然后有时候需要绑定学号,然后才出现签到得按钮,那么这个二维码里面肯定是存在链接的。拍了一张座位的二维码,解码了一下。得到签到系统的链接。
但是这个链接是有点恶心的,302重定向了3次,最后一次才到达登录系统。302重定向是拿不到cookie的,只能捕捉每次重定向的链接,通过这个链接去获取下一次重定向的链接再访问。最后到达登录界面时进行登录抓包,依然是携带TwfID
, 完成登录的过程是仅次于VPN系统登录的
,除了这个TwfID, 还需要图书馆登录后设置的Cookie,以及座位二维码链接第一次访问设置的path参数,在320重定向的过程中还涉及Cookie的设置,因为没法捕捉到,试了无数次都不行,差点放弃。
可是ahao从来不是轻易放弃的玩家,阴差阳错的,直接把最后一次登录的固定的Cookie全部拿过去,单独设置了其中一个变化的Cookie,居然成功了。
然后就是签到签退抓包分析了,也是比较简单的。
三、总结
本文是CDU-Lib-Robot
项目的完整分析过程,写这篇博文也是为了记录自己的学习过程,创建这个项目的初心也是为了 成大考研学子
和每天都泡图书馆
的同学 更加方便使用图书馆座位。
忠告:请勿将本项目用于包括但不限于恶意占座
等浪费图书馆公共资源等行为。
使用本项目之前请仔细阅读 免责声明、注意事项。
评论区