起因为实战中遇到的一个站点,请求参数使用 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