JSRPC(JavaScript Remote Procedure Call)是一种基于 JavaScript 的远程过程调用协议,用于实现客户端和服务器之间的通信和函数调用。它允许开发人员在客户端 JavaScript 代码中调用远程服务器上的函数,以便获取数据、执行操作或获取服务。
在实际渗透测试的场景中,会遇到很多前端加密、签名校验、返回包加密等等的场景,如果是自己去尝试获取加解密函数,然后自己构造环境去绕过,第一时间成本确实很高,如果js文件进行混淆那种,时间成本还是很高的,第二点对于我这种看看还行,自己本地运行就是纯折磨的来说,jsrpc算是不错的选择。
因为不需要知道完整流程是如何实现的,只需要找到函数然后调用即可,因为没有找到数据包也加密的场景尝试自动解密,本文就拿快手src的登录功能进行演示。
文章实现的逻辑流程图如下:
演示的站点用快手src,别的不说,快手src的礼物确实多(
直接搜索encrypt
或者password
都可以定位到加密的函数
因为不运行的状态下,浏览器可能不会加载这个js,导致你在console.log(le.encrypt("123",w(w({},0))));
的时候会提示le这个是未定义的,所以我们先设置断点,然后在调试模式下,设置为全局变量
设置完成后,尝试加密一个内容
这里用到的是sekiro
,jsrpc只是其中的一部分
https://github.com/yint-tech/sekiro-open
下载后,按照下面的方式运行
构建完成后,会生成文件夹sekiro-open-demo
,在bin文件夹中根据系统来选择运行.bat还是.sh即可
如果你不想自己构建,可以在下面地址里下载
https://oss.iinti.cn/sekiro/sekiro-demo
运行后的样子如下:
这里要说明的是,在网上很多文章文档的地址都不正确(毕竟时间长,难免地址变了),新的地址是https://sekiro.iinti.cn/sekiro-doc/
在文档中可以看到,官方提供了注入的js
但是在.js中,还需要我们自己单独注册一个接口
所以我们把前面的js复制进来后,在底下自己自定义接口就好了(注意:免费版的地址是business-demo)
function guid() {
function S4() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}
return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
}
//Modify content
group = "kuaishou";
//自定义组织名,一般以网站为单位
registerAction = "encrypt";
//自定义方法名,一般以函数为单位
var client = new SekiroClient("ws://127.0.0.1:5612/business-demo/register?group=" + group + "&clientId=" + guid());
client.registerAction(registerAction, function(request, resolve, reject) {
response = le.encrypt(request['text'], w(w({}, o)));
//接口返回内容 resolve(response);
resolve(response);
})
写完后,运行就ok了
这个时候我们访问接口,就能请求数据并加密了
在我们的环境中,我们需要调用这个jsrpc来加密后,将处理过的数据包发送到网站中,所以需要一个mitmproxy来加密参数,然后发送
首先安装运行库
pip3 install mitmproxy
接着创建服务器
import requests
import re
import json
from mitmproxy import ctxdef encrypt(data):
url="http://127.0.0.1:5612/business-demo/invoke?group=kuaishou&action=encrypt&text={}".format(data)
res=requests.get(url)
print(res.text)
res = json.loads(res.text)
return res['encryptText']
def request(flow):
# 获取数据包
body = flow.request.get_text()
data = {}
for pair in body.split("&"):
key, value = pair.split("=")
data[key] = value
# 获取 password
password = data.get("password", "")
phone = data.get("phone", "")
# 调用 encrypt 函数进行加密
encrypted_password = encrypt(password)
encrypted_phone = encrypt(phone)
# 修改请求的 body 数据为加密后的值
data["password"] = encrypted_password
data["phone"] = encrypted_phone
# 构造修改后的 body 数据
modified_body = "&".join([f"{key}={value}" for key, value in data.items()])
# 设置修改后的请求 body 数据
flow.request.set_text(modified_body)
ctx.log.warn("加密内容: "+str(flow.request.get_text()))
# 请求后的数据
def response(flow):
response = flow.response
print(response.text)
ctx.log.info(str(response.status_code))
注意我这里面的
data = {}
for pair in body.split("&"):
key, value = pair.split("=")
data[key] = value
正常来说,网上的例子都是json.loads(),但是我们的数据包并不是json格式的
所以只能自己获取内容后,重新生成这部分的内容,写完后保存,命令运行
mitmproxy -v -s <filename> -p <port>
mitmweb -v -s <filename> -p <port>
这两个都可以,web也会监听端口,但是web的话会多一个web的可视化界面
我们设置一个burpsuite的代理到mitmproxy中,然后尝试发送一个包
burpsuite数据包如下
我们在mitmweb中查看数据包,可以看到内容已经加密了