前言
- 本项目仅为本人学习测试使用,健康状态会按真实情况上报
- 本文章/思路仅供网络爱好者学习参考,请勿用于非法用途
- 本文如有幸被
新中新工作人员
/学校信息处老师
看到,希望你们仔细阅读“探索”部分并启用SSL
证书安全传输数据以解决全校师生隐私信息泄露的隐患
探索
7月31号,随着疫情忽然的严重,学校开始要求恢复每日健康上报。然而,就本人而言,绝大多数时候身体状态都是健康的(偶尔的懒散通常都是睡一觉就会好了)。每天都得想着这件事按时点进页面点一下提交按钮就显得有点折磨(考虑到疫情防控需要,如果要求大家都只需上报异常数据估计这个系统就没几个人能记得了,可以理解)。于是当天对我的相关表单抓下了数据包,第二天尝试用它通过之前写的查电费爬虫的sessionid
上报,得到了以下返回信息:
如果不停发包,随后便会返回上报异常:
嗯,可以理解,毕竟每天就只应该接受一次成功的上报请求嘛。
然而又过了一天它还一直提示“上报异常”,而不是“会话过期”,很显然上报记录(成功与否)被存放在服务器端Session
的一个变量里面。
于是通过一些办法获取到一个新的合法的sessionid
,post
同样的表单数据,系统竟然很玄妙的又返回了一个上报成功:
通过身边有权限的同学进入系统后台一看:果然有两条成功上报数据,好在后台没有对同一天内的重复上报行为加以警报或因重复上报行为引起统计系统的崩溃,捏了一把冷汗(后来刻意看了一下,如果当日已成功上报则会在用户再次获取健康上报前端页面时得到已上报页面从而拒绝其重复上报请求,当然验证时是换过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
环境re
、requests
、MySQLdb
、smtplib
等python
第三方库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.py
、health.py
和index.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校园卡
[免责声明]:除非你能够完全看懂如下代码和相关文字说明并完全按照提示/更好的安全机制操作,否则不建议尝试此部分(尤其在公网环境)。任何人参考本文思路尝试/可能/已经导致的一切不良后果,本站/本人不承担任何责任。
安全性:
- 数据建议高度保密(涉及到校园卡money)
- 建议采用
https
协议传输 - 中间文件名和二维码图片名建议设置为较复杂的组合
- 访问密码一定要设置成强密码
改进空间:
- 访问密码尝试错误一定次数要求验证码或直接封
ip
地址 - 二维码图片名随机生成并拥有合理的自动删除机制
配套环境:
- 二维码图片采用
PHP QR Code
开源模块动态生成,因此需下载其模块才能使用 - 下载的压缩包经过解压后需放到
phpqrcode
目录下并确保里面的phpqrcode.php
文件可被本地访问 - 下载地址:点击这里 备用地址
经过两天吃饭时的测试,动态二维码可用于餐厅买饭!
效果展示如下:
登录(身份认证成功)前:
登录(身份认证成功)后:
其中二维码每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
版权属于:soarli
本文链接:https://blog.soarli.top/archives/540.html
转载时须注明出处及本声明。
给大佬点赞