**声明:**该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,禁止私自转载,如需转载,请联系作者!!!!
请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与文章作者和本公众号无关!!!!!
前言
当进行渗透测试或安全评估时,JavaScript(JS)成为我们特别关注的领域之一。在前端源代码中,我们可能会找到接口、敏感信息以及一些逻辑处理,比如参数加解密等。为了防止攻击者拦截数据包篡改参数,使服务端执行恶意指令,开发者通常会在前端对输入参数进行加密,这是一种常见的防御手段。这导致我们抓取到的数据包中传入的参数和返回的包内容都是加密的。我们需要对加密方式进行逆向分析。如果无法还原出加密和解密方式,渗透测试可能会受到阻碍。逆向分析有多种方法,下面将介绍一种常用的,即JSRPC在JS逆向中的应用。
JSRPC工作原理
JSRPC简单来说就是远程调用JavaScript函数的方法。它的原理是在客户端(也就是浏览器)注入JSRPC环境,使客户端与JSRPC服务器建立WebSocket连接,保持通信。然后在客户端注册需要用到的加解密函数的demo,这样当JSRPC服务器发送信息给客户端时,客户端接收到并执行相应的方法,然后将结果发送回服务器。服务器接收到结果并将其显示出来。
案例
先抓个包看看是哪个接口
在接口处打个断点,可以看到email首先通过:SG_sm4encrypt(this.email, 'N200y834yEhD5gX5')加密
继续跟进,如下图,大致可以看懂完整的加密函数流程,但是一些函数的调用需要继续跟进,因此先在关键三处打上断点
a:三十多位随机值
o:g.a.sign(a + s() (e.data) + n)
n:时间戳
email:SG_sm4encrypt("xxx@x.com","N200y834yEhD5gX5")
s()(e.data):{"email":SG_sm4encrypt("xxx@x.com","N200y834yEhD5gX5"),"simpleCaptcha":"YNS2a"}
param:g.a.globalEncrypt(a + s() (e.data) + o)
t:时间戳,0
Requesttime:g.a.globalEncrypt(t)
这些可以通过控制台进行调试出来
梳理大致流程后,只需跟进g.a.globalEncrypt和g.a.sign,可以通过多种方式回溯函数;
第一种:如上图打断点
第二种:在控制台调试出相应函数全局搜索
全局搜索
很明显,还有一个函数不是很明确,继续打个断点,调试出来即可this.sg2()
至此函数所用到的加密参数都知道了,a是三十多位的随机值可以写死,s() (e.data) 里面带有邮箱和图形验证码,o的加密公式也知道了,n是时间戳,t是时间戳,其他:
o的值就是:SG_sm3encrypt(a + s() (e.data) + n)
param的值就是:SG_sm4encrypt(a + s() (e.data) + o,'N200y834yEhD5gX5')
Requesttime的值就是:SG_sm4encrypt(t,'N200y834yEhD5gX5')
整个流程已经基本完成,接下来只需编写还原加密函数的整体逻辑。但这可能会很费时,因此有了第二种方式:利用JSRPC远程调用JavaScript函数。这样做的好处是我无需编写加解密逻辑,只需了解参数值是如何生成的,然后调用目标网站的函数来实现加解密功能即可。
JSRPC案例
首先在Github下载JSRPC,下载地址:https://github.com/jxhczhl/JsRpc,编译成exe后,再本地命令行执行exe文件,目的是启动一个JSRPC服务,然后监听12080端口,在客户端控制台注入JSRPC环境。
function Hlclient(wsURL) {
this.wsURL = wsURL;
this.handlers = {
_execjs: function (resolve, param) {
var res = eval(param)
if (!res) {
resolve("没有返回值")
} else {
resolve(res)
}
}
};
this.socket = undefined;
if (!wsURL) {
throw new Error('wsURL can not be empty!!')
}
this.connect()
}
Hlclient.prototype.connect = function () {
console.log('begin of connect to wsURL: ' + this.wsURL);
var _this = this;
try {
this.socket = new WebSocket(this.wsURL);
this.socket.onmessage = function (e) {
_this.handlerRequest(e.data)
}
} catch (e) {
console.log("connection failed,reconnect after 10s");
setTimeout(function () {
_this.connect()
}, 10000)
}
this.socket.onclose = function () {
console.log('rpc已关闭');
setTimeout(function () {
_this.connect()
}, 10000)
}
this.socket.addEventListener('open', (event) => {
console.log("rpc连接成功");
});
this.socket.addEventListener('error', (event) => {
console.error('rpc连接出错,请检查是否打开服务端:', event.error);
});
};
Hlclient.prototype.send = function (msg) {
this.socket.send(msg)
}
Hlclient.prototype.regAction = function (func_name, func) {
if (typeof func_name !== 'string') {
throw new Error("an func_name must be string");
}
if (typeof func !== 'function') {
throw new Error("must be function");
}
console.log("register func_name: " + func_name);
this.handlers[func_name] = func;
return true
}
//收到消息后这里处理,
Hlclient.prototype.handlerRequest = function (requestJson) {
var _this = this;
try {
var result = JSON.parse(requestJson)
} catch (error) {
console.log("catch error", requestJson);
result = transjson(requestJson)
}
//console.log(result)
if (!result['action']) {
this.sendResult('', 'need request param {action}');
return
}
var action = result["action"]
var theHandler = this.handlers[action];
if (!theHandler) {
this.sendResult(action, 'action not found');
return
}
try {
if (!result["param"]) {
theHandler(function (response) {
_this.sendResult(action, response);
})
return
}
var param = result["param"]
try {
param = JSON.parse(param)
} catch (e) {}
theHandler(function (response) {
_this.sendResult(action, response);
}, param)
} catch (e) {
console.log("error: " + e);
_this.sendResult(action, e);
}
}
Hlclient.prototype.sendResult = function (action, e) {
if (typeof e === 'object' && e !== null) {
try {
e = JSON.stringify(e)
} catch (v) {
console.log(v)//不是json无需操作
}
}
this.send(action + atob("aGxeX14") + e);
}
function transjson(formdata) {
var regex = /"action":(?<actionName>.*?),/g
var actionName = regex.exec(formdata).groups.actionName
stringfystring = formdata.match(/{..data..:.*..\w+..:\s...*?..}/g).pop()
stringfystring = stringfystring.replace(/\\"/g, '"')
paramstring = JSON.parse(stringfystring)
tens = `{"action":` + actionName + `,"param":{}}`
tjson = JSON.parse(tens)
tjson.param = paramstring
return tjson
}
然后再控制台建立链接JSRPC服务器的通信
// 注入环境后连接通信
var demo = new Hlclient("ws://127.0.0.1:12080/ws?group=zzz");
// 可选
//var demo = new Hlclient("ws://127.0.0.1:12080/ws?group=zzz&clientId=hliang/"+new Date().getTime())
后续就是需要注册需要调用哪个函数的方法,JSRPC作者也是提供了demo,过一遍就行,这里不做解释,这里我们只需要注册相应调用函数的方法就可以了
demo.regAction("SG_sm3encrypt", function (resolve,param) {
//这样添加了一个param参数,http接口带上它,这里就能获得
var o = SG_sm3encrypt(param)
resolve(o);
})
demo.regAction("SG_sm4encrypt", function (resolve,param) {
//这里还是param参数 param里面的key 是先这里写,但到时候传接口就必须对应的上
res=SG_sm4encrypt(param["value"],param["key"])
resolve(res);
})
然后编写脚本识别图形验证码获取图形验证码的值,带入函数中即可,核心代码逻辑就这些
那么解密过程也是同理,后续就是与burp联动这些了,有时间再分享。
关注我们
公众号回复“2024Hw应急工具”获取Hw应急工具集
公众号回复“简历模版”获取****Hw简历模版
****公众号回复“2024面经大全”****获取全套面经以及回答思路