如何在 Burp 中自动加解密请求包和返回包

简介

在 APP 漏洞挖掘或者 Web 站点渗透中经常碰到抓包双端加密的情况,这种时候逆向出加解密方式也不太方便进一步手工渗透甚至自动渗透。

在某天上网闲逛的时候发现了一个 Burp 的插件,叫 Galaxy,这个插件支持自己编写 Java、Python、JS 去加解密包。

插件使用注意事项

项目采用 Burp Montoya API 开发,Burp 版本不低于 v2023.10.3.7 。

项目使用 JDK 17 进行开发及编译,请确保启动 Burp 的 JDK 不低于 17。

项目使用了动态编译,请确保启动 Burp 的是 JDK,而不是 JRE。

如果你下载或打包后的 jar 包含 without-jython 字样,请在 Burp 的 Java environment(Settings -> Extensions)配置一个文件夹,并将 jython-standalone-xxx.jar 放在该文件夹。

使用方法

此处使用某个 APP 做举例,下为某个请求包和返回包

image

可以看到双端都加密了,而且还有签名保证防篡改。

逆向加解密方式

这里直接装上 frida hook java 加解密即可获取到加密方式和密钥。

数据使用 AES/ECB/PKCS5Padding​ 加密,密钥假设为 xxx​。

签名的加密方式为

1
加密后的数据base64编码+请求头里的时间+请求头里的id+get请求中的token参数

这些内容都在请求包里,可以直接加密签名。

编写代码

这是这个插件提供的默认 AES ECB 模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import org.m2sec.core.utils.*;
import org.m2sec.core.models.*;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;

/**
* 内置示例,需要自定义代码文件时查看该文档:https://github.com/outlaws-bai/Galaxy/blob/main/docs/Custom.md
* 按 Ctrl(command) + ` 可查看内置函数
*/
public class AesEcb {

private static final String ALGORITHM = "AES/ECB/PKCS5Padding";
private static final byte[] secret = "32byteslongsecretkeyforaes256!aa".getBytes();
private static final String jsonKey = "data";

private Logger log;

public AesEcb(Logger log) {
this.log = log;
}


/**
* HTTP请求从客户端到达Burp时被调用。在此处完成请求解密的代码就可以在Burp中看到明文的请求报文。
*
* @param request Request 请求对象
* @return 经过处理后的request对象,返回null代表从当前节点开始流量不再需要处理
*/
public Request hookRequestToBurp(Request request) {
// 获取需要解密的数据
byte[] encryptedData = getData(request.getContent());
// 调用内置函数解密
byte[] data = CryptoUtil.aesDecrypt(ALGORITHM, encryptedData, secret, null);
// 更新body为已加密的数据
request.setContent(data);
return request;
}

/**
* HTTP请求从Burp将要发送到Server时被调用。在此处完成请求加密的代码就可以将加密后的请求报文发送到Server。
*
* @param request Request 请求对象
* @return 经过处理后的request对象,返回null代表从当前节点开始流量不再需要处理
*/
public Request hookRequestToServer(Request request) {
// 获取被解密的数据
byte[] data = request.getContent();
// 调用内置函数加密回去
byte[] encryptedData = CryptoUtil.aesEncrypt(ALGORITHM, data, secret, null);
// 将已加密的数据转换为Server可识别的格式
byte[] body = toData(encryptedData);
// 更新body
request.setContent(body);
return request;
}

/**
* HTTP请求从Server到达Burp时被调用。在此处完成响应解密的代码就可以在Burp中看到明文的响应报文。
*
* @param response Response 响应对象
* @return 经过处理后的response对象,返回null代表从当前节点开始流量不再需要处理
*/
public Response hookResponseToBurp(Response response) {
// 获取需要解密的数据
byte[] encryptedData = getData(response.getContent());
// 调用内置函数解密
byte[] data = decrypt(encryptedData);
// 更新body
response.setContent(data);
return response;
}

/**
* HTTP请求从Burp将要发送到Client时被调用。在此处完成响应加密的代码就可以将加密后的响应报文返回给Client。
*
* @param response Response 响应对象
* @return 经过处理后的response对象,返回null代表从当前节点开始流量不再需要处理
*/
public Response hookResponseToClient(Response response) {
// 获取被解密的数据
byte[] data = response.getContent();
// 调用内置函数加密回去
byte[] encryptedData = encrypt(data);
// 更新body
// 将已加密的数据转换为Server可识别的格式
byte[] body = toData(encryptedData);
// 更新body
response.setContent(body);
return response;
}

public byte[] decrypt(byte[] content) {
return CryptoUtil.aesDecrypt(ALGORITHM, content, secret, null);
}

public byte[] encrypt(byte[] content) {
return CryptoUtil.aesEncrypt(ALGORITHM, content, secret, null);
}

private byte[] getData(byte[] content) {
return CodeUtil.b64decode((String) JsonUtil.jsonStrToMap(new String(content)).get(jsonKey));
}

private byte[] toData(byte[] content) {
HashMap<String, Object> jsonBody = new HashMap<>();
jsonBody.put(jsonKey, CodeUtil.b64encodeToString(content));
return JsonUtil.toJsonStr(jsonBody).getBytes();
}
}

可以看到有四个函数需要修改,底下只提供了两个 getData​ 和 toData

我先编写了 Python 手动解密了一次,发现需要进行 url 解码一次。下面直接给出一整个加解密的 Java 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import org.m2sec.core.utils.*;
import org.m2sec.core.models.*;

import java.util.HashMap;
import java.util.Map;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

import org.slf4j.Logger;

/**
* 内置示例,需要自定义代码文件时查看该文档:https://github.com/outlaws-bai/Galaxy/blob/main/docs/Custom.md
* 按 Ctrl(command) + ` 可查看内置函数
*/
public class AesEcbTmp {

private static final String ALGORITHM = "AES/ECB/PKCS5Padding";
private static final byte[] secret = "xxx".getBytes();

private Logger log;

public AesEcbTmp(Logger log) {
this.log = log;
}


/**
* HTTP请求从客户端到达Burp时被调用。在此处完成请求解密的代码就可以在Burp中看到明文的请求报文。
*
* @param request Request 请求对象
* @return 经过处理后的request对象,返回null代表从当前节点开始流量不再需要处理
*/
public Request hookRequestToBurp(Request request) {
// 获取需要解密的数据
byte[] encryptedData = getReqData(request.getContent());
// 调用内置函数解密
byte[] data = CryptoUtil.aesDecrypt(ALGORITHM, encryptedData, secret, null);
// 更新body为已加密的数据
request.setContent(URLDecoder.decode(new String(data), StandardCharsets.UTF_8).getBytes());
return request;
}

/**
* HTTP请求从Burp将要发送到Server时被调用。在此处完成请求加密的代码就可以将加密后的请求报文发送到Server。
*
* @param request Request 请求对象
* @return 经过处理后的request对象,返回null代表从当前节点开始流量不再需要处理
*/
public Request hookRequestToServer(Request request) {
// 获取被解密的数据
byte[] data = request.getContent();
// 调用内置函数加密回去
byte[] encryptedData = CryptoUtil.aesEncrypt(ALGORITHM, data, secret, null);
// 将已加密的数据转换为Server可识别的格式
byte[] body = toServerData(encryptedData, request);
// 更新body
request.setContent(body);
return request;
}

/**
* HTTP请求从Server到达Burp时被调用。在此处完成响应解密的代码就可以在Burp中看到明文的响应报文。
*
* @param response Response 响应对象
* @return 经过处理后的response对象,返回null代表从当前节点开始流量不再需要处理
*/
public Response hookResponseToBurp(Response response) {
// 获取需要解密的数据
byte[] encryptedData = getResData(response.getContent());
// 调用内置函数解密
byte[] data = decrypt(encryptedData);
// 更新body
String encb64data = '"' + CodeUtil.b64encodeToString(encryptedData) + '"';
String urldecodeData = URLDecoder.decode(new String(data), StandardCharsets.UTF_8);
String body = response.getBody();
response.setContent(body.replace(encb64data, urldecodeData).getBytes());
return response;
}

/**
* HTTP请求从Burp将要发送到Client时被调用。在此处完成响应加密的代码就可以将加密后的响应报文返回给Client。
*
* @param response Response 响应对象
* @return 经过处理后的response对象,返回null代表从当前节点开始流量不再需要处理
*/
public Response hookResponseToClient(Response response) {
// 获取被解密的数据
byte[] data = response.getContent();
// 调用内置函数加密回去
byte[] encryptedData = encrypt(data);
// 更新body
// 将已加密的数据转换为Server可识别的格式
byte[] body = toClientData(encryptedData);
// 更新body
response.setContent(body);
return response;
}

public byte[] decrypt(byte[] content) {
return CryptoUtil.aesDecrypt(ALGORITHM, content, secret, null);
}

public byte[] encrypt(byte[] content) {
return CryptoUtil.aesEncrypt(ALGORITHM, content, secret, null);
}

private byte[] getResData(byte[] content) {
Map<?, ?> bizData =(Map<?, ?>) JsonUtil.jsonStrToMap(new String(content)).get("bizData");
return CodeUtil.b64decode((String) bizData.get("key"));
}

private byte[] toServerData(byte[] content, Request request) {
HashMap<String, Object> jsonBody = new HashMap<>();
// 导入加密后的数据
jsonBody.put("key", CodeUtil.b64encodeToString(content));
// 以下是开始计算签名
Headers headers = request.getHeaders();
Query query = request.getQuery();
String key = CodeUtil.b64encodeToString(content);
String tmp = key + headers.get("Request-Date").get(0);
tmp += headers.get("Request-Id").get(0);
tmp += query.get("utoken").get(0);
jsonBody.put("sign", HashUtil.calcToHex("MD5", tmp.getBytes(), null));
return JsonUtil.toJsonStr(jsonBody).getBytes();
}

private byte[] getReqData(byte[] content){
return CodeUtil.b64decode((String) JsonUtil.jsonStrToMap(new String(content)).get("key"));
}

private byte[] toClientData(byte[] content){
return null;
}
}

使用

将代码复制进插件区域之后,点击 Start 即可

image

以下为请求过程中自动解密内容

image

我们直接把解密后的内容发送到 Repeater,然后可以直接进行编辑,这个插件也会自动加密

image

这个插件也能在 Intruder 中使用,而且能与 sqlmap 联动

注意事项

在页面新建插件的时候,注意类名要跟文件名相同,构造函数名称要跟类名相同,其实这个都是 Java 基础内容了,不过我确实太久没用,忘记了,折腾了一小会儿才想起来这么个事儿。