-
主要目的:学习且了解网站加密方式
-
次要目的:怎么通过技术手段,自动处理人情世故
(DA KA) -
网站地址(
Base64加密):aHR0cHM6Ly90Yi5nZGVpLmVkdS5jbi9sb2dpbg==- 参考网站:
https://base64.us/
- 参考网站:
分析
登录成功后,进入打卡界面后,通过开发者工具,可以看到当点击每日健康填报中的点击一键打卡时,主要是发送一个Ajax请求
点击数据包,观察请求地址和响应
-
请求地址:
https://xxxx/system/yqdc/yjtb?+_=xxx- 明显看出请求携带的查询参数是一个时间戳
-
请求方式:
GET -
返回响应:
打卡成功-1 OR 打卡失败-3
那么,明白了打卡原理后,首先想到的就是可以通过模拟浏览器发送请求,以此来达到人手动打卡的目的。
-
模拟浏览器发送请求:
requests模块 -
那么模拟请求肯定不会那么简单,首先明确知道的是打卡请求携带的查询参数是时间戳
-
时间戳:可通过
time模块自动生成-
str(int(time.time() * 1000))
-
-
-
其次,请求时带上的请求头中一般除
UA外,还要分析需不需要其他 -
通过多次请求(退出登录后,重新登录打卡),查看数据包,观察得出请求头中的
cookie是会发生改变的,也就是cookie是有时效期此时能想到的处理方法有两种:
-
查看
js代码,了解cookie生成的本质(破解cookie生成) -
基于Session对象实现自动处理
- 那么使用session,就意味着session对象至少要使用两次,第一次使用session发送请求的过程中,如果产生了cookie,则cookie会被自动存储到该session对象中,而到了下次再次使用session对象发送请求时,该请求就会携带之前存储的cookie进行请求发送。
那么第一次使用session发送什么请求才能拿到cookie呢?
- 要知道,想打卡成功,前提是登录
所以首先就要用session对象对登录发送请求
在登录界面,随意输入账号和密码,点击登录后,在开发者工具中对登录数据包进行分析:
- 这里输入的账号是
(zhanghao),密码(123456)
此时返回的数据包中,肯定是账号密码错误
但没关系,此时主要想知道登录的时候,它对账号名和密码做了什么处理
那么从数据包中,可以得知,登录的时候是发送了一个
POST请求,并携带上的请求参数中,有之前输入的账号和密码,并且密码是通过加密的。 -
登录密码加密
所以主要来学习一下该网站是通过什么方式来加密密码的
-
在开发者工具中,按
Ctrl+Shift+F,调出搜索框,输入password进行全局搜索,可以看到许多js文件中都有password关键字,但其中有一个叫login.js的文件,就非常值得注意。因为此时我们就是在了解其登录时怎么加密的,所以加密的关键代码最有可能在这个login.js文件中。 -
点进去,按
Ctrl+F,输入password搜索,看到第一处中刚好有一个关键字眼encrypt,也就是加密的意思,所以有理由怀疑这里就是加密关键代码,于是在该行代码以及周围都打上断点。 -
重新登录,观察断点位置,运行到最后一个断点处,就观察到password已经加密完,变成我们想要的形状。
-
断点运行完,与数据包中的一对比,发现断点处的password就是登录最终加密的password
-
所以在
js代码中的login函数上面的,encrypt就是加密的方法。
改写逆向js代码
-
那么既然已经了解
encrypt就是加密的方法,接着就可以把encrypt所在的function函数里的代码都先粘贴进一个新建的js文件中,并进行略微改写。var password = $.common.trim($("input[name='password']").val()) 该代码里的$("input[name='password']",就相当于最开始输入的密码,并赋给一个password的变量 ```$("input[name='password']").val(encrypt.encrypt(password)); 该代码就把输入的密码,加密后重新赋值回原来存代码的input标签中 ```略微改写:
function getPwd(password) { var kk = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCaGxxomLHQmUzALgHx3tV0L/zY0fabQJVPWJeW0O+KBsQdeP/HC015zV6C4ExiBxHkA8RtnSfMkKKBH3/6dyatFCrCxIw5KgtNqmC9dtsdUmDJq8IDW7hFkODAyzDAvuovvGn+wQvRl97iEqUVstnLi7SD9zCuM39AUM4ugtqsBwIDAQAB"; var encrypt = new JSEncrypt(); encrypt.setPublicKey(kk); var password = $.common.trim(password); var pwd = encrypt.encrypt(password); return pwd }其中
$.common.trim和new JSEncrypt()都是未知的。 -
碰到不知道的东西,都可以在开发者工具的
Console中,输入查看,那么很明显$.common.trim就是下图的函数
-
改写为一个函数:
function trim(value) { if (value == null) { return ""; } return value.toString().replace(/(^\s*)|(\s*$)|\r|\n/g, ""); }; -
再把之前的修改一下
var password = $.common.trim(password); 就可以变为 var password = trim(password); -
然后
encrypt.encrypt(password)中,encrypt方法具体干了什么,就可以把鼠标放上去悬停,然后点击进入encrypt这个方法里,查看
-
可以看到encrypt也是一个函数方法
-
但它是被一个叫
rt的函数给包裹住了 -
但
rt这个函数外面也有一层大函数包裹,所以要想正常,简单的使用,只需要把encrypt关键字所在文件的全部代码复制粘贴,然后根据运行报错,修改即可。 -
在改写的
js代码最后,调用创建的getpwd函数,根据运行报错调试。- 调用代码
console.log(getPwd('123456'))
- 运行时可能报错:
1. "Microsoft Internet Explorer" == navigator.appName ? (O.prototype.am = function(t, e, i, r, n, s) { ^ ReferenceError: navigator is not defined `'navigator' 未定义` 解决方法: 定义它:navigator javascript内置对象 在最开头写navigator = this; 2. if (window.crypto && window.crypto.getRandomValues) { ^ ReferenceError: window is not defined `'window' 未定义` window javascript内置对象 解决方法: 在最开头写window = this; 3. var encrypt = new JSEncrypt(); ^ ReferenceError: JSEncrypt is not defined 在最开头写JSEncrypt = this; 在这时候,改写就已经成功,可以使用execjs来调用该js代码,对密码进行加密 但是该代码可能在node.js中运行不成功 依然报错: var encrypt = new JSEncrypt(); ^ TypeError: JSEncrypt is not a constructor 这时它找到了JSEncrypt,但说他不是一个构造函数。 解决方法: window.JSEncrypt = rt, t.JSEncrypt = rt, window.或t.随便删一个 这样在node.js中就能成功运行 - 调用代码
运行成功:
实现人情世故
-
创建
seesion对象,携带上请求头headers,请求参数data,对登录地址,发起post请求。-
verify=False 移除SSL认证
-
-
登录请求成功后,就对打卡地址进行
get请求,携带上查询参数时间戳打卡有两种返回结果,成功和不成功,所以可加上异常捕获
不成功就是3,就可以发送邮件信息提醒(也可以短信啥的),手动打卡
发送邮件失败,就写入日志,等别人提醒你打卡,哈哈。
不过因为这个是个人版,一天只打卡一次吧,只要程序逻辑正确,一般没啥意外
-
有始有终,登录过了,就退出登录
allow_redirects=False 禁止302跳转
-
最后,如果想每天都自动打卡,就把程序部署上服务器。
定时代码:
daka(password.login_username, password.login_password)对打卡过程封装while True: now_hour = time.strftime("%H", time.localtime()) now_min = time.strftime("%M", time.localtime()) if now_hour < "08": rest = 8 - int(now_hour) sleeptime = (rest - 1) * 3600 + (60 - int(now_min)) * 60 print("启动时北京时间为:" + time.strftime("%H:%M", time.localtime()), "\t 打卡程序将在", rest - 1, "小时", int((sleeptime - (rest - 1) * 3600) / 60), "分钟后运行") time.sleep(sleeptime) daka(password.login_username, password.login_password) elif now_hour > "08": rest = 8 - int(now_hour) + 24 sleeptime = (rest - 1) * 3600 + (60 - int(now_min)) * 60 print("启动时北京时间为:" + time.strftime("%H:%M", time.localtime()), "\t 打卡程序将在", rest - 1, "小时", int((sleeptime - (rest - 1) * 3600) / 60), "分钟后运行") time.sleep(sleeptime) daka(password.login_username, password.login_password) elif now_hour == "08": print("启动时北京时间为:" + time.strftime("%H:%M", time.localtime()), "\t 打卡程序将在每天 8 点运行!") # 以下为定时任务 daka(password.login_username, password.login_password) time.sleep(86400 - int(now_min) * 60)服务器是
Ubuntu的话,持续运行python程序方法:-
后台启动python程序
nohup python -u main.py > main.out 2>&1 & -
打印输出
tail -f main.out退出
Ctrl+C -
停止python程序
-
查看
pidps -ef | grep python -
杀死
pidkill -9 xxx
-
-
以上只是提供一个思路,并没有其他想法。