前言

本文包含个人主观情绪(被气坏了),非纯教程文章

之前做课表APP做上瘾了,但因为教务系统的功能主要还是看课表,抢课教评什么的一学期就那一两次,真正日常常用的功能还是在学校委托第三方(或者是自己开发的?)做的APP上。

当然,APP还是继承国内APP的优良传统之典中典Web APP,所以自己实现难度不大。

其实最主要还是APP太shit了,有时候启动卡半天不知道在干嘛,还有很多无用的信息(对于我自己来说),因此打算一不做二不休,自己做个第三方的

这次先打个头阵,对APP进行抓包分析。

初步分析

APP采用统一CAS登录,和教务系统账号独立。

APP内部所有页面无需重复登录,并且具有登录状态保持能力,因此一定使用了cookie或者其他技术保持登录状态,所有功能共享登录状态,而不是依靠浏览器session

APP内功能模块动态更新,考虑到是Web APP,应该是通过更新Web页面或者相关list信息达到动态新增模块功能

抓包分析

登录模块

为保(liu)护(dian)隐(lian)私(mian)这里就不透露域名了,仅公布路径

md一抓就绷不住了。。。

首先使用POST请求/token/mfa/detect

header为普通内容,不带任何具有session功能的内容

然后body为空

返回值如下

1
2
3
4
5
6
7
8
9
10
11
12
{
"code": 0,
"data": {
"mfaTypeSecurePhone": true,
"mfaTypeFedAuth": false,
"need": false,
"mfaTypeFaceVerify": false,
"mfaEnabled": false,
"state": "一串字符,没啥用",
"mfaTypeSecureEmail": true
}
}

通过路径和返回值可以得出此次请求是获取mfa信息的,以便进行mfa验证,但是我们学校app貌似没mfa这个功能,所以这个检查可有可无。

但目前有个巨大的问题,POST请求body为空,那服务器怎么知道用户信息呢?

没错,md用户名和密码在请求路径中。。。。

完整请求路径为:/token/mfa/detect?username=学号&deviceId=设备ID(暂时未知如何生成)&password=明文密码

看到这我都气笑了,还POST什么啊,干脆直接GET得了。。。还明文传输密码,我们教务系统虽然用的是HTTP,但人家至少密码还是通过自研算法加密了(不知道是不是单向的,真不如哈希加盐)才传输的,HTTPS都表示自己无能为力。

言归正传,下面是真正登录请求

使用POST请求/token/password/passwordLogin

请求header还是正常普通的header,密码仍然是内嵌路径(已经懒得喷了),请求参数表如下:

Name Value
username 学号
password 明文密码
appId 固定值,为APP包名
geo 空,不知道是干嘛的
deviceId 设备ID,和前面一样,不知道是怎么生成的
osType 系统类型,这里是Android
clientId 客户端ID,不知道是怎么生成的
mfaState

返回值如下:

1
2
3
4
5
6
7
8
9
10
{
"code": 0,
"data": {
"userNonActivated": false,
"passwordStatus": 0,
"idToken": "JWT",
"userNonCompleted": true,
"refreshToken": "JWT"
}
}

就很好奇居然懂得用JWT了,为什么还会犯用POST请求路径传输明文密码这种低级错误,就很难蚌。

通过解析JWT,可以得到下面信息:

JWT Header:

Name Value
alg (Algorithm)RS512 (RSASSA-PKCS1-v1_5 using SHA-512)

JWT Payload:

Name Value
ATTR_userNo 学号
sub (Subject) 学号
iss (Issuer) cas系统Host
deviceId 设备ID
ATTR_identityTypeId 用不上
ATTR_accountId 用户ID
ATTR_userId 用户ID
ATTR_identityTypeCode S02
ATTR_identityTypeName 学生
ATTR_organizationName 班级
ATTR_userName 姓名
exp (Expiration Time) 过期时间,有效期一个月
ATTR_organizationId 用不上
iat (Issued At) 令牌创建时间
jti (JWT ID) Id-Token-xxxxxxxx
req 包名
ATTR_organizationCode 用不上

成功拿到JWT就简单很多了

后面还有个绑定clientid以及向/openplartform/auth/api/token的请求,不知道干啥用的,后面实测不请求也貌似没什么影响

openplartform请求返回的JWT后面也没用上

拉取功能模块

在一堆没用的请求中终于找到了server list请求

/portal-api/v1/service/list发送POST请求

header中携带JWT,具体如下:

1
2
3
4
5
6
X-Device-Info: 我手机的型号
Accept: application/json
X-Device-Infos: 简单的系统信息
X-Id-Token: JWT内容
X-Terminal-Info: app

主要还是携带JWT,剩下那几个X-xxx的内容都没必要,当然如果是打算伪装的话还是需要一起携带

然后body是空。。。

不是,真就不知道GET和POST怎么用是吧???

建议重新学习前端

返回的json包含一个services数组,里面就是整个APP所有的功能模块

数组内一个对象包含的对象一堆,但有用的就几个:

Name Value
serviceName 功能名称
servicePicUrl 图标
serviceUrl 功能页面地址

简单试了一下,请求header中携带X-Id-Token: JWT即可正常访问页面

后记

APP具体开发要等到寒假,最近忙着应付考试完全没有时间(今天才考完汇编,泪目了)