起因为实战中遇到的一个站点,请求参数使用js前端加密,即使加密过程很简单 但仍希望有一个中间hook能直接看到明文,如果能接入sqlmap直接测试(免去写tamper)的过程就更好了,于是有了本文

*均已脱敏,所以可能看起来比较简单?whatever

*060923(金):增加部分内容

*070423(火):过于铸币,今天才想起来要push

前置

拾起远古js逆向技巧,f12大法伺候之下理清加密逻辑,从encrypt.js中抠出可以单独执行的加密函数jscode以便被execjs调用

把加密函数替换为空,其余不变另存一份encrypt2.js

yakit

我们希望能在burpsuite中直接看到明文来方便调试,让我们用yakit来做到这一点:

  1. 准备jscode
  2. yakit脚本编写:劫持encrypt.js响应,让其返回的encrypt函数为明文(encrypt2.js);劫持/xyz.action且带data的POST请求,将body明文参数的name和value分别用jscode做加密处理
  3. 配置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 error
  • http_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,对每一个接口、参数如何设置都写的很详细,五星好评

image-20230609174248050

实测mimt可以为xray做上游代理进行正常测试,但是会比直接挂xray速度会慢一些 始终有pending的部分(但谁让mimt操作简单呢,我单方面宣布与这一缺点和解!

实例

日常测站遇到的实例,参数被写死的key进行AES加密,返回内容也可用固定的key解密,直接抠出来替换(加密过程非常非常简单)

image-20230609175026087

这里遇到了第一个坑点:jsencrypt库是针对前端环境的,支持一种很奇怪的AES Public Key格式(126位),无法b64解码 也不含-----PUBLIC这种标配的头,但是可以被正常使用,然而nodejs-jsencrypt库却不支持这种格式!!!!!所以需要单独为jsencrypt补上运行环境(见最上两行代码)

之后就是把网页加载的 含有加密部分的代码换为空,让其返回明文,再编写mimt脚本

image-20230609175636518

第二个小坑点是mimt修改query参数竟然会自动进行urlencode……我还改了半天,结果删掉quote()就好了(是我自作多情了)

然后就可以用burp 愉快的测站啦qwq