起因为实战中遇到的一个站点,请求参数使用js前端加密,即使加密过程很简单 但仍希望有一个中间hook能直接看到明文,如果能接入sqlmap直接测试(免去写tamper)的过程就更好了,于是有了本文
*均已脱敏,所以可能看起来比较简单?whatever
*060923(金):增加部分内容
*070423(火):过于铸币,今天才想起来要push
前置
拾起远古js逆向技巧,f12大法伺候之下理清加密逻辑,从encrypt.js
中抠出可以单独执行的加密函数jscode
以便被execjs调用
把加密函数替换为空,其余不变另存一份encrypt2.js
yakit
我们希望能在burpsuite中直接看到明文来方便调试,让我们用yakit来做到这一点:
- 准备jscode
- yakit脚本编写:劫持encrypt.js响应,让其返回的encrypt函数为明文(encrypt2.js);劫持/xyz.action且带data的POST请求,将body明文参数的name和value分别用jscode做加密处理
- 配置burpsuite上游代理为yakit,yakit下游代理为最外层代理(可选)
yak脚本示例:
func encrypt(name){
jscode = `...function encrypt(){}...`
encoded, _ = js.CallFunctionFromCode(jscode,"encrypt",name)
return encoded, _
}
hijackHTTPRequest = func(isHttps, url, req, forward /*func(modifiedRequest []byte)*/, drop /*func()*/) {
urls := ["xyz", "qwe"]
if str.Contains(string(url), ".action"){
sUrl := str.Split(str.Split(string(url),".action")[0],"/")[-1]
for url in urls{
if str.Contains(sUrl,url){
freq = fuzz.HTTPRequest(req)~
tmp := ""
for param in freq.GetPostQueryKeys(){
value,_ := codec.UnescapeQueryUrl(freq.GetPostQueryValue(param))
param,_ := codec.UnescapeQueryUrl(param)
value0,_ = encrypt(value)
param0,_ := encrypt(param)
trmp := codec.EscapeQueryUrl(string(param0)) +"="+ codec.EscapeQueryUrl(str.ReplaceAll(string(value0),"+"," "))
tmp = tmp+"&"+trmp
}
modifiedBytes := freq.FuzzPostRaw(str.TrimLeft(tmp,"&")).FirstHTTPRequestBytes()
forward(modifiedBytes)
}
}
}
}
hijackHTTPResponse = func(isHttps, url, rsp, forward, drop) {
a1 = b`...encrypt2.js...`
if str.Contains(string(url), "encrypt.js") {
modified = poc.FixHTTPResponse(a1)
forward(modified)
}
}
经过这样的劫持,请求包会呈现这样的流向:
- 原本
浏览器 POST /xyz.action 加密参数(由encrypt.js加密)
-> burpsuite 拦截加密参数和请求
-> 发送加密请求
-> burpsuite得到响应 浏览器得到响应
- 修改后
浏览器 POST /xyz.action 明文参数(由修改后的encrypt2.js返回明文)
-> burpsuite 拦截明文参数和请求
-> 发送给上游代理yakit
-> yakit 拦截明文参数和请求 做加密处理
-> 发送加密请求
-> yakit得到响应 burpsuite得到响应 浏览器得到响应
这样burp中可以看到明文请求和正常响应,方便我们爆破和其他测试(比如接入burp, xray等等)
mitmproxy
但话说回来,yakit本身是一个比较庞大的类burp软件,编写脚本还必须使用yaklang,我更倾向于使用mitmproxy来做相同的事情,毕竟用python写是再轻松不过了~
mitmproxy为每一种连接方式(http, socks….)提供了5个生命周期(修改阶段),体现在代码里就是我们可声明的函数
requestheaders
:仅读取headers,此时body为空request
:读取到request全文;注意如果streaming传输开启,劫持在stream之后发生responseheaders
:仅读headers,body为空response
:读取response全文;注意如果streaming传输开启,劫持在stream之后发生error
:http errorhttp_connect
http_connect_upstream
简单举例:
import execjs
def encrypt(var):
with open('encrypt.js', 'r', encoding='utf-8') as f:
ctx = execjs.compile(f.read())
encoded_var = ctx.call('encode', var)
return var
class Modify:
def request(self, flow):
if flow.request.url.startswith('...'):
if flow.request.urlencoded_form: # 此处的urlencoded_form是[(name, value), (...)]
data = []
for i in flow.request.urlencoded_form:
data.append((encrypt(i), encrypt(flow.request.urlencoded_form[i])))
flow.request.urlencoded_form = data
addons = [
Modify()
]
使用:
pip install mitmporxy
# $env:all_proxy = "socks5://127.0.0.1:6005" # 可选
mitmdump -p 8085 -s addon.py # 8085给burp做上游代理
然而实际编写符合要求的脚本时就陷入了困难——官方文档简直是****,让人不忍卒读……这里推荐这份文档:Mitmproxy-Document,对每一个接口、参数如何设置都写的很详细,五星好评
实测mimt可以为xray做上游代理进行正常测试,但是会比直接挂xray速度会慢一些 始终有pending的部分(但谁让mimt操作简单呢,我单方面宣布与这一缺点和解!
实例
日常测站遇到的实例,参数被写死的key进行AES加密,返回内容也可用固定的key解密,直接抠出来替换(加密过程非常非常简单)
这里遇到了第一个坑点:jsencrypt库是针对前端环境的,支持一种很奇怪的AES Public Key格式(126位),无法b64解码 也不含-----PUBLIC
这种标配的头,但是可以被正常使用,然而nodejs-jsencrypt库却不支持这种格式!!!!!所以需要单独为jsencrypt补上运行环境(见最上两行代码)
之后就是把网页加载的 含有加密部分的代码换为空,让其返回明文,再编写mimt脚本
第二个小坑点是mimt修改query参数竟然会自动进行urlencode……我还改了半天,结果删掉quote()
就好了(是我自作多情了)
然后就可以用burp 愉快的测站啦qwq