完成该文章主要是通过看到一个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对加密的请求体进行解密。

参考链接

JS渗透逆向| V_jstools、Jsrpc、Autodecoder三者强强联动自动加解密

保姆级教程--前端加密的对抗(附带靶场)

JSRPC+Yakit轻松实现前端加解密