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


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

分析得加解密为AES,得到密钥与IV均为1234567891234567

编写Mimtproxy解密脚本:

plaintext
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加密+签名校验脚本:

plaintext
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



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