针对请求接口加密/响应需要解密的情况,在使用Burp测试时无法对数据进行操作,需要确认传输都为明文我们才可以进行常规测试。这块有点JS基础和Python基础基本都可以通杀。(JsRpc也是FridaRpc的前置,主要都是用到了mitmproxy,技术这东西一通百通,大家卷起来,呆哥躺平。)
▲ JSRPC请求处理逻辑
项目地址:https://github.com/mitmproxy/mitmproxy
安装证书:在连接mitmproxy之后,手机或设备需要设置代理,输入http://mitm.it/安装证书
代理配置:使用--mode upstream:来设Suite,加上--ssl-insecure忽略 SSL 证书验证:
mitmdump-p6666-s main.py --mode upstream:http://127.0.0.1:6662 --ssl-insecure
这里upstream就是burp的端口。
mitm request属性:https://docs.mitmproxy.org/stable/api/mitmproxy/http.html#Request
def__init__(
self,
host:str, // 主机
port:int, // 端口
method:bytes, // HTTP请求方法,例如“GET”。
scheme:bytes, // HTTP请求方案,应为“http”或“https”。
authority:bytes, // HTTP 请求权限
path:bytes, // 路径
http_version:bytes,
headers:Headers | tuple[tuple[bytes, bytes], ...],
content:bytes | None,
trailers:Headers | tuple[tuple[bytes, bytes], ...] | None,
timestamp_start:float,
timestamp_end:float | None):
mitm response属性:https://docs.mitmproxy.org/stable/api/mitmproxy/http.html#Response
def __init__(
self,
bytes, :
int, :
bytes, :
Headers | tuple[tuple[bytes, bytes], ...], :
bytes | None, :
None | Headers | tuple[tuple[bytes, bytes], ...], :
float, :
float | None, :
)
项目地址:https://github.com/jxhczhl/JsRpc
用法说明:双击JSRPC.exe启动监听服务,然后在命令行执行以下脚本创建Hlclient对象,然后再通过对其实例化进行连接服务,连接上之后可以正常使用。
这里只做基础的加解密函数定位,研究型文章不做过多的分析,JS逆向基础差的可以爬楼找最上面的几篇文章,只做JsRpc演示防止被恶意利用,这个做过金融项目的大概率遇到过。
发现请求被加密了,开始加入XHR调试拦截:
然后一层层找调用栈。
然后往下调试找加密点。
▲ 加密函数
经过一点点调试发现由该位置对请求request进行一次加密。
▲ l就是传入的json对象
然后我们找response解密函数,一步步跟栈调用也是同理,M$e(e.data)这个解密函数就被找到了。
▲ 解密函数位置
> 我们已知加密函数z9(),解密函数M$e(e.data)
现在开始RPC调用,第一步启动服务:
▲ 双击下载下来的JsRPC.exe,监听在12080端口
客户端在浏览器控制台注入以下代码,创建类定义:https://github.com/jxhczhl/JsRpc/blob/main/resouces/JsEnv_Dev.js
functionHlclient(wsURL){
this.wsURL = wsURL;
this.handlers = {
_execjs:function(resolve, param){
varres =eval(param)
if(!res) {
resolve("没有返回值")
}else{
resolve(res)
}
}
};
this.socket =undefined;
if(!wsURL) {
thrownewError('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 =newWebSocket(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(typeoffunc_name !=='string') {
thrownewError("an func_name must be string");
}
if(typeoffunc !=='function') {
thrownewError("must be function");
}
console.log("register func_name: "+ func_name);
this.handlers[func_name] = func;
returntrue
}
//收到消息后这里处理,
Hlclient.prototype.handlerRequest =function(requestJson){
var_this =this;
try{
varresult =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
}
varaction = result["action"]
vartheHandler =this.handlers[action];
if(!theHandler) {
this.sendResult(action,'action not found');
return
}
try{
if(!result["param"]) {
theHandler(function(response){
_this.sendResult(action, response);
})
return
}
varparam = 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(typeofe ==='object'&& e !==null) {
try{
e =JSON.stringify(e)
}catch(v) {
console.log(v)//不是json无需操作
}
}
this.send(action + atob("aGxeX14") + e);
}
functiontransjson(formdata){
varregex =/"action":(?<actionName>.*?),/g
varactionName = 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
returntjson
}
不过为了方便就小小修改两个地方就可以不用重复输入了。
把函数用let定义,然后直接点击右下角的三角形运行即可。(此处在页面加载后就可以开始了)
▲ 在Sources->Snippets下面新建一个脚本,然后把它复制进去
然后启动RPC链接:
var demo = new Hlclient("ws://127.0.0.1:12080/ws?group=zzz");
然后回到原始代码中,我们可以看到这里断点还在,我们将其用window.hook=z9代出来。
▲ 代理加密函数
我们甚至可以直接看出来他的加密方式,然后Hook完成后将断点放开。
这样我们就可以看到这个函数被我们代理到全局window中了,然后我们进行调用。
demo.regAction("encode", function (resolve,param) {
//这样添加了一个param参数,http接口带上它,这里就能获得
var ret = window.hook(JSON.stringify(param))
resolve(ret);
})
注入加密方法定义。
▲ 注册好了
▲ 正常显示加密
然后写python调用一下。
curl 'http://127.0.0.1:12080/go?group=zzz&action=encode¶m='
curl -X POST -H 'Content-Type: application/x-www-form-urlencoded' -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36' -d 'action=encode&group=zzz¶m=' 'http://127.0.0.1:12080/go'
既可以用GET也可以用POST,考虑到传入参数可能多样性,所以我这里会用POST来做代码层面处理,GET用于测试。
然后访问这个网页:https://curlconverter.com/http/
import requests
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
data = {
'action': 'encode',
'group': 'zzz',
'param': '',
}
response = requests.post('http://127.0.0.1:12080/go', headers=headers, data=data)
发现调用成功了,然后修改脚本为通用版本。
import requests
def getRpc(param,method):
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
data = {
'action': method,
'group': 'zzz',
'param': param
}
resJson = requests.post('http://127.0.0.1:12080/go', headers=headers, data=data).json()
return resJson["data"]
def encode(param):
return getRpc(param, 'encode')
def decode(param):
return getRpc(param, 'decode')
print(encode('{"username":"admin","password":"123456","code":"2","uuid":"xxxxx"}'))
开始整合mitm进行改写。
import requests
import mitmproxy
from mitmproxy import http,ctx
from mitmproxy import flowfilter
def encode(param):
return getRpc(param, 'encode')
def decode(param):
return getRpc(param, 'decode')
def getRpc(param,method):
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
data = {
'action': method,
'group': 'zzz',
'param': param
}
resJson = requests.post('http://127.0.0.1:12080/go', headers=headers, data=data).json()
return resJson["data"]
# mitm核心处理逻辑
class Interceptor:
def __init__(self):
pass
def request(self, flow: mitmproxy.http.HTTPFlow):
request = flow.request
# 直接将请求体进行一次加密处理
request.content = encode(request.content).encode('utf-8')
# 日志模块
ctx.log.info(request.content)
def response(self, flow: mitmproxy.http.HTTPFlow):
pass
addons = [
Interceptor()
]
加密解决了,现在对解密函数进行处理。
▲ 加解密是一套
代理到window.decode里面了。
demo.regAction("decode", function (resolve,param) {
//这样添加了一个param参数,http接口带上它,这里就能获得
var ret = window.decode(param)
resolve(ret);
})
import requests
import mitmproxy
from mitmproxy import http,ctx
from mitmproxy import flowfilter
info = ctx.log.info
# 加密方法
def encode(param):
return getRpc(param, 'encode')
# 解密方法
def decode(param):
return getRpc(param, 'decode')
# 获取RPC调用方法
def getRpc(param,method):
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
data = {
'action': method,
'group': 'zzz',
'param': param
}
resJson = requests.post('http://127.0.0.1:12080/go', headers=headers, data=data).json()
return resJson["data"]
class Hook:
def __init__(self):
pass
# 处理request请求
def request(self, flow: mitmproxy.http.HTTPFlow):
request = flow.request
info("=======Request修改前:" + str(request.content) + "=======")
request.content = encode(request.content).encode('utf-8')
info("=======Request修改后:" + str(request.content) + "=======")
# 处理response请求
def response(self, flow: mitmproxy.http.HTTPFlow):
response = flow.response
info("=======Response修改前:" + response.text + "=======")
response.text = decode(response.text)
info("=======Response修改后:" + response.text + "=======")
addons = [
Hook()
]
这里请求/响应都处理完成了。
如果请求可以解密的话,先前置一个mitm将加密请求解密之后发送给burp这样就可以直接被动扫接受时自动处理。
▲ 类似这样
把response的处理pass掉,如果请求有加密,并且被我们解密了,这个作为直接和客户端交互的第一步所以也可以在这里配置响应加密返回给客户端正常显示,这样到burp的数据都是明文的。
处理逻辑就是:客户端->beforeRpc-mitm(请求解密) ->burpsuit(明文请求)->totalRpc-mitm(请求加密/响应解密)->burpsuit(明文响应)->beforeRpc-mitm(响应加密)
前置mitm启动方式:
mitmdump -p 6666 -s beforeRpc.py --mode upstream:http://127.0.0.1:65530 --ssl-insecure
命令输入这个即可,意思是启动666端口监听,但是上游是burp的端口65530,然后burp的上游设置7777的加解密RPC。
写了个通用模板方便使用:
import json
import requests
import mitmproxy
from mitmproxy import http,ctx
from mitmproxy import flowfilter
info = ctx.log.info
# 工具方法
def is_valid_json(text):
try:
json.loads(text)
return True
except json.JSONDecodeError:
return False
# sign
def rpcSign(param):
return getRpc(param, 'sign')
# 加密方法
def rpcEncode(param):
return getRpc(param, 'encode')
# 解密方法
def rpcDecode(param):
return getRpc(param, 'decode')
# 获取RPC调用方法
def getRpc(param,method):
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
data = {
'action': method,
'group': 'zzz',
'param': param
}
resJson = requests.post('http://127.0.0.1:12080/go', headers=headers, data=data).json()
return resJson["data"]
# 处理Param模式
def changeRequestParam(reqObj):
reqObj.query.set_all("要修改的key", ["要修改的任意类型,又可以改为encode(xxx)"])
info(reqObj.query)
# 处理Form模式
def changeRequestForm(reqObj):
reqObj.urlencoded_form["要修改的key"] = "要修改的任意类型,又可以改为encode(xxx)"
info(reqObj.urlencoded_form)
# 处理JSON模式
def changeRequestJson(reqObj):
if reqObj.headers["Content-Type"].find("application/json") != -1:
if len(str(reqObj.content)) != 0:
if is_valid_json(reqObj.content):
# 说明传入的是JSON
jsonObj = json.loads(reqObj.content)
jsonObj["要修改的key"] = "要修改的任意类型,又可以改为encode(xxx)"
reqObj.content = json.dumps(jsonObj).encode('utf-8')
info(reqObj.content)
# 处理整个请求体都为加密时
def changeRquestBody(reqObj):
reqObj.content = rpcEncode(reqObj.content).encode('utf-8')
info(reqObj.content)
# 处理请求头加密时候
def changeRequestHeaders(reqObj):
reqObj.headers["Sign1"] = "要修改的任意类型,又可以改为sign(xxx)"
# 响应全加密时候
def changeResponseBody(reqObj):
reqObj.text = rpcDecode(reqObj.text)
info(reqObj.text)
# 响应为json且部分加密的时候
def changeResponseJson(reqObj):
if reqObj.headers["Content-Type"].find("application/json") != -1:
if len(str(reqObj.content)) != 0:
if is_valid_json(reqObj.content):
jsonObj = json.loads(reqObj.content)
jsonObj["要修改的key"] = "要修改的任意类型,又可以改为encode(xxx)"
reqObj.content = json.dumps(jsonObj).encode('utf-8')
info(reqObj.content)
# 请求处理最终控制器(只需要改这里启用)
def totalRequest(reqObj):
# changeRquestBody(reqObj)
pass
# 响应处理最终控制器(只需要改这里启用)
def totalResponse(reqObj):
# changeResponseJson(reqObj)
pass
class Hook:
def __init__(self):
pass
# 处理request请求
def request(self, flow: mitmproxy.http.HTTPFlow):
request = flow.request
totalRequest(request)
# 处理response请求
def response(self, flow: mitmproxy.http.HTTPFlow):
response = flow.response
totalResponse(response)
addons = [
Hook()
]
只需要修改要启用的操作方法,并且totalResponse和totalRequest里面将其注册进去即可,小白都能用,读一下就懂了。
这段代码如果不想复制我就把他放进网盘里:【阿呆攻防】软件分享->脚本->JSRPC
原文始发于微信公众号(阿呆攻防):JSRPC|看不懂加密方式?稳了!以金融为例且提供最终样板脚本
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论