完成该文章主要是通过看到一个JS相关文章,然后自己的js比较薄弱,然后想着照着学一遍并进行实现。
测试网站
http://39.98.108.20:8085/#/login
测试利用相关工具
autoDecoder:https://github.com/f0ng/autoDecoder/(burp插件)
jsRpc:https://github.com/jxhczhl/JsRpc
v_jstools:https://github.com/cilame/v_jstools(浏览器插件)
正常登录查看请求包和返回包
发送到Repeter中进行重放发现400,应该是对timestamp、requestId、sign进行校验了。
js逆向
通过利用v_jstool浏览器插件定位到js的加解密函数和timestamp、requestId、sign生成方式,过程如下:
登录定位到数据执行位置。
点击js
链接进行跳转到指定位置
设置断点再次进行登录操作
步入l()函数查看原数据是怎么被处理的
步入l()函数发现是AES加密,使用pad.Pkcs7填充,密钥和偏移量都是1234567891234567。
使用网站进行验证是否正确
对请求体进行解密,发现是测试提交的原数据,至此加密方式已经完成。
在测试过程中发现请求处理过程是:
请求时浏览器前端提交原数据 --> 通过js对原数据进行加密 --> 服务端返回加密的响应 --> js解密加密的响应体-->进行逻辑判断然后输出
接下来找js解密请求体的方法,还是通过v_jstool来定位,然后通过一步步执行js代码来找到解密的方法。
定位到解密函数。
利用JSRPC调用函数
JsRpc具体可以参考:https://github.com/jxhczhl/JsRpc,也可以参考相关链接使用方法。
开启JsRpc服务
我们取消所有断点,然后在客户端注入js环境
在控制粘贴如下链接中的js代码,然后回车
https://github.com/jxhczhl/JsRpc/blob/main/resouces/JsEnv_Dev.js
然后连接我们开启的JsRpc服务端:
var demo = new Hlclient("ws://127.0.0.1:12080/ws?group=zzz");
这一步注意:部分浏览器不支持websocket连接,可以更换浏览器进行尝试,或者修改浏览器设置。
上述执行完成之后,就要去注册js方法。
注册加密函数
还是设置断点,找到执行相关函数的地方然后进行注册函数,如果我们执行进行注册的话会显示引用的函数未定义,因为部分函数作用于局部。
先注册加密函数,点击登录让js执行到第一次断点的位置,然后在控制台执行一下代码进行注册函数
// 时间戳
window.time = Date.parse;
// requestId
window.id = function() {
return p();
};
// v函数
window.v1 = function(param) {
return v(param);
};
// 签名
window.m = function(data) {
return a.a.MD5(data).toString();
};
// 加密
window.enc = function(data) {
return l(data);
};
注册函数并测试函数是否调用成功
然后进行传递函数名进行调用,继续将下面的js代码粘贴至控制台。
// 注册函数
demo.regAction("req", function(resolve, param) {
// 请求头
let timestamp = window.time(new Date());
let requestid = window.id();
let v_data = JSON.stringify(window.v1(param));
let sign = window.m(v_data + requestid + timestamp);
// 加密请求体
let encstr = window.enc(v_data);
let res = {
"timestamp": timestamp,
"requestid": requestid,
"encstr": encstr,
"sign": sign
};
resolve(res);
});
然后测试用接口调用一下注册的传递函数(测试时取消断点)
测试可以加密数据并获取timestamp、requestId、sgin的值。
注册解密函数
接下来设置解密的调用
让js执行到解密函数的位置,然后使用控制台执行注册函数
直接注册整个函数:
window.dec = function(data) {
var e = a.a.AES.decrypt(data, f, {
iv: h,
mode: a.a.mode.CBC,
padding: a.a.pad.Pkcs7
});
return e.toString(a.a.enc.Utf8)
}
传递函数名进行调用
//注册函数
demo.regAction("decrypt",function(resolve,param){
let decryptedData = window.dec(param);
resolve(decryptedData);
});
测试注册函数
调用接口对数据进行解密测试(测试时取消断点)
然后就是利用别人写好的Python脚本来调用JsRpc
Py脚本调用JsRpc接口
Python脚本:
import requests
import json
from flask import Flask, Response, request
import re
app = Flask(__name__)
url = "http://localhost:12080/go"
@app.route('/encode', methods=["POST"])
def encrypt():
body = request.form.get('dataBody') # 获取 post 参数
headers = request.form.get('dataHeaders') # 获取 post 参数
reqresp = request.form.get('requestorresponse') # 获取 post 参数
data = {
"group": "zzz",
"action": "req",
"param": body
}
if headers is not None: # 开启了请求头加密
# 使用正则表达式提取 timestamp、requestid 和 sign
timestamp_match = re.search(r'timestamp: (\d+)', headers)
requestid_match = re.search(r'requestId: ([\w-]+)', headers)
sign_match = re.search(r'sign: ([\w-]+)', headers)
# 提取匹配的值
timestamp_before = timestamp_match.group(1) if timestamp_match else None
requestid_before = requestid_match.group(1) if requestid_match else None
sign_before = sign_match.group(1) if sign_match else None
# 发送请求到目标 URL
res = requests.post(url, data=data)
encry_param = json.loads(res.text)['data']
print(encry_param)
# 将 encry_param 转换为字典
encry_param_dict = json.loads(encry_param)
encstr = encry_param_dict['encstr']
# 提取新的 timestamp、requestid 和 sign
timestamp_new = encry_param_dict['timestamp'] # 提取 timestamp
requestid_new = encry_param_dict['requestid'] # 提取 requestid
sign_new = encry_param_dict['sign']
# 确保 timestamp_new 是字符串
timestamp_new = str(timestamp_new)
# 替换 headers 中的旧值为新值
headers = headers.replace(sign_before, sign_new, 1)
headers = headers.replace(requestid_before, requestid_new, 1)
headers = headers.replace(timestamp_before, timestamp_new, 1)
return f"{headers}\r\n\r\n\r\n\r\n{encstr}" # 返回值为固定格式,不可更改 必需必需必需,共四个\r\n
# 否则,只返回 encstr
return encstr
@app.route('/decode', methods=["POST"])
def decrypt():
body = request.form.get('dataBody') # 获取 post 参数
headers = request.form.get('dataHeaders') # 获取 post 参数
reqresp = request.form.get('requestorresponse') # 获取 post 参数
data = {
"group": "zzz",
"action": "decrypt",
"param": body
}
res = requests.post(url, data=data)
body = json.loads(res.text)['data']
print(body)
if headers is not None: # 如果需要处理响应头
# 返回值为固定格式,不可更改 必需必需必需,共四个\r\n
return f"{headers}\r\n\r\n\r\n\r\n{body}"
# 否则,只返回 body
return body
if __name__ == '__main__':
app.debug = True # 设置调试模式,生产模式的时候要关掉debug
app.run(host="0.0.0.0", port="8888")
启动Python脚本
然后利用autoDecoder Burp插件进行调用Python脚本开启的接口
然后使用接口解密就可以自动解密了
插件设置
Proxy
Repeater(解密后的数据包替换原数据,然后再修改请求体即可利用auto进行正常请求)
Intruder
总结
整个过程,例如Repeater中进行重放,插件会调用加密接口,然后再调用JsRpc的rep接口来对数据进行加密并获取随机变的请求头,然后发送到服务端。服务端接收到请求后,返回了一个加密请求体,插件调用解密接口,然后再调用JsRpc的decrypt对加密的请求体进行解密。