分析前端加密的意义?

分析前端加密的意义?

正常来说,我们在做测试的时候,一般都是明文数据,并且服务端不会对传入的参数进行检测。这个时候的测试就是一番风顺。

如下图,请求参数会带一个sign=xx,后端会对这个sign进行校验,此处的校验规则是:将请求参数进行拆分,并且按照ascii顺序排序之后进行md5(query + “字符常量”)的加密方式进行校验

 

遇到这些复杂的情况时,我们一般很难用burp继续去测试,因为需要对参数重新进行加密算法的操作,人工操作的话肯定是不现实的。

基本操作

找断点——通过搜索敏感关键词

 

通过请求参数发现关键词sign=

通过devtools进行sources的搜索

 

找到sign加密的地方并且打断点,再次触发请求可以进行debug

 

找断点——通过网络请求的调用栈

tips:有时候关键词搜索出来的结果过于多,可以通过此方式查找会更精确

 

找一个看起来更容易定位的函数,这里的函数其实可以随便找,因为在调用栈中,可以回溯/跟进每个函数。

 

断点发现有一个关键词参数encryptedData

 

跟踪加密的方法是s=Object(ht.b)(o,_dyn$.t(622)),可以确认加密函数是s=Object(ht.b), 所以可以在上图3245行进行断点跟踪,找到加密方法

 

mitmdump的基本使用(addons编写)

1.简单的demo

class AutoDecoderClass(object):

    def request(self, flow: http.HTTPFlow):
        pass

    def response(self, flow: http.HTTPFlow):
        pass

addons = [
    AutoDecoderClass()
]


给mitmdump运行, 加了一些参数,仅供参考

设置burp代理

如果勾选了Do DNS lookups over SOCKs Proxy, 脚本能获取的url信息是包含域名的,如果不勾选的话,获取到的url信息是IP的,因为经过了一层解析。

手写加密算法

如下图所示的加密校验(此处我修改了3c为2c,为了是将报错作为参考系)

可以看出sign=xxx是整个数据包发送到后端之后提供给后端校验的数据

通过debug查看加密算法

signData为请求GET请求参数ethod=edit&roleid=5985&operatorid=13223232324&_=1675850145395

getQuery方法(这部分不用细看,想手动还原算法分也行,不过此处的getQuery运行一下就可以得到结果从而可以判断出算法是啥样的)

function getQuery(_0x25e05e) {
    var _0x59335d = {
        'tMUuu': function(_0x5c4842, _0x4a44ef) {
            return _0x5c4842(_0x4a44ef);
        },
        'FukXq': function(_0x68f762, _0x5c9bb) {
            return _0x68f762(_0x5c9bb);
        },
        'IAwqi': function(_0x4c0bd0, _0x4ae967, _0x16eeab) {
            return _0x4c0bd0(_0x4ae967, _0x16eeab);
        },
        'RYViz': _0xfa96('‮b9', 'aosW')
    };
    var _0x4c50e2 = [];
    var _0x8ddc6f = _0x25e05e['split']('&');
    for (var _0xe55c87 = 0x0; _0xe55c87 < _0x8ddc6f['length']; _0xe55c87++) {
        if (_0x8ddc6f[_0xe55c87]['split']('=')[0x0]) {
            _0x4c50e2[_0xfa96('‮ba', 'b#9z')]({
                'name': _0x8ddc6f[_0xe55c87]['split']('=')[0x0],
                'value': _0x59335d[_0xfa96('‮bb', 'SKqK')](decodeURIComponentSafely, _0x59335d['FukXq'](decodeURIComponentSafely, _0x59335d[_0xfa96('‮bc', '4q@0')](getCaption, _0x8ddc6f[_0xe55c87], _0x8ddc6f[_0xe55c87][_0xfa96('‮bd', 'oc$h')]('=')[0x1])))
            });
        }
    }
    return _0x4c50e2[_0xfa96('‮be', '%866')](_0x59335d['FukXq'](createComprisonFunction, _0x59335d['RYViz']));
}


从运行结果看得出,是将所有的参数分隔之后将key和value都取出来以{name: X, value: Y}的方式存入数组并返回,同时也可以看出此处的排序规则为ASCII从小到大排序

getSign签名算法, 和上面的函数一样,可以手动还原 但是可能没啥必要。还原算法的时候可以配合debug的变量作用域来编写代码。比如_0xfa96(‘‫13d’, ‘3nwA’)其实就是一个length图片

getSign签名算法, 和上面的函数一样,可以手动还原 但是可能没啥必要。还原算法的时候可以配合debug的变量作用域来编写代码。比如_0xfa96(‘‫13d’, ‘3nwA’)其实就是一个length


function getSign(_0x2c8a1a) {
    var _0x388511 = {
        'DWutz': function(_0x1bf78a, _0x23f0a5) {
            return _0x1bf78a + _0x23f0a5;
        },
        'ldLIf': function(_0x41a705, _0x25b696) {
            return _0x41a705 - _0x25b696;
        },
        'pwfjR': _0xfa96('‮13c', 'Yb@s')
    };
    var _0x2f1bba = '';
    for (var _0x4d9213 = 0x0; _0x4d9213 < _0x2c8a1a[_0xfa96('‫13d', '3nwA')]; _0x4d9213++) {
        if (_0x2c8a1a[_0x4d9213] && _0x2c8a1a[_0x4d9213][_0xfa96('‮13e', 'eoTh')]) {
            _0x2f1bba += _0x388511[_0xfa96('‫13f', 'eoTh')](_0x2c8a1a[_0x4d9213][_0xfa96('‫140', 'Q5pM')] + '=', _0x2c8a1a[_0x4d9213][_0xfa96('‮141', 'hlCR')]);
            if (_0x388511['ldLIf'](_0x2c8a1a['length'], 0x1) > _0x4d9213) {
                _0x2f1bba += '&';
            }
        }
    }
    console[_0xfa96('‮142', 'o9yX')](_0x2f1bba);
    return md5(_0x388511[_0xfa96('‮143', 'SKqK')](_0x2f1bba, _0x388511['pwfjR']));
}


跟踪进入getSign查看md5加密的值

其中,_0x2f1bba是重新拼接之后的值, _0x388511[‘pwfjR’])为一个常量salteBrkhGPrugSZqXEwB6YnX7m49VIQYObJ。

最后组合成为 md5(排序参数组合+salt)

梳理加密流程 获取参数 –> 排序参数 -> 组合参数 -> 末尾拼接salt -> md5 -> 拼接sign= -> 发送

mitmproxy脚本实现

class AutoDecode4Finance(AutoDecoderClass):

    # 排序字符串 重组
    def new_string(self, params):
        params_sorted = sorted(params.split("&"))
        for i, v in enumerate(params_sorted):
            # 每个参数url解码一次
            params_sorted[i] = urllib.parse.unquote(v)
        return "&".join(params_sorted)

    def request(self, flow: http.HTTPFlow):
        # 跳过drw的url, 已经带sign=的不在计算
        if flow.request.url.endswith(".dwr") or '&sign=' in flow.request.url:
            return

        if flow.request.method.lower() == "get":
            # 获取get链接的参数
            query = urllib.parse.urlparse(flow.request.url).query
        else:
            # 获取 post 链接的参数
            query = flow.request.content.decode("utf-8")

        new_string = self.new_string(query)
        new_string += "eBrkhGPrugSZqXEwB6YnX7m49VIQYObJ"
        flow.request.query['sign'] = md5hash(new_string)


效果

去掉sign参数可以正常请求,因为mitmdump已经自动完成参数的拼接了

最后

本文是举了一个比较简单的算法例子,还有其他的如des、aes、sm4等算法都可以用此办法进行加密中转。

但是如果遇到复杂的算法,比如自带了一些自己写的算法,如果在代码篇幅不多的情况下,其实手动还原会好点,如果引用了数不清的莫名其妙的东西,可以采用其他方法进行加密算法的还原或者是调用。比如python的execjs来局部调用一些js的算法(不想还原js算法的情况下)。

原创文章,作者:guozi,如若转载,请注明出处:https://www.sudun.com/ask/81924.html

(0)
guozi's avatarguozi
上一篇 2024年5月31日 下午2:12
下一篇 2024年5月31日 下午2:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注