国庆的更新:
发现打不了卡了,返回“请使用微信或钉钉打卡”,分析发现从 cas
获取的 token
已经被禁用,只有从微信或钉钉拿到的 token
才可以打卡。
由此分析助手内部业务的 token
是有不同平台区分的,钉钉、微信、CAS有区分。在打卡的 validate
请求响应的 grant_type
也可以看出
目前的微信认证根链接:https://api.hduhelp.com/login/direct/wxmp?clientID=healthcheckin&redirect=https%3A%2F%2Fhealthcheckin.hduhelp.com%2F%23%2Fauth
(从前端源码也能找到,我是抓包抓出来的)
中途会通过微信的开放平台拿 token
,这个过程太难模拟了,只能微信抓包直接拿微信版的 token
,一个月换一次了。
好久没折腾了,这几天刚开学有点闲下来了,探索一下杭电助手的健康打卡接口,杭电助手也算是很不错的技术社团。技术栈都还算前沿。
一开搞发现还挺有难度的,也是第一次自己独自尝试找前端加密规则(搞完发现还挺简单的),主要被微信内置浏览器抓包和 sign
参数绊了一会儿,前前后后花了大概两个半小时。
打卡接口
请求方式:POST
Header参数:
参数名 | 类型 | 内容 | 备注 |
---|---|---|---|
authorization | string | token <TOKEN> | OAuth2.0 认证。注意中间空格。<TOKEN> 获取通过 HDU 统一认证平台获取。 |
该 token
可以通过访问 https://healthcheckin.hduhelp.com
抓包手动获取,有效期一个月。
自动登录 hdu-cas
获取 token
请看 https://iyear.me/archives/874.html/
Query参数:
参数名 | 类型 | 内容 | 备注 |
---|---|---|---|
sign | string | 校验参数 | SHA1 算法40位不加盐,签名错误返回 418 状态码。具体计算方式如下 |
// 字符串形式相连接
// <NAME> 姓名,与Body中保持一致
// <ID> 学号
// <TIMESTAMP> 10位时间戳,与Body中保持一致
// <PROVINCE> 省份编号,与Body中保持一致
// <CITY> 城市编号,与Body中保持一致
// <COUNTRY> 区编号,与Body中保持一致
//
// 伪代码 GO实现: https://gist.github.com/iyear/5e0715c7d869414e04879d0e259b9825
SHA1(<NAME> + BASE64(<ID>) + <TIMESTAMP> + BASE64(<PROVINCE>) + <CITY> + <COUNTRY>)
Body参数(application/json
):
参数名 | 类型 | 内容 | 备注 |
---|---|---|---|
name | string | 姓名 | - |
timestamp | num | 时间戳 | 10位长度(UTC时间),经测试只用于 sign 计算,实际打卡时间由后端生成并写入 |
province | string | 省份编号 | - |
city | string | 城市编号 | - |
country | string | 区编号 | - |
answerJsonStr | string | 打卡提交数据 | JSON文本表示,注意转义 |
在 https://healthcheckin.hduhelp.com/js/app.8d49ea3c.js 中搜索 t.default
即可找到编号数据
以实际参数为例对 answerJsonStr
说明:
ques
是答案数据,应该是历史原因看着设置得挺乱的,能抓包就抓包拿来当数据,不行的话就老老实实去看 app.js
好了。
carTo
是编号数据的字符串数组,就是提交的省份、城市、区编号。
{
"ques1": "健康良好",
"ques2": "正常在校(未经学校审批,不得提前返校)",
"ques3": null,
"ques4": "否",
"ques5": "否",
"ques6": "",
"ques7": null,
"ques77": null,
"ques8": null,
"ques88": null,
"ques9": null,
"ques10": null,
"ques11": null,
"ques12": null,
"ques13": null,
"ques14": null,
"ques15": "否",
"ques16": "否",
"ques17": "无新冠肺炎确诊或疑似",
"ques18": "37度以下",
"ques19": null,
"ques20": "绿码",
"ques21": "否",
"ques22": "否",
"ques23": "否",
"ques24": "共二针 - 已完成第二针",
"carTo": [
"330000",
"330100",
"330104"
]
}
请求示例 (已脱敏) :
curl 'https://api.hduhelp.com/base/healthcheckin?sign=47944bac930a19d59d5218b8d45a32bda327e5c1' \
-H 'authorization: token dxxxxxxx-1axx-47xx-aaxx-dxxxx2dxxxx1' \
-H 'content-type: application/json;charset=UTF-8' \
-H 'referer: https://healthcheckin.hduhelp.com/' \
--data-raw '{"name":"xxx","timestamp":1631237051,"province":"330000","city":"330100","country":"330104","answerJsonStr":"{\"ques1\":\"健康良好\",\"ques2\":\"正常在校(未经学校审批,不得提前返校)\",\"ques3\":null,\"ques4\":\"否\",\"ques5\":\"否\",\"ques6\":\"\",\"ques7\":null,\"ques77\":null,\"ques8\":null,\"ques88\":null,\"ques9\":null,\"ques10\":null,\"ques11\":null,\"ques12\":null,\"ques13\":null,\"ques14\":null,\"ques15\":\"否\",\"ques16\":\"否\",\"ques17\":\"无新冠肺炎确诊或疑似\",\"ques18\":\"37度以下\",\"ques19\":null,\"ques20\":\"绿码\",\"ques21\":\"否\",\"ques22\":\"否\",\"ques23\":\"否\",\"ques24\":\"共二针 - 已完成第二针\",\"carTo\":[\"330000\",\"330100\",\"330104\"]}"}'
打卡成功响应:
{
"cache": false,
"data": {
"code": 200,
"data": "",
"message": "保存成功!"
},
"error": 0,
"msg": "success"
}
重复打卡响应 (已脱敏) :
{
"cache": false,
"data": {
"code": 200,
"data": {
"answerJsonStr": "\"{\"ques1\":\"健康良好\"...",
"city": "330100",
"country": "330104",
"creator": "2105xxxx",
"gmtCreate": {
"date": 11,
"day": 6,
"hours": 8,
"minutes": 19,
"month": 8,
"nanos": 0,
"seconds": 39,
"time": 16313000000,
"timezoneOffset": -480,
"year": 121
},
"gmtModified": null,
"id": 9660000,
"idType": "1",
"modifier": "",
"name": "xxx",
"province": "330000",
"reportTime": "2021-09-11 08:xx:xx",
"schoolId": "2105xxxx2021-09-11",
"schoolno": "2105xxxx",
"xueyuan": "计算机学院(软件学院)"
},
"message": "今日已填报!"
},
"error": 0,
"msg": "success"
}
探索过程
电脑打开 https://healthcheckin.hduhelp.com/ 发现没有打卡按钮,应该是没有微信授权 Cookie
就不显示按钮了 ,抓不了提交的包,于是找办法抓包微信内置浏览器。
尝试了 Fiddler
抓包、 替换电脑微信浏览器内核调出 dev tools
,均失败。
最终找到了一个最佳方法,使用感受和调试网页一模一样——通过 Chrome
的 inspect
功能对 Chromium
内核浏览器进行远程调试。
方法在 https://momane.com/debug-webpage-on-mobile-and-wechat-in-chrome 中,如果还无法调试可以在 https://debugx5.qq.com 中 信息
栏打开所有和调试有关的开关,再回来用 Chrome
调试。
解决了调试问题,那么就提交抓个包,很轻松抓到了提交数据的包。
浏览一下提交的数据,基本都是明文,只有一个 sign
需要我们研究,反正前端的签名肯定是可以找出来的,只是时间问题。
下载 webpack
打包的 app.js
,第一次看混淆丑化的代码,不太熟练,格式化代码后搜索 sign
,总共 11
个结果,筛选后发现以下代码块
可以看见 sign
上方就是我们刚刚抓到包的数据,可以断定这个就是我们要的 sign
。整个加密就 be
是未知的算法,其他都是内置函数。
跳转到 be
定义,发现 be = a("6199")
,结合文件开头一堆的不明数字和 .exports
,盲猜是函数出入口。
全文搜索 6199
结果只发现了另一个同样用 a
来做出入口的 W = a("6199")
,然后就卡死在这里,一直在看前面的函数导出导入,一堆 a
e
t
看晕了。看了半天才想起来另一个 chunk-vendors.js
,进入搜索 6199
,只有一个结果。
结合下面的代码关键词和这块注释,推断调用的第三方 SHA1
库,没有什么复杂逻辑在里面。
尝试不加盐 SHA1
计算得出的字符串,完美符合,至此分析结束。
第二天测试接口的时候忘记改时间戳直接提交了,后来改了时间戳想着覆盖一下,发现重复打卡响应里的时间戳是今天的,那么提交数据的时间戳应该就是校验用途了
时隔一天的时间戳都能正常提交,就压根没做时间戳范围校验,可以说这个 sign
参数也是废了,抓过一次包就可以拿着这个包重复提交,是个挺严重的问题
题外话
发现杭电助手请求很快,顺手查了一下 hduhelp.com
的信息
服务器在杭州阿里云,应该是备案过的。
WHOIS查出域名注册在 2013年10月。
再查备案信息,主体为 廖凌云
,找到 https://www.hdu.edu.cn/news/important_27528;https://www.hdu.edu.cn/news/media_23517 两条新闻。
不知这位学长现在是否还在杭电助手一线?
其他接口
其他接口不用微信内置浏览器就可以抓,而且数据基本都是明文,简略写一下。
所有提交只需要一个OAuth认证头
Header参数:
参数名 | 类型 | 内容 | 备注 |
---|---|---|---|
authorization | string | token <TOKEN> | OAuth2.0 认证。注意中间空格。<TOKEN> 获取通过 HDU 统一认证平台获取 |
Validate
https://api.hduhelp.com/token/validate
获取认证信息
请求方式:GET
响应示例 (已脱敏) :
// 可以看出是一个有着历史包袱的接口
{
"Data": {
"accessToken": "06xxxxxx-bxxx-4xxx-9xxx-f34xxx78xxxa",
"clientID": "healthcheckin",
"expiredTime": 1633780760, // token过期时间
"grantType": "",
"isValid": 1,
"isViald": 1,
"school": "hdu",
"staffId": "2105xxxx", // 学号
"tokenType": 0,
"uid": 4600000 // 杭电助手内的uid
},
"cache": false,
"data": {
"accessToken": "06xxxxxx-bxxx-4xxx-9xxx-f34xxx78xxxa",
"clientID": "healthcheckin",
"expiredTime": 1633780760,
"grantType": "",
"isValid": 1,
"isViald": 1,
"school": "hdu",
"staffId": "2105xxxx",
"tokenType": 0,
"uid": 4600000
},
"error": 0,
"msg": "success"
}
Info
https://api.hduhelp.com/salmon_base/person/info
获取个人信息
请求方式:GET
响应示例 (已脱敏) :
{
"cache": true,
"data": {
"staffId": "2105xxxx", // 学号
"staffName": "xxx", // 姓名
"staffState": "在校",
"staffType": "1",
"unitCode": "05"
},
"error": 0,
"msg": "success"
}
Daily
https://api.hduhelp.com/base/healthcheckin/info/daily
获取今日提交数据
请求方式:GET
响应示例 (已脱敏) :
{
"cache": false,
"data": {
"code": 200,
"data": {
"answerJsonStr": "\"{\"ques1\":\"健康良好\"....", // 省略部分信息
"city": "330100",
"country": "330104",
"creator": "2105xxxx",
"gmtCreate": {
"date": 11,
"day": 6,
"hours": 8,
"minutes": 19,
"month": 8,
"nanos": 0,
"seconds": 39,
"time": 1631300009000, // UTC时间,需自己转换时区
"timezoneOffset": -480,
"year": 121
},
"gmtModified": null,
"id": 9660000,
"idType": "1",
"modifier": "",
"name": "xxx",
"province": "330000",
"reportTime": "2021-09-11 08:00:00",
"schoolId": "2105xxxx2021-09-11",
"schoolno": "2105xxxx",
"xueyuan": "计算机学院(软件学院)"
},
"message": "今日已填报!"
},
"error": 0,
"msg": "success"
}
Code
https://api.hduhelp.com/salmon_base/health/code
获取个人健康码信息
请求方式:GET
响应示例 (已脱敏) :
{
"cache": true,
"data": {
"codeStatus": "绿码",
"lastUpdateTime": "2020-02-20T00:00:00.000+0000",
"location": "xx市",
"reason": "你去过疫情关注地区",
"serverUpdateTime": "2021-09-11 00:00:00"
},
"error": 0,
"msg": "success"
}
History
https://api.hduhelp.com/base/healthcheckin/history
获取历史打卡记录
请求方式:GET
响应示例 (已脱敏) :
{
"cache": false,
"data": [
{
"Answer": {
"carTo": [
"330000",
"330100",
"330104"
],
"ques1": "健康良好",
"ques10": null,
// ...
},
"City": "330100",
"Country": "330104",
"IDType": 1,
"Name": "",
"Province": "330000",
"ReportTime": 1631237035, // UTC时间,需自己转换时区
"SchoolName": "计算机学院(软件学院)",
"StaffID": "2105xxxx"
},
{
"Answer": {
"carTo": [
"330000",
"330100",
"330104"
],
"ques1": "健康良好",
"ques10": null,
// ...
},
"City": "330100",
"Country": "330104",
"IDType": 1,
"Name": "",
"Province": "330000",
"ReportTime": 1631150879, // UTC时间,需自己转换时区
"SchoolName": "计算机学院(软件学院)",
"StaffID": "2105xxxx"
}
// ...
],
"error": 0,
"msg": "success"
}
1 条评论