原文链接:https://forum.butian.net/share/3019
起因:手头上的乱七八糟的事情处理基本接近尾声了,想着休息休息挖挖src吧,然后选中了几个资产,打开第一个,请求响应全加密,算了懒得搞,换!第二个,又是请求响应全加密,6,那我再换一个,又你*请求响应全加密,干
功能页面如下所示
获取验证码请求如下图所示,其实不光发送验证码功能,网站的每个请求都将我们实际的参数进行了加密,然后赋值给了data,紧接着还有sign做验证,同样的响应体res也进行了加密,这样对我们渗透测试来说非常的不便
0x01 data加密分析
在讲核心内容之前,我们首先要依次分析下data和sign的生成, 由于data字符串太过常见,sign相对生僻一些,我这里直接sign:
,搜索出来了5处,挨个点开看一下,最终发现下方代码,不用说它就是我们请求的时候赋值的代码,我这里先看data,可以看到将变量t赋值给了data,我们往上翻找变量t
如图所示,逻辑不难看出,参数e一开始赋值给了变量t,紧接着对变量t
进行了一个函数操作,将返回值重新赋值回了变量t,所以我大概率可以推断这个v就是加密函数
接下来我们在t = v(t)那块打上断点,然后重新发请求触发js,可以看到该站采用了xml的传参方式,而函数调用前的t就包含我们原始请求的值,如手机号,时间戳等
然后我们接着看看v函数,跟进到v函数里面,不用看了,一目了然,哎呦,还用的国密,sm4对称加密,加密模式为CBC
这里其实关于响应体我们就不用分析了,盲猜也是用上方的对称算法进行解密,接下来我们验证一下
一模一样吧
同样尝试解密响应也成功
0x02 sign分析
sign的相关的代码在data加密的代码附近,这里可以看到,sign是a的值,而a是在t没被加密前,调用了getRequstSign(t)函数,那么我们看一下这个函数做了什么
代码如下,可以看到最后的返回值是将参数i
进行了md5加密,而i的值,是t的值加上e(也就是我们的参数),外加字符串'VETRIP_B2C'的组合,而t的值是对this.COMPID进行md5得到的,this.COMPID我已经打印了,也是个字符串CLYTB,既然this.COMPID是固定的,那么它的md5加密也就是t的值也是固定的为d4a开头的一串值,逻辑就有了
同样的我们可以尝试验证一下
一样
0x03 浏览器->Burp
上面的js分析很基础,接下来就到了本篇文章的核心内容了,针对于此网站,我测试的时候想达到在Burp测试的时候请求体的值是可视的也就是没有加密的,同样响应我也想要解密后的,这些条件满足的同时还不能影响实际使用,整体解决方案如下图所示
首先我们想要让想达到请求和响应都明文,第一关就是将浏览器到Burp的参数为未加密状态,怎么实现呢?可以通过hook,但是还有更简单的方式,浏览器选择本地覆盖,新建,
选择本地的一个空文件夹后,允许
右键代码处
这里因为我将上款的代码格式化了,不能直接用,这里面坑很多,最保险的做法是重新刷新一下,然后定位到对应js,注意千万不要格式化,右键选择保存并覆盖
这时候我们访问这个网站,这个网站加载这个js的时候就会加载我们本地的js
接下来去Overrides标签页选择对应js,这时候就可以放心大胆的格式化了(这里卖个关子,当然也不能100%放心,还有某些情况是不能格式化的,具体什么情况呢,我不说?),然后将t加密这行删掉,这样就保证请求时候的data字段不会被加密了(devtools也就是f12不要关),别忘了ctrl+s保存一下修改后的js
我们可以抓包看一下,请求中data的值是原值,没有被加密,这样我们就达到了Burp中的可视化目的
0x04 Burp上游加解密
上面我们已经解决了请求中data字段将其变成了未加密前的原值,但是后续我们发包就是以原值发的所以是400,并且响应中的res解密操作也还没做
接下来我们就要用到之前写rpc文章里面mitmproxy了,让其成为Burp的上游,在发请求和收响应的时候都改一下我们的值,另其加密解密,以及确保sign值正确,代码写到下边了,懒得写注释了,自己研究吧
import base64
import hashlib
import json
from gmssl import sm4
from mitmproxy import ctx
encrypt_key = b'd6a785b93b6c0d4a'
iv = b'2d91d2be3ad9e42c'
gmsm4 = sm4.CryptSM4()
def encryptSM4(value):
gmsm4.set_key(encrypt_key, sm4.SM4_ENCRYPT)
data_str = str(value)
encrypt_value = gmsm4.crypt_cbc(iv,data_str.encode())
text = base64.b64encode(encrypt_value)
return text.decode('utf-8')
def decryptSM4(encrypt_value):
encrypt_value = base64.b64decode(encrypt_value).hex()
gmsm4.set_key(encrypt_key, sm4.SM4_DECRYPT)
decrypt_value = gmsm4.crypt_cbc(iv,bytes.fromhex(encrypt_value))
return decrypt_value.decode()
def sign(text):
md5_hash = hashlib.md5()
md5_hash.update(('d4a4a210ed3d3d367049d4d331883485'+text+'VETRIP_B2C').encode())
return md5_hash.hexdigest()
#截获请求加密data,以及更新sign
def request(flow):
if flow.request.urlencoded_form:
data_value = flow.request.urlencoded_form['data']
flow.request.urlencoded_form['data'] = encryptSM4(data_value)
flow.request.urlencoded_form['sign'] = sign(data_value)
def response(flow):
content_type = flow.response.headers.get("Content-Type", "")
if "application/json" in content_type:
# 解析 JSON 响应体
response_body = flow.response.get_text()
json_data = json.loads(response_body)
json_data['res'] = decryptSM4(json_data['res'])
flow.response.set_text(json_data['res'])
利用mitmproxy开代理,设置为Burp的上游代理
mitmproxy.exe -p 8081 -s .mit.py
最终效果如下所示,这时候参数我们也可以放心大胆的修改了
0x05 本地js修改
晚上正睡觉呢,突然想起来不对,少点东西,就腾的一下爬起来,有了下文
上面遗漏了一步,就是虽然响应是明文了,但是返回到浏览器它肯定也是明文,正常浏览器肯定会提取res的值,然后使用之前,前面肯定有个解密的步骤所以现在这样肯定有问题,我们要去把浏览器获取响应后的数据处理代码改一下
其实这个步骤应该放在漆面sign分析后,因为那时候还没改浏览器本地覆盖调试比较简单,但是改完之后其实也没太大关系了,如果你真的理解操作起来也一样
这是我们响应包,可以看出是json格式,并且呢里面有res字段,所以我们在js中怎么快速定位到响应包处理的代码呢,其实很简单,正常逻辑它肯定要拿到我们响应中的res然后并进行解密吗,所以这里直接搜"res"
即可,so easy
发现两处,将发现的两处都打上断点然后再次点击发送验证码触发断点,这里可以看到t.data
就是我们从浏览器取出的json,然后它取了里面的res字段,将其赋值给了变量i
,后边肯定有对其解密的代码,我们仔细看看
紧接着这行可以看到对i
调用了函数b
,那想都不用想那个b
函数肯定是解密函数,然后解密后取出了里面res值,然后发现下边那行也调用了b(i)
,所以这两处都需要改
然后我们想想我们这里怎么改,这是我们正常浏览器返回的json,它是解密后的,所以直接动两处代码就好了
然后这时候再触发,擦,抛异常了,然后研究了一通,发现问题所在,就是这块的JSON.parse,JSON.parse() 是 JavaScript 中的一个内置函数,用于将 JSON 字符串解析为 JavaScript 对象;而这里出错的原因就是i
已经是JavaScript 对象了,所以就不用多此一举了
如下
成功,一切正常,继续睡觉
原文始发于微信公众号(小艾搞安全):在渗透的幽灵模式中开启透视外挂
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论