soarli

正常健康数据自动上报解决方案
前言本项目仅为本人学习测试使用,健康状态会按真实情况上报本文章/思路仅供网络爱好者学习参考,请勿用于非法用途本文如...
扫描右侧二维码阅读全文
07
2021/08

正常健康数据自动上报解决方案

前言

  • 本项目仅为本人学习测试使用,健康状态会按真实情况上报
  • 本文章/思路仅供网络爱好者学习参考,请勿用于非法用途
  • 本文如有幸被新中新工作人员/学校信息处老师看到,希望你们仔细阅读“探索”部分并启用SSL证书安全传输数据以解决全校师生隐私信息泄露的隐患

探索

7月31号,随着疫情忽然的严重,学校开始要求恢复每日健康上报。然而,就本人而言,绝大多数时候身体状态都是健康的(偶尔的懒散通常都是睡一觉就会好了)。每天都得想着这件事按时点进页面点一下提交按钮就显得有点折磨(考虑到疫情防控需要,如果要求大家都只需上报异常数据估计这个系统就没几个人能记得了,可以理解)。于是当天对我的相关表单抓下了数据包,第二天尝试用它通过之前写的查电费爬虫的sessionid上报,得到了以下返回信息:

如果不停发包,随后便会返回上报异常:

嗯,可以理解,毕竟每天就只应该接受一次成功的上报请求嘛。

然而又过了一天它还一直提示“上报异常”,而不是“会话过期”,很显然上报记录(成功与否)被存放在服务器端Session的一个变量里面。

于是通过一些办法获取到一个新的合法的sessionidpost同样的表单数据,系统竟然很玄妙的又返回了一个上报成功:

通过身边有权限的同学进入系统后台一看:果然有两条成功上报数据,好在后台没有对同一天内的重复上报行为加以警报或因重复上报行为引起统计系统的崩溃,捏了一把冷汗(后来刻意看了一下,如果当日已成功上报则会在用户再次获取健康上报前端页面时得到已上报页面从而拒绝其重复上报请求,当然验证时是换过sessionid的,这里的漏洞问题不大,维持现状即可)。。

考虑到自己的时间安排,到这里其实已经不想搞了,但是戏剧性极强的是有多位同学似乎遇到了和我开篇提到的同样的困扰而在此时和我讨论“能否正常就自动化免得遗忘”的问题。基于此情况,结合之前的发现提供了以下三种理论可行的解决方案:

留出2个晚上的预算(8月4号和5号(实际4、6号))通过方案一解决了问题,下面将列出具体代码和简要过程供有能力且有兴趣的小伙伴学习参考。尤其提醒可以成功实现的小伙伴一定要坚持诚实上报自己身体的异常数据,否则后果自负。

除此之外,抓包时发现了一个意外的大彩蛋(相关管理人员请仔细阅读以下两段,其他人可略过):

新中新工作人员/学校信息处老师如果看到这里,请一定结合控制器(Controller)仔细检查/wechat/basicQuery/getCardInfo.html视图(View)的实际意义,每次每人获得sessionid后都会通过http协议(注意这可不是https)自动请求它。而它却包含了当前用户的所有敏感信息(包括但不限于姓名、手机号、身份证号、银行卡号码等大量个人重要信息)这就意味着任何一个人只要短时间内访问过“校园卡”后台(哪怕没有健康上报),他的上述信息都会自动同时地明文暴露在网络中,任何具有上层/同层网络权限的人/黑客都可以轻松窃取。甚至具有一定Web基础的人就可以通过钓鱼网站以CSRF等方式轻松窃取。

建议:如果非必要,不要把这些信息暴露在前端传输(以Session变量、每次读取数据库等后端方式都可解决);如果确实需要,请采用一定加密算法或HTTPS协议安全传输这些数据。

实现

环境准备

  • 一台长期开机的服务器(Windows/Linux系统均可)
  • Python 3.7 环境
  • rerequestsMySQLdbsmtplibpython第三方库
  • Mysql数据库环境
  • PHP环境(非必须、供状态信息查询和sessionid交互式入库)
  • 健康表单信息(抓包得到)
  • 通过计划任务(linux可通过crontab实现)每天定点运行health.py

环境配置

安装第三方Python库

如遇pip install 库名无法安装情况,在官网下载对应python版本的whl包再pip install 文件名即可解决,如MySQLdb在我电脑上无法直接安装,可在https://pypi.org/project/mysqlclient/#files 下载后:

pip install mysqlclient-1.3.14-cp37-cp37m-win_amd64.whl

出现:

Sucessfully installed mysqlclient-1.3.14

说明安装成功。

sessionid的获取

推荐度:PC微信+fiddler/wireshark >PC微信+burp > 手机微信+PC burp

由于sessionid保存在cookie中,因此会话清除cookie并重新建立会话即可获取。

如何清除cookie:安卓手机微信可通过http://debugx5.qq.com/ 开启调试模式,若提示x5内核不支持可以访问http://debugmm.qq.com/?forcex5=true 启用x5内核,调试模式页面有清除cookie选项;iOS/PC微信退登+杀进程+重登可清除cookie

配置MySQL数据库

功能:

存放sessionid,连接health_keep.pyhealth.pyindex.php,作为其数据共享的桥梁。

代码:

-- 主机: localhost
-- 生成日期: 2021-08-07 22:22:44
-- 服务器版本: 5.7.26
-- PHP 版本: 7.3.4

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- 数据库: `health`
--

-- --------------------------------------------------------

--
-- 表的结构 `henau_health`
--

CREATE TABLE `henau_health` (
  `id` int(11) NOT NULL,
  `sessionid` varchar(50) NOT NULL,
  `date` datetime DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

--
-- 转储表的索引
--

--
-- 表的索引 `henau_health`
--
ALTER TABLE `henau_health`
  ADD PRIMARY KEY (`id`);

--
-- 在导出的表使用AUTO_INCREMENT
--

--
-- 使用表AUTO_INCREMENT `henau_health`
--
ALTER TABLE `henau_health`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
COMMIT;

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

health_keep.py

功能:

以轮询的方式维持数据库中所有sessionid的存活状态,此处动态生成的日志信息(keep_session_info.php,供通过远程Web访问获取程序运行状态)强烈建议设置访问密码(在第51行,文件日志信息包含的jsessionid为重要信息,泄露会导致他人可获取你的大量个人信息/根据你的个人信息进行校园卡相关各种操作);动态生成的日志文件因需加密为php格式,如需访问请将其放在web可访问目录(若同时存放python脚本文件建议在web服务器配置文件中将.py设为禁止访问类型)

代码:

from requests import Request, Session
import re,time
import email.mime.multipart
import email.mime.text
import smtplib
import MySQLdb

# 查询本地数据库中数据的条数
def db_count(db_wm,db_nm):
    # 数据库信息(输入自己的对应信息)
    db = MySQLdb.connect(host='localhost', user='数据库用户名(输入自己的对应信息)', passwd='数据库密码(输入自己的对应信息)', db=db_wm, charset='utf8')
    cursor = db.cursor()
    query = " select count(*) from {}".format(db_nm)
    cursor.execute(query)
    db.commit()
    count_wm = cursor.fetchall()
    return count_wm[0][0]

# 根据下标获取sessionid的值
def db_find(value):
    # 数据库信息(数据库名默认为health,输入自己的对应信息)
    conn = MySQLdb.connect(host='localhost',user='数据库用户名(输入自己的对应信息)',passwd='数据库密码(输入自己的对应信息)',db='health',charset='utf8')
    cursor = conn.cursor()    

    # 查询sessionid
    count = cursor.execute('select sessionid from henau_health') # 数据表名默认为henau_health,可根据需要输入自己的对应信息
        
    sessionid_list=[]

    # 获取所有结果     
    results = cursor.fetchall()
    result=list(results)    
    for r in result:    
        sessionid_list.append(('%s' % r))

    sessionid = sessionid_list[value]

    # 游标归零
    cursor.scroll(0,mode='absolute') 
     
    conn.close()   

    return sessionid

# 创建日志文件
info = '''
<?php // 访问密码
$url = 'http://xxxxxx.com/keep_session_info.php'; // 日志文件的url(输入自己的对应信息)
//echo $url;
if (!session_id()){session_start();};
if(isset($_POST['password']) && $_POST['password'] == 'xxxxxx'){ // 设置日志信息的访问密码(输入自己的对应信息)
    $_SESSION['recheck'] = 1;
    header('location:'.$url);
}
if(!isset($_SESSION['recheck'])){
    exit('<!-- 引入 layui.css -->
    <link rel="stylesheet" href="//unpkg.com/layui@2.6.8/dist/css/layui.css">
    <!-- 引入 layui.js -->
    <script src="//unpkg.com/layui@2.6.8/dist/layui.js"></script><div id="divcss">
        <form method="post" class="layui-form layui-form-pane" style="margin:15px;">
            <div class="layui-form-item">
                <label for="" class="layui-form-label">访问密码:</label>
                    <div class="layui-input-inline">
                        <input type="password" name="password" class="layui-input"/>
                    </div>
            </div>
            <div class="layui-form-item">
                <div class="layui-input-inline">
                    <button type="submit" class="layui-btn">提交</button>
                    <button type="reset" class="layui-btn layui-btn-primary">重置</button>
                </div>
            </div>
        </form>
    </div>
    ');
}
?>
'''
html = open('keep_session_info.php','w+', encoding="utf-8")
print(info,file=html)
print('<br>',file=html)
html.close()

while 1:
    try:
        # 从数据库获取数据条数
        num = db_count('health','henau_health')
        for i in range (0,num):
            sessionid = db_find(i)
            url = 'http://yktwx.henau.edu.cn/wechat/basicQuery/getPaygwServiceTime.html'
            # 维持sessionid的表单
            data = 'paytype=weixin'

            headers = {
                'Host': 'yktwx.henau.edu.cn',
                'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12A365 MicroMessenger/5.4.1 NetType/WIFI',
                'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
                'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
                'Accept-Encoding': 'gzip, deflate',
                'Content-Type': 'application/x-www-form-urlencoded',
                'Content-Length': '607',
                'Origin': 'http://yktwx.henau.edu.cn',
                'Connection': 'keep-alive',
                'Referer': 'http://yktwx.henau.edu.cn/wechat/basicQuery/getPaygwServiceTime.html',
                'Upgrade-Insecure-Requests': '1',
                'Cookie': sessionid
            }
            s = Session()
            req = Request('POST', url, data=data, headers=headers)
            prepped = s.prepare_request(req)
            time.sleep(1) # 休息一秒

            r = s.send(prepped)
            # time.sleep(2) # 休息两秒

            #获取时间
            now_yue = time.strftime("%m", time.localtime()) 
            now_ri = time.strftime("%d", time.localtime()) 
            now_shi = time.strftime("%H", time.localtime()) 
            now_fen = time.strftime("%M", time.localtime()) 

            # 生成提示信息
            info = now_yue + "月" + now_ri + "日" + now_shi + '点' + now_fen + '分' + ',尝试维持' + sessionid + ',返回信息如下:'

            # 向控制台输出
            print(info)

            # 向文件输出
            html = open('keep_session_info.php','a+', encoding="utf-8")
            print(info,file=html)
            print('<br>',file=html)
            html.close()
            
            # 向控制台输出返回信息
            print(r.text)

            # 向文件输出返回信息
            html = open('keep_session_info.php','a+', encoding="utf-8")
            print(r.text,file=html)
            print('<br><br>',file=html)
            html.close()


            # send_mail()
            time.sleep(5) #每5秒维护一次
    except Exception:
        pass

health.py

功能:

每次运行都会从数据库中取出一条sessionid并通过其进行健康上报,上报成功会从数据库中自动删除这条sessionid记录,不论健康上报成功或失败都会触发邮件提醒告知上报状态,若sessionid数少于阈值(可设置)也会触发邮件提醒。

代码:

from requests import Request, Session
import re,time
import email.mime.multipart
import email.mime.text
import smtplib
import MySQLdb
# 查询本地数据库中数据的条数
def db_count(db_wm,db_nm):
    # 数据库信息(输入自己的对应信息)
    db = MySQLdb.connect(host='localhost', user='数据库用户名(输入自己的对应信息)', passwd='数据库密码(输入自己的对应信息)', db=db_wm, charset='utf8')
    cursor = db.cursor()
    query = " select count(*) from {}".format(db_nm)
    cursor.execute(query)
    db.commit()
    count_wm = cursor.fetchall()
    return count_wm[0][0]

# 根据下标获取sessionid的值
def db_find(value):
    # 数据库信息(数据库名默认为health,输入自己的对应信息)
    conn = MySQLdb.connect(host='localhost',user='数据库用户名(输入自己的对应信息)',passwd='数据库密码(输入自己的对应信息)',db='health',charset='utf8')
    cursor = conn.cursor()    

    # 查询sessionid
    count = cursor.execute('select sessionid from henau_health') # 数据表名默认为henau_health,可根据需要输入自己的对应信息
        
    sessionid_list=[]

    # 获取所有结果     
    results = cursor.fetchall()
    result=list(results)    
    for r in result:    
        sessionid_list.append(('%s' % r))

    sessionid = sessionid_list[value]

    # 游标归零
    cursor.scroll(0,mode='absolute') 
     
    conn.close()   

    return sessionid

def del_data(sessionid):
    # 数据库信息(数据库名默认为health,输入自己的对应信息)
    conn = MySQLdb.connect(host='localhost',user='数据库用户名(输入自己的对应信息)',passwd='数据库密码(输入自己的对应信息)',db='health',charset='utf8')
    cursor = conn.cursor()    
    delete_sql = "delete from henau_health where sessionid=\'" + sessionid + "\'" # 数据表名默认为henau_health,可根据需要输入自己的对应信息
    print(delete_sql)
    cursor.execute(delete_sql)
    print(cursor.rowcount)
    conn.close()  
    return True

def success_mail():
    title = '健康上报成功提醒!'
    # 获取系统时间
    now_yue = time.strftime("%m", time.localtime()) 
    now_ri = time.strftime("%d", time.localtime()) 
    now_shi = time.strftime("%H", time.localtime()) 
    now_fen = time.strftime("%M", time.localtime()) 
    content = now_yue + "月" + now_ri + "日" + now_shi + '点' + now_fen + '分' + ',健康上报成功!'
    # print(content)
    ret = True
    FROM_MAIL = "xxxxxx@163.com" # 发送邮箱(输入自己的对应信息)
    TO_MAIL = "xxxxxx@qq.com" # 接收邮箱(输入自己的对应信息)
    SMTP_SERVER = 'smtp.163.com' # smtp服务器(输入自己的对应信息)
    SSL_PORT = '465'
    USER_NAME = FROM_MAIL 
    USER_PWD = "xxxxxxxxxxxx" # 邮箱验证密钥(输入自己的对应信息)
    msg = email.mime.multipart.MIMEMultipart()
    msg['from'] = FROM_MAIL
    # msg['to'] = ';'.join(TO_MAIL) 
    msg['to'] = TO_MAIL
    msg['subject'] = title
    txt = email.mime.text.MIMEText(content)
    txt["Content-Type"] = 'text/html; charset=UTF-8'
    msg.attach(txt)

    try:
        # 纯粹的ssl加密方式
        smtp = smtplib.SMTP_SSL(SMTP_SERVER, SSL_PORT)   #邮件服务器地址和端口
        smtp.ehlo()  # 用户认证
        smtp.login(USER_NAME, USER_PWD)  # 括号中对应的是发件人邮箱账号、邮箱密码
        smtp.sendmail(FROM_MAIL, TO_MAIL, msg.as_string())  # 收件人邮箱账号、发送邮件
        smtp.quit()  # 等同 smtp.close()  ,关闭连接
    except Exception as e:
        ret = False
        print(">>>>>>>:" + e)

def fail_mail():
    title = '健康上报失败提醒!'
    # 获取系统时间
    now_yue = time.strftime("%m", time.localtime()) 
    now_ri = time.strftime("%d", time.localtime()) 
    now_shi = time.strftime("%H", time.localtime()) 
    now_fen = time.strftime("%M", time.localtime()) 
    content = now_yue + "月" + now_ri + "日" + now_shi + '点' + now_fen + '分' + ',健康上报失败!'
    # print(content)
    ret = True
    FROM_MAIL = "xxxxxx@163.com" # 发送邮箱(输入自己的对应信息)
    TO_MAIL = "xxxxxx@qq.com" # 接收邮箱(输入自己的对应信息)
    SMTP_SERVER = 'smtp.163.com' # smtp服务器(输入自己的对应信息)
    SSL_PORT = '465'
    USER_NAME = FROM_MAIL 
    USER_PWD = "xxxxxxxxxxxx" # 邮箱验证密钥(输入自己的对应信息)
    msg = email.mime.multipart.MIMEMultipart()
    msg['from'] = FROM_MAIL
    # msg['to'] = ';'.join(TO_MAIL) 
    msg['to'] = TO_MAIL
    msg['subject'] = title
    txt = email.mime.text.MIMEText(content)
    txt["Content-Type"] = 'text/html; charset=UTF-8'
    msg.attach(txt)

    try:
        # 纯粹的ssl加密方式
        smtp = smtplib.SMTP_SSL(SMTP_SERVER, SSL_PORT)   #邮件服务器地址和端口
        smtp.ehlo()  # 用户认证
        smtp.login(USER_NAME, USER_PWD)  # 括号中对应的是发件人邮箱账号、邮箱密码
        smtp.sendmail(FROM_MAIL, TO_MAIL, msg.as_string())  # 收件人邮箱账号、发送邮件
        smtp.quit()  # 等同 smtp.close()  ,关闭连接
    except Exception as e:
        ret = False
        print(">>>>>>>:" + e)
        
def warn_mail(num):
    title = '健康Session仅剩' + str(num-1) +'条,请及时添加!' # 因为紧接着会删除一条,因此数量会-1
    # 获取系统时间
    now_yue = time.strftime("%m", time.localtime()) 
    now_ri = time.strftime("%d", time.localtime()) 
    now_shi = time.strftime("%H", time.localtime()) 
    now_fen = time.strftime("%M", time.localtime()) 
    content = '截至' + now_yue + "月" + now_ri + "日" + now_shi + '点' + now_fen + '分,' + '健康Session仅剩' + str(num-1) +'条,请及时添加!'
    # print(content)
    ret = True
    FROM_MAIL = "xxxxxx@163.com" # 发送邮箱(输入自己的对应信息)
    TO_MAIL = "xxxxxx@qq.com" # 接收邮箱(输入自己的对应信息)
    SMTP_SERVER = 'smtp.163.com' # smtp服务器(输入自己的对应信息)
    SSL_PORT = '465'
    USER_NAME = FROM_MAIL 
    USER_PWD = "xxxxxxxxxxxx" # 邮箱验证密钥(输入自己的对应信息)
    msg = email.mime.multipart.MIMEMultipart()
    msg['from'] = FROM_MAIL
    # msg['to'] = ';'.join(TO_MAIL) 
    msg['to'] = TO_MAIL
    msg['subject'] = title
    txt = email.mime.text.MIMEText(content)
    txt["Content-Type"] = 'text/html; charset=UTF-8'
    msg.attach(txt)

    try:
        # 纯粹的ssl加密方式
        smtp = smtplib.SMTP_SSL(SMTP_SERVER, SSL_PORT)   #邮件服务器地址和端口
        smtp.ehlo()  # 用户认证
        smtp.login(USER_NAME, USER_PWD)  # 括号中对应的是发件人邮箱账号、邮箱密码
        smtp.sendmail(FROM_MAIL, TO_MAIL, msg.as_string())  # 收件人邮箱账号、发送邮件
        smtp.quit()  # 等同 smtp.close()  ,关闭连接
    except Exception as e:
        ret = False
        print(">>>>>>>:" + e)


# 从数据库获取数据条数
num = db_count('health','henau_health')
# print(num)
if (num < 5): # 指定小于多少条邮件提醒(输入自己的对应信息)
    warn_mail(num)

# 从数据库获取一条sessionid
sessionid = db_find(0)
print(sessionid)

# 从数据库删除获取到的这条sessionid
del_flag = del_data(sessionid)
if del_flag:
    print("删除" + sessionid + "成功!")
else:
    print("删除失败!")

# 尝试健康上报
url = 'http://yktwx.henau.edu.cn/wechat/health/addHealthReaport.html'
# 健康上报表单(输入自己的对应信息)格式id=&phone=xxxxxxxxxxxx&jqqx=xxxxxxxxxxxx&qxbz=&zt=xxxxxxxxxxxx&tw=xxxxxxxxxxxx&stzk=xxxxxxxxxxxx&stzkbz=&sfwlwh=xxxxxxxxxxxx&sfjc=xxxxxxxxxxxx.......
data = 'xxxxxxxxxxxxxxxxxxxxxxxx'

headers = {
    'Host': 'yktwx.henau.edu.cn',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
    'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
    'Accept-Encoding': 'gzip, deflate',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length': '607',
    'Origin': 'http://yktwx.henau.edu.cn',
    'Connection': 'keep-alive',
    'Referer': 'http://yktwx.henau.edu.cn/wechat/health/report.html?jkbh=0023',
    'Upgrade-Insecure-Requests': '1',
    'Cookie': sessionid
}
s = Session()
req = Request('POST', url, data=data, headers=headers)
prepped = s.prepare_request(req)
time.sleep(1) # 休息一秒

r = s.send(prepped)
time.sleep(2) # 休息两秒

#获取时间
now_yue = time.strftime("%m", time.localtime()) 
now_ri = time.strftime("%d", time.localtime()) 
now_shi = time.strftime("%H", time.localtime()) 
now_fen = time.strftime("%M", time.localtime()) 

# 生成提示信息
info = now_yue + "月" + now_ri + "日" + now_shi + '点' + now_fen + '分' + ',尝试健康上报,返回信息如下:'

# 向控制台输出
print(info)

# 向控制台输出返回信息
# print(r.text)

# 不管成功失败,都要邮件提醒
state = str(r.text)
print(state)
if (re.match('.*上报健康状态成功.*', state)):
    success_mail()
else:
    fail_mail()
# success_mail()

index.php

效果:

未登录(未正确提交访问密码)状态:

已登录(正确提交访问密码)状态:

功能:

方便远程录入sessionid,提交的值会存入数据库。

代码:

<?php // 访问密码
$url = 'http://xxxxxx.com/'; // 当前文件可被访问的url(可用子目录,输入自己的对应信息)
//echo $url;
if (!session_id()){session_start();};
if(isset($_POST['password']) && $_POST['password'] == 'xxxxxx'){  // 设置日志信息的默认访问密码(输入自己的对应信息)
    $_SESSION['recheck'] = 1;
    header('location:'.$url);
}
if(!isset($_SESSION['recheck'])){
    exit('<!-- 引入 layui.css -->
    <link rel="stylesheet" href="//unpkg.com/layui@2.6.8/dist/css/layui.css">
    <!-- 引入 layui.js -->
    <script src="//unpkg.com/layui@2.6.8/dist/layui.js"></script><div id="divcss">
        <form method="post" class="layui-form layui-form-pane" style="margin:15px;">
            <div class="layui-form-item">
                <label for="" class="layui-form-label">访问密码:</label>
                    <div class="layui-input-inline">
                        <input type="password" name="password" class="layui-input"/>
                    </div>
            </div>
            <div class="layui-form-item">
                <div class="layui-input-inline">
                    <button type="submit" class="layui-btn">提交</button>
                    <button type="reset" class="layui-btn layui-btn-primary">重置</button>
                </div>
            </div>
        </form>
    </div>
    ');
}
?>
<?php // sessionid数据入库

    if ($_SESSION['recheck'] == 1 && isset($_POST['sessionid'])) {
        header('Content-Type:text/html;charset=utf-8');
        define('DB_HOST','127.0.0.1:3306'); // 数据库socket,默认为本机+3306端口,输入自己的对应信息
        define('DB_USER','xxxxxx'); // 数据库用户名,输入自己的对应信息
        define('DB_PWD','xxxxxx'); // 数据库密码,输入自己的对应信息
        define('DB_NAME','health'); // 数据库名默认为health,输入自己的对应信息
        $con=@mysqli_connect(DB_HOST,DB_USER,DB_PWD);
        mysqli_select_db($con,DB_NAME) or die('数据库错误,错误信息:' . mysqli_error($con));
        mysqli_query($con,'SET NAMES UTF8') or die('字符集设置错误(无指定的字符集)!');

        $sessionid = $_POST['sessionid'];
        $sessionid=addslashes($sessionid);
        $sessionid=str_replace("%","\%",$sessionid);
        $sessionid=str_replace("_","\_",$sessionid);
        // 数据表名默认为henau_health,可根据需要输入自己的对应信息
        $query = "INSERT INTO henau_health(sessionid,date)VALUES('$sessionid',NOW())";
        mysqli_query($con,$query) or die('新增记录时出现错误:'. mysqli_error($con));
        mysqli_close($con);
        echo "
        <!-- 引入 layui.css -->
        <link rel='stylesheet' href='//unpkg.com/layui@2.6.8/dist/css/layui.css'>
        <!-- 引入 layui.js -->
        <script src='//unpkg.com/layui@2.6.8/dist/layui.js'></script>
        <script>
            layui.layer.msg('数据成功添加到数据库!');

        </script>";
    }

?>
<!DOCTYPE html>
    <html>
    <head>
        <title>新增SessionID</title>
        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
        <meta charset="utf-8">
        <!-- 引入 layui.css -->
        <link rel="stylesheet" href="//unpkg.com/layui@2.6.8/dist/css/layui.css">
        <!-- 引入 layui.js -->
        <script src="//unpkg.com/layui@2.6.8/dist/layui.js"></script>
    </head>
    <body>
    <fieldset class="layui-elem-field layui-field-title" style="margin-top: 20px;">
    <legend>新增SessionID</legend></fieldset>
    <form action="" method="post" class="layui-form layui-form-pane" style="margin:15px;">
        <div class="layui-form-item">
        <label for="" class="layui-form-label">SessionID:</label>
        <div class="layui-input-inline">
            <input type="text" required lay-verify="required" name="sessionid" class="layui-input" placeholder="请输入SessionID">
        </div>
        </div>
        <div class="layui-form-item">
            <div class="layui-input-inline">
                <button type="submit" class="layui-btn">提交</button>
                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
            </div>
        </div>
    </form>
    </body>
    </html>

定时运行

以下是本人Windows计划任务导出的xml文件和相关脚本内容,Linux只需用crontab每日自动运行一次health.py即可,本人没做测试:

健康上报.xml

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2021-08-01T04:08:54.1892392</Date>
    <Author>iZbhw308oufmc4Z\Administrator</Author>
  </RegistrationInfo>
  <Triggers>
    <CalendarTrigger>
      <StartBoundary>2021-08-01T00:05:06</StartBoundary>
      <Enabled>true</Enabled>
      <ScheduleByDay>
        <DaysInterval>1</DaysInterval>
      </ScheduleByDay>
    </CalendarTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">
      <UserId>iZbhw308oufmc4Z\Administrator</UserId>
      <LogonType>Password</LogonType>
      <RunLevel>LeastPrivilege</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <AllowHardTerminate>false</AllowHardTerminate>
    <StartWhenAvailable>false</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>C:\phpstudy_pro\WWW\health\auto\start.vbs</Command>
    </Exec>
  </Actions>
</Task>

start.vbs

set shell=wscript.createObject("wscript.shell")  
run=shell.Run("C:\phpstudy_pro\WWW\health\auto\start.bat", 0)

start.bat

cd /phpstudy_pro/WWW/health
set http_proxy=
start /b cmd /k  "C:\Users\Administrator\AppData\Local\Programs\Python\Python37\python health.py"

8.16晚更新

Web校园卡

[免责声明]:除非你能够完全看懂如下代码和相关文字说明并完全按照提示/更好的安全机制操作,否则不建议尝试此部分(尤其在公网环境)。任何人参考本文思路尝试/可能/已经导致的一切不良后果,本站/本人不承担任何责任。

安全性:

  1. 数据建议高度保密(涉及到校园卡money)
  2. 建议采用https协议传输
  3. 中间文件名和二维码图片名建议设置为较复杂的组合
  4. 访问密码一定要设置成强密码

改进空间:

  1. 访问密码尝试错误一定次数要求验证码或直接封ip地址
  2. 二维码图片名随机生成并拥有合理的自动删除机制

配套环境:

  1. 二维码图片采用PHP QR Code开源模块动态生成,因此需下载其模块才能使用
  2. 下载的压缩包经过解压后需放到phpqrcode目录下并确保里面的phpqrcode.php文件可被本地访问
  3. 下载地址:点击这里 备用地址

经过两天吃饭时的测试,动态二维码可用于餐厅买饭!

效果展示如下:

登录(身份认证成功)前:

登录(身份认证成功)后:

其中二维码每3秒钟便会自动刷新一次。

代码记录如下:

spider.py

from requests import Request, Session
import re,time
import MySQLdb
# 根据下标获取sessionid的值
def db_find(value):
    # 数据库信息(数据库名默认为health,输入自己的对应信息)
    conn = MySQLdb.connect(host='localhost',user='数据库用户名(输入自己的对应信息)',passwd='数据库密码(输入自己的对应信息)',db='health',charset='utf8')
    cursor = conn.cursor()    

    # 查询sessionid
    count = cursor.execute('select sessionid from henau_health') # 数据表名默认为henau_health,可根据需要输入自己的对应信息
        
    sessionid_list=[]

    # 获取所有结果     
    results = cursor.fetchall()
    result=list(results)    
    for r in result:    
        sessionid_list.append(('%s' % r))

    sessionid = sessionid_list[value]

    # 游标归零
    cursor.scroll(0,mode='absolute') 
     
    conn.close()   

    return sessionid

while 1:
    # 从数据库获取一条sessionid
    sessionid = db_find(0)
    try:
        url = 'http://yktwx.henau.edu.cn/wechat/pay/getBarcode.html'
        
        # 表单
        data = 'paytype=1&payacc=000'

        headers = {
            'Host': 'yktwx.henau.edu.cn',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
            'Accept-Encoding': 'gzip, deflate',
            'Content-Type': 'application/x-www-form-urlencoded',
            'Content-Length': '607',
            'Origin': 'http://yktwx.henau.edu.cn',
            'Connection': 'keep-alive',
            'Referer': 'http://yktwx.henau.edu.cn/wechat/basicQuery/queryElecRoomInfo.html',
            'Upgrade-Insecure-Requests': '1',
            'Cookie': sessionid
        }
        s = Session()
        req = Request('POST', url, data=data, headers=headers)

        prepped = s.prepare_request(req)
        time.sleep(1) # 休息一秒

        r = s.send(prepped)
        print(r.text)

        # 提取结果
        zz = re.findall(r"arcode\"\:\"(.*)\",\"a", r.text)
        info = zz[0]

        print(info)
        f = open('设定一个保密的中间文件名1(输入自己的对应信息)','w+')
        print(info,file=f)
        f.close()
        time.sleep(10)

    except Exception:
        pass

index.php

<?php // 访问密码
$url = 'https://xxxxx'; // 日志文件的url(输入自己的对应信息)
//echo $url;
if (!session_id()){session_start();};
if(isset($_POST['password']) && $_POST['password'] == 'xxxxxxx'){ // 设置访问密码(输入自己的对应信息)
    $_SESSION['recheck'] = 1;
    header('location:'.$url);
}
if(!isset($_SESSION['recheck'])){
    exit('<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
    <!-- 引入 layui.css -->
    <link rel="stylesheet" href="//unpkg.com/layui@2.6.8/dist/css/layui.css">
    <!-- 引入 layui.js -->
    <script src="//unpkg.com/layui@2.6.8/dist/layui.js"></script><div id="divcss">
        <form method="post" class="layui-form layui-form-pane" style="margin:15px;">
            <div class="layui-form-item">
                <label for="" class="layui-form-label">访问密码:</label>
                    <div class="layui-input-inline">
                        <input type="password" name="password" class="layui-input"/>
                    </div>
            </div>
            <div class="layui-form-item">
                <div class="layui-input-inline">
                    <button type="submit" class="layui-btn">提交</button>
                    <button type="reset" class="layui-btn layui-btn-primary">重置</button>
                </div>
            </div>
        </form>
    </div>
    ');
}
?>

<?php

    $fn = fopen("设定一个保密的中间文件名1(输入自己的对应信息)","r");
    $result = fgets($fn);
    // echo $result;
    fclose($fn);
    include 'phpqrcode/phpqrcode.php'; 
    $value = "$result"; //二维码内容
    $errorCorrectionLevel = 'L';//容错级别
    $matrixPointSize = 6;//生成图片大小
    //生成二维码图片
    QRcode::png($value, 'qrcode.png', $errorCorrectionLevel, $matrixPointSize, 2);
    $logo = 'logo.png';//准备好的logo图片
    $QR = 'qrcode.png';//已经生成的原始二维码图
       
    if ($logo !== FALSE) {
        @$QR = imagecreatefromstring(file_get_contents($QR));
        @$logo = imagecreatefromstring(file_get_contents($logo));
        @$QR_width = imagesx($QR);//二维码图片宽度
        @$QR_height = imagesy($QR);//二维码图片高度
        @$logo_width = imagesx($logo);//logo图片宽度
        @$logo_height = imagesy($logo);//logo图片高度
        @$logo_qr_width = $QR_width / 5;
        @$scale = $logo_width/$logo_qr_width;
        @$logo_qr_height = $logo_height/$scale;
        @$from_width = ($QR_width - $logo_qr_width) / 2;
        //重新组合图片并调整大小
        @imagecopyresampled($QR, $logo, $from_width, $from_width, 0, 0, $logo_qr_width, 
        @$logo_qr_height, $logo_width, $logo_height);
    }

    $filename = '设定一个保密的图片文件名2(输入自己的对应信息).png';
    imagepng($QR, $filename);
    echo '<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0"><link rel="apple-touch-icon" href="此处可以自定义苹果设备桌面应用ico(输入自己的对应信息)"/>
    <meta name="apple-mobile-web-app-capable" content="yes"><title>电子校园卡</title><script>setTimeout("location=location; ", 3000(3秒钟自动刷新一次,可修改,输入自己的对应信息)); </script><center><h2>校园卡二维码</h2><img src='.$filename.' style="width:80%;height:auto;"></center>';

?>

2022.01.14更新

由于后续发现本方案中的sessionid时常因学校服务器的短暂50x而失效,考虑到重新抓包入库工作的复杂性,本人决定改用基于OpenCV+PyautoGUI的本地识别及构造表单方案用了数月。由于那段所写程序的使用门槛过低,为避免给学校管理造成不必要的麻烦,至今一直不考虑将其在博客公开。近期有将自动化程序从笔记本电脑转移到台式机运行的需求,为方便计划任务的部署操作,特将导出的XML文本记录如下:

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2021-08-01T04:08:54.1892392</Date>
    <Author>iZbhw308oufmc4Z\Administrator</Author>
    <URI>\health</URI>
  </RegistrationInfo>
  <Triggers>
    <CalendarTrigger>
      <StartBoundary>2021-08-01T00:03:06</StartBoundary>
      <Enabled>true</Enabled>
      <ScheduleByDay>
        <DaysInterval>1</DaysInterval>
      </ScheduleByDay>
    </CalendarTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">
      <UserId>S-1-5-21-1310891779-4248587301-955746212-1001</UserId>
      <LogonType>InteractiveToken</LogonType>
      <RunLevel>LeastPrivilege</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <AllowHardTerminate>false</AllowHardTerminate>
    <StartWhenAvailable>false</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
    <UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>C:\startup\health_auto.vbs</Command>
    </Exec>
  </Actions>
</Task>

参考资料:

pypi.org/project/mysqlclient/#files

www.runoob.com/python/python-mysql.html

blog.csdn.net/beyond__devil/article/details/109544402

blog.csdn.net/weixin_42840933/article/details/85274313

blog.csdn.net/mouday/article/details/90606464

blog.csdn.net/imjtrszy/article/details/47030781

www.cnblogs.com/liangzp/p/8512820.html

www.php.cn/php-weizijiaocheng-402924.html

最后修改:2022 年 01 月 14 日 09 : 11 PM

1 条评论

  1. 东边的大西瓜

    给大佬点赞

发表评论 取消回复