前言:
本方案适用于请求包与响应包为全加密以及有签名校验的情景,实现Mimtproxy+Burp配合下的无痛对AES加解密的网站进行渗透测试,后续可联动被动扫描xray等工具,遇到其他情况可根据实际情况更改脚本,思路大概一致。
靶场搭建:
https://github.com/0ctDay/encrypt-decrypt-vuls/

先分析JS中的加解密方法:

分析得加解密为AES,得到密钥与IV均为1234567891234567
编写Mimtproxy解密脚本:
from mitmproxy import http from Crypto.Cipher import AES import base64
# 固定参数 KEY = b'1234567891234567' # 16 字节密钥 IV = b'1234567891234567' # 16 字节初始化向量
def decrypt_aes_cbc_pkcs7(encrypted_data): """ 解密 AES-CBC-PKCS7 加密的数据 """ cipher = AES.new(KEY, AES.MODE_CBC, IV) decrypted_data = cipher.decrypt(encrypted_data) # 去除 PKCS7 填充 padding_len = decrypted_data[-1] decrypted_data = decrypted_data[:-padding_len] return decrypted_data
def request(flow: http.HTTPFlow) -> None: # 判断请求的 Host 是否为目标值 if flow.request.host == "192.168.200.22": # 获取请求体 encrypted_body = flow.request.content try: # Base64 解码 encrypted_body_bytes = base64.b64decode(encrypted_body) # AES 解密 decrypted_body = decrypt_aes_cbc_pkcs7(encrypted_body_bytes) # 将解密后的数据替换原始请求体 flow.request.content = decrypted_body print("解密成功!") except Exception as e: # 解密失败,保留原始数据包 print(f"解密失败:{e}") # 这里可以选择是否保留原始数据包,若注释掉下一行则不修改请求体 flow.request.content = encrypted_body
|
此处只解密了请求包,无需解密响应包,因为此时请求包为明文,发过去服务器也无法识别
其中脚本可根据实际换成其他限定的Host,符合条件的进行解密,否则放行,无法解密也放行
数据包原始内容:

启动脚本对请求包进行解密处理:

浏览器代理8010,也就是代理到mitmproxy监听的端口,然后mitmproxy的上游给到burp,也就是把数据包传到burp
mitmdump -p 8010 -s decrypt.py --mode upstream:http://127.0.0.1:8080 --ssl-insecure

查看到Burp的效果:

此处请求已变成明文,但此时发送到服务器是无法识别的,所以要再将其加密回去

而请求包是带有签名校验的,需要先还原签名校验算法


requestID生成逻辑已有,可以模拟还原,整体签名流程是对body的明文内容+requestID+时间戳进行MD5
编写Mimtproxy加密+签名校验脚本:
from mitmproxy import http from Crypto.Cipher import AES import base64 import json import hashlib import time import random import string
# 固定参数 KEY = b'1234567891234567' # 16 字节密钥 IV = b'1234567891234567' # 16 字节初始化向量
def encrypt_aes_cbc_pkcs7(data): """ 使用 AES-CBC-PKCS7 模式加密数据 """ # 确保数据是 bytes 类型 if isinstance(data, str): data = data.encode('utf-8') # 计算需要填充的长度 block_size = AES.block_size padding_len = block_size - (len(data) % block_size) # PKCS7 填充 padding = bytes([padding_len] * padding_len) padded_data = data + padding # 加密 cipher = AES.new(KEY, AES.MODE_CBC, IV) encrypted_data = cipher.encrypt(padded_data) # 返回 Base64 编码的加密数据 return base64.b64encode(encrypted_data).decode('utf-8')
def decrypt_aes_cbc_pkcs7(encrypted_data): """ 解密 AES-CBC-PKCS7 加密的数据 """ # Base64 解码 encrypted_data_bytes = base64.b64decode(encrypted_data) # 解密 cipher = AES.new(KEY, AES.MODE_CBC, IV) decrypted_data = cipher.decrypt(encrypted_data_bytes) # 去除 PKCS7 填充 padding_len = decrypted_data[-1] decrypted_data = decrypted_data[:-padding_len] return decrypted_data
def generate_uuid(): """ 生成符合要求的 UUID 字符串 """ # 生成 32 位随机 hexadecimal 字符串 hex_digits = "0123456789abcdef" s = [random.choice(hex_digits) for _ in range(32)] # 设置版本位(第 14 位为 '4') s[14] = '4' # 设置 variant 位(第 19 位) s[19] = hex_digits[(int(s[19], 16) & 0x3) | 0x8] # 设置分隔符位置 s[8] = s[13] = s[18] = s[23] = '' # 返回 UUID 字符串 return ''.join(s)
def generate_sign(data, request_id, timestamp): """ 生成签名 """ # 确保 data 是字符串 if isinstance(data, bytes): data = data.decode('utf-8') # 拼接字符串并计算 MD5 哈希 sign_str = data + request_id + str(timestamp) md5 = hashlib.md5() md5.update(sign_str.encode('utf-8')) return md5.hexdigest()
def sort_ascii(data): """ 按 ASCII 码排序数据(模拟前端的 sortASCII 函数) """ if not isinstance(data, dict): return data sorted_data = {} for key in sorted(data.keys()): sorted_data[key] = sort_ascii(data[key]) return sorted_data
def request(flow: http.HTTPFlow) -> None: if flow.request.host == "192.168.200.22": try: # 获取明文请求体 plaintext_body = flow.request.content # 将请求体转换为 JSON data = json.loads(plaintext_body) # 对 data 按 ASCII 码排序 sorted_data = sort_ascii(data) # 生成时间戳 timestamp = int(time.time() * 1000) # 生成 UUID request_id = generate_uuid() # 生成签名 sign = generate_sign(json.dumps(sorted_data, separators=(',', ':')), request_id, timestamp) # 将请求体加密 encrypted_body = encrypt_aes_cbc_pkcs7(json.dumps(data, separators=(',', ':'))) # 构造最终的请求体(Base64 编码的加密数据) final_encrypted_body = encrypted_body # 更新请求的 Content-Type 和 Content-Length flow.request.headers["Content-Type"] = "application/json;charset=utf-8" flow.request.headers["Content-Length"] = str(len(final_encrypted_body)) # 将 timestamp、requestId 和 sign 添加到请求头中 flow.request.headers["timestamp"] = str(timestamp) flow.request.headers["requestId"] = request_id flow.request.headers["sign"] = sign # 更新请求体 flow.request.content = final_encrypted_body.encode('utf-8') print("加密成功!") except Exception as e: print(f"加密失败:{e}")
def response(flow: http.HTTPFlow) -> None: """ 解密响应体 """ if flow.request.host == "192.168.200.22": try: # 获取加密的响应体 encrypted_body = flow.response.content.decode('utf-8') # 解密响应体 decrypted_body = decrypt_aes_cbc_pkcs7(encrypted_body) # 将解密后的响应体转换为字符串 decrypted_body_str = decrypted_body.decode('utf-8') # 更新响应体为明文 flow.response.content = decrypted_body_str.encode('utf-8') print("解密成功!") except Exception as e: print(f"解密失败:{e}")
|
再启动另一个Mitmproxy,监听端口8020
mitmdump -p 8020 -s encrypt.py --ssl-insecure

此时Burp设置上游代理到Mitmproxy监听的端口

下面查看效果:
Proxy:

Repeater:

至此,可以无痛进行一系列爆破或其他测试


同一接口下,无mitmproxy与启动mitmproxy对比:


如果联动xray,需要基于以上配置改换一下:
Burp设置上游为xray监听的7777

Xray的config.yaml设置代理127.0.0.1:8020,也就是第二个mitmproxy

设置扫描白名单为搭建的靶场:

Xray启动监听7777:
xray_windows_amd64.exe webscan --listen 127.0.0.1:7777 --html-output test.html


联动成功,成功开始漏洞,以及检测出敏感数据