妈妈再也不用担心我忘记填报疫情(Ⅲ)——写在岁末年初的重构 - Pinming's Blog

这样暴露 Token 属于是你瓜特色程序了,疫情没事干就水一贴罢。

0x00 前言

阅读本文前需要知道……

本文是以下两篇文章的后继:
  • 『妈妈再也不用担心我忘记填报疫情——巧用云函数完成自动化数据填报』
  • 『妈妈再也不用担心我忘记填报疫情(Ⅱ)——利用 Server 酱完成填报结果推送』
  • 本次迭代实际上只是以往版本的微调,将一 main 到底的面条代码变成了面向对象逐个封装。软件的操作原理未发生改变。

    本次新版本迭代也考虑到该程序还是更适合云函数的应用场景,因此不再考虑本地执行。本次重构迭代后,命名及程序入口设计均为能在云函数中即插即用而服务。

    然而毕竟是一年以后的更新,因此还是容我赘述一下这个软件本应有的 「前言」

    在新冠疫情的背景之下,想必很多学校都要求学生每日申报自己的健康状况。但是日子一天天过去总有忘记的时候——轻则全班公开处刑,重则全院公开处刑,尴尬至极。后来接入了企业微信,如果不填每天中午 11:30 又准时来催你填……每天还得去对着表格整几下,实在有些麻烦,遂考虑制作一个自动化填报的程序来减轻自己的工作量。

    警告 / 请认真阅读本部分

    • 本软件设计之本意为技术学习,请在遵循法律及学校各项规定的前提下使用本软件。
    • 如您需要使用该软件,请确保您的身体状况良好,如实申报自身身体状况。
    • 若您的身体状况出现异常,应立即停止使用本软件、关闭云函数自动触发功能,并及时于学校系统更改每日申报情况。
    • 因使用该软件误报身体状况而引发的不良后果应由您自行承担。
    • 本软件原理是提取上一次的填报结果来提交,如果您的所在地发生改变,请自行手动填报一次,理论上程序会自动跟进后续的填报并与之同步。如出现异常烦请反馈!
    • 该软件并非万能,请时常检查填报结果!

    本项目 Github 仓库:
    如果 Github 访问异常,请浏览 Gitee 镜像。

    0x01 2021 年 12 月疫情填报系统的两处修改

    那就直入主题。2021 年 12 月,疫情突然袭击西安,校内连续高频率的核酸检测成为了日常。基于此因,校方对疫情填报系统做出了两处改动:

    • 加入了新问题「近 48 小时内是否进行过核酸检测」。
    • 在对 /wx/ry/ry_util.jsp POST 每日健康状况表单时,加入了签名校验;

    第一个问题非常好解决,只需要在 POST 的时候加入一对键值 'hsjc': '1'

    第二个问题我一开始看到也愣了——加密校验?那不是脚本通杀了?让人意外的是,签名参数 signtimeStamp 竟然暴露在 /wx/ry/jrsb.jsp 的 js 中,一览无余。那么获得一个合法的签名也就变得容易许多,只需要用已获取的参数来伪造请求即可。

    似乎工程师还想开嘲讽,不过我也不知道到底哪种方案的自动化步骤会看到您这个提示😅

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    # 初始化当次填报信息
    def init_info(self):
    self.session.post(url_jrsb)
    header_for_init = {...}
    data_for_init = {...}
    res_jrsb = self.session.post(url_jrsb,
    data=data_for_init,
    headers=header_for_init)
    # 获取时间戳
    self.timeStamp = re.findall(re.compile('(?<=&timeStamp=).*(?=\')'),
    res_jrsb.text)[0]
    # 获取签名
    self.sign = re.findall(re.compile('(?<=sign=).*(?=&)'),
    res_jrsb.text)[0]
    (...) # 以下省略


    # 提交表单
    def submit(self):
    # 伪造一次对 Form 页面的请求,获得 JSESSIONID
    self.session.get(url_jrsb)
    header_for_submit = {...}
    self.data_for_submit = {
    '......', # 省略部分
    'hsjc': '1', # 新增的是否 48 小时内核酸检测
    # TODO: 后续再加获取上一次记录,现在就默认填 1 了
    }
    url_ry_util_with_token = url_ry_util + '?sign=' + self.sign + '&timeStamp=' + self.timeStamp
    self.session.post(url=url_ry_util_with_token,
    data=self.data_for_submit,
    headers=header_for_submit)

    这样的话也就解决了上述的两处变化。

    0x02 其他一些改进

    2.1 判断填报成功的逻辑

    以前只是通过填报后跳转页面的提示来判断填报是否成功,这样一来并不是对填报数据的直接判断,二来也不好判断今天到底填没填过,会造成当天重复填报,并不优雅。

    因此引入如下判断:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    # `self.judge_last_report_is_today(
    # self.get_last_report_time(data_for_init, header_for_init))` 的意义:
    # 判断系统当前日期是否与最后一次填报的日期一致。
    # 如条件为 `False`,则不一致,认为今日未填报,执行 POST;
    # 否则跳过 POST 步骤,不再重复填报。
    if not (self.judge_last_report_is_today(self.get_last_report_time(data_for_init, header_for_init))):
    url_ry_util_with_token = url_ry_util + '?sign=' + self.sign + '&timeStamp=' + self.timeStamp
    self.session.post(url=url_ry_util_with_token,
    data=self.data_for_submit,
    headers=header_for_submit)
    else:
    print('今日已填报,无需重复填报!')
    return

    # 判断填报成功
    if (self.judge_last_report_is_today(self.get_last_report_time(data_for_init, header_for_init))):
    print('申报成功!')
    if user_config.SC_switcher == 1:
    Pusher.scPush(self)
    else:
    print('申报失败,请重试!')
    if user_config.SC_switcher == 1:
    Pusher.sc_push_when_wrong_info(self)


    def judge_last_report_is_today(self, report_time):
    report_time = self.string_toDatetime(report_time)
    if report_time.date() == datetime.today().date():
    return True
    else:
    return False

    # 获取最近一次填报的时间,判断是否为今天
    def get_last_report_time(self, data, header):
    last_report_time = \
    etree.HTML(self.get_last_report(data, header)).xpath(
    '/html/body/div[1]/div[2]/div/div[2]/div[1]/div[2]/text()')[0]
    return last_report_time

    2.2 获取上一次核酸检测的状态

    其实和上面大同小异,水个贴记录一下更新就得了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    self.hsjc = self.get_last_hsjc_status(data_for_init, header_for_init)

    # 获取上一次核酸检测的状态
    def get_last_hsjc_status(self, data, header):
    # 获取最近一次日报核酸检测状态:'已检测' or '未检测'
    nuc_acid_test_status = \
    etree.HTML(self.get_last_report(data, header)).xpath(
    '/html/body/div[1]/div[2]/div/div[2]/div[2]/div[2]/text()')[0]
    if nuc_acid_test_status == '已检测':
    return '1'
    else:
    return '0'

    至于其他原本的细节,还是回到第一篇去看吧~那里写得还是更加详细一些。

    评论



    Powered by Hexo.

    博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议

    本站使用 Volantis 为主题 | 总访问量为
    © Pinming 2019-2015 | All Rights Reserved.
    载入天数...载入时分秒...
    粤 ICP 备 19139605 号
    粤公网安备 44030502004717 号