HUST微校园解密
一、登录微校园
第一步当然是登录 hust
微校园,我之前对登录接口抓包研究过,找到了加密过程,提交的数据包等。只差一步,就是验证码的识别,只要能够自动识别出验证码,就可以实现登录的自动化,重新记录一下解密过程。
抓登录包
微信端应用中心接口:https://pass.hust.edu.cn/cas/login?service=http://m.hust.edu.cn/wechat/apps_center.jsp
从这个接口登录,打开 F12
,对登录数据抓包
可以发现,post
方法,然后状态码是 $ 302 $ ,post
成功之后进行了重定向,跳转了应用中心页面。
在 payload
中可以看到实际提交的表单数据,很明显对账号密码数据进行了 rsa
加密
ul
貌似是 username length
, pl
貌似是 password length
,code
是验证码,rsa
是账号密码经过 rsa
加密之后的值,剩下的三个不知道从哪个地方来的
分析数据包
查看网页源代码
没想到这三个值直接写到前端源代码里了,蚌埠住了
分析rsa加密
尝试直接搜索 rsa
,没想到直接搜到了,这防范也太弱了,直接可以看到是怎么加密的。
username + password + lt
构成待加密的字符串,参数是 $ 1,2,3 $ ,打上断点,看看实际执行加密的函数是哪个
跳到了这个 des.js
的文件,上面还都有注释,就是加密的函数。直接把这个文件搞下来,在 python
里面执行 js
代码,完成加密过程
这里我选择了 execjs
库,直接 pip install execjs
就行
1 | with open('des.js') as file: |
用法大致就是这样,读进来,编译,然后调用
分析验证码
现在只剩下了验证码难题,一般来说,验证码挺难处理的。可以使用图像识别,打码平台等。这张验证码还是动态的,就挺烦的。
如果样本多的话,可以试着使用深度学习搞一下。因为这个验证码比较简单,杂乱的噪点比较少,所以这里我使用的是 ocr
的方式
首先把验证码搞下来
可以看到验证码的链接,验证码的变化应该和 cookie
有关
很明显是一张 gif
图片,下载下来保存为 gif
图片,对 gif
图片分解,分解为一张张的 jpg
发现是四张图片,第一张的后两个数字和第四张的前两个数字的噪点比较少,这里使用的策略是截取出第一张的后两个数字,第四张的前两个数字,然后拼在一起
然后进行二值化,转化为灰度图片
最后使用一些去噪方法处理一下,这里使用的是用 $ n \times n $ 的方格内数量最多的像素颜色代替整个方格的颜色
这样处理之后已经能够很容易看出来了,下面使用 ocr
ocr
可以使用百度等接口,也可以使用 pytesseract
库,我使用的是 pytesseract
,但是感觉使用接口的话效果会更好一点
构造数据包
1 | lt = re.findall('id="lt" name="lt" value="(.*?)"', html)[0] |
使用 request
post
就行了,注意第一次抓 html
获取 lt, execution, _eventId
, 然后使用该次的 cookie
请求验证码链接,得到图片,然后处理得到验证码。最后构造好登录数据包
根据登录后返回的 html
判断是否登录成功
1 | r = self.session.post(url, data=loginData, headers=self.head, cookies=curCookie) |
二、预约出校
抓提交包
发现非常不正常,居然是个 get
接口,提交数据按理说应该是个 post
。估计应该是前端代码中 post
提交,然后根据 post
的返回值生成了这个页面,然后重定向到了这个页面
注意 cookie
变了,所以需要先进行 get
一下预约页面,得到新 cookie
然后再构造数据包 post
查找post接口
搜索了一下 url
中的 data
找到了重定向的地方,附近应该有 post
的地方,可以打上断点,进行调试,找到提交的地方
找到了 post
的链接 http://access.hust.edu.cn/IDKJ-P/student/resStudentAPI
分析post数据包
经过打断点调试,发现这个数据包 data
是将提交的数据进行了加密,其他的居然全是常量,写到了前端代码里。这也太假了!
这个地方进行了加密
发现是 AES
加密,使用的 CBC
模式,Zero
填充,并且密钥和偏移又是明码,还是一样的。随便找个 AES
在线加密的网页进行了一下验证,发现正确。直接搜一份 AES CBC Zeropadding
的 python
代码,进行测试,最后发现填充的不是 0
,应该是空格 space
构造数据包
隐私数据我删了,自己搞的时候记得加上
没想到 startTime
和 endTime
之间居然可以差很多天,这样的话早知道就不写脚本了,直接使用 postman
将 cookie
放进去,直接提交了几百天的数据包,post
的 resMsg
显示是预约成功了,不知道效果如何,明天后天试试能不能刷开校门
1 | # 对如下json加密,加密之后填到上面的data,然后直接post就行 |
AES
加密的类
1 | class AesCbcZeroPadding(object): |
提交数据包
1 | r = self.session.post(postUrl, json=data, headers=self.head) |
注意一下 data
是放到 json
参数中了,之前我直接填的 data
,r = self.session.post(postUrl, data, headers=self.head)
一直返回服务器网络错误。
- JSON
- 使用
json
参数,不管报文是str
类型,还是dict
类型,如果不指定headers
中content-type
的类型,默认是:application/json
。
- DATA
使用
data
参数,报文是dict
类型,如果不指定headers
中content-type
的类型,默认application/x-www-form-urlencoded
,相当于普通form
表单提交的形式,会将表单内的数据转换成键值对,此时数据可以从request.POST
里面获取,而request.body
的内容则为a=1&b=2
的这种键值对形式。
注意:即使指定content-type=application/json
,request.body
的值也是类似于a=1&b=2
,所以并不能用json.loads(request.body.decode())
得到想要的值。使用
data
参数,报文是str
类型,如果不指定headers
中content-type
的类型,默认application/json
。之前就是犯了这个错误,指定了
content-type=application/json
,但还是不行,改成json
就行了
三、发送邮件
编写 smtp
发送邮件的类,预约成功和失败时都把 log.txt
中的数据发送到自己的邮箱参看
四、定时任务
使用了 apscheduler
库,这个库可以用类似 corn
表达式的方式实现定时任务
1 | def main(): |
设置成每天早上 $ 7 $ 点运行,对登录模块和预约模块进行多次循环,失败时自动重试
五、总结
通过这个项目,练习了 js
解密,又重温了 request
爬虫。详细代码可见 luobuyu/HUST-appointments-out-school: 华科微校园自动预约出校 (github.com)