前言
这篇文章适合有一定burp基础,java基础,maven打包基础,但几乎没有burp插件开发基础的小白。文章包含基础环境(客户端、服务端)代码,burp插件代码(源码下载:https://github.com/alteralver/优快云-KPX-/blob/main/KPX.zip)。
开发环境参考:java -17.0.9、nodejs - v23.10.0、burp社区版 - 2023.1.3
背景
作者在实践中遇到一些前端加解密场景,虽然网上的插件非常多,但是作者属于更喜欢了解插件开发的原理,这样遇到什么问题可以比较方便修改代码。(因为使用其他大佬的插件总是有这样那样不明白的问题。0.0)所以在工作之余自己模拟工作中遇到的情况,写了简单应用场景,开发了合适的插件。
实践中遇到的问题是,服务端对请求进行了数据完整性校验(原环境是mblNum、loginTime、请求体进行2轮MD5计算,实现数据完整性校验),修改请求体会导致校验不过
实现了burp proxy( burp.api.montoya.proxy.http中ProxyRequestHandler接口,后文均称proxy接口)和http(burp.api.montoya.http.handler的HttpHandler接口,后文均称http接口)两个接口。主要功能是,获取请求中mblNum、loginTime字段,进行2轮MD5计算,添加security,绕过服务端对请求的数据完整性校验。
正文
基础环境与插件源码下载:https://github.com/alteralver/优快云-KPX-/blob/main/KPX.zip
基础环境(服务端、客户端)
开发环境参考:java -17.0.9、nodejs - v23.10.0、burp社区版 - 2023.1.3(不同版本api有差异,推荐使用本版本开发)
客户端(需要修改xxxxxx为自己的ip)
仅实现了发送请求
<!DOCTYPE html>
<html>
<head>
<title>请求发送工具</title>
</head>
<body>
<button onclick="sendGet()">发送GET请求</button>
<button onclick="sendPost()">发送POST请求</button>
<script>
function sendGet() {
fetch('http://xxxxxx:3001/?name=test&id=123×tamp=' + Date.now(), {
method: 'GET'
});
}
function sendPost() {
fetch('http://xxxxxx:3001/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'loginTime': '1759208633762',
'mblNum': '13585587065'
},
body: JSON.stringify({
action: 'test',
data: 'sample data',
time: new Date().toISOString(),
resBody: 'chentest1;chentest2;chentest3'
})
});
}
</script>
</body>
</html>
服务端(不需要修改,需要关注端口端口占用问题)
仅实现了处理请求,以及打印接收到的请求头、请求头,方便观察效果。
const http = require('http');
const querystring = require('querystring');
const server = http.createServer((req, res) => {
// 1. 增强CORS头的灵活性
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', '*');
res.setHeader('Access-Control-Expose-Headers', 'Content-Type, X-Custom-Header');
// 2. 处理OPTIONS预检请求
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
// 3. 处理POST请求
if (req.method === 'POST') {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
// --- 核心修改部分:在控制台打印请求头和请求体 ---
console.log('--- 收到新的 POST 请求 ---');
console.log('请求头 (Headers):');
console.log(req.headers); // 打印请求头对象
console.log('-------------------------');
console.log('请求体 (Body):');
console.log(body); // 打印请求体字符串
console.log('-------------------------\n');
// --- 你原来的业务逻辑保持不变 ---
let resBody = '';
let parsedData = null;
try {
const contentType = req.headers['content-type'] || '';
if (contentType.includes('application/json')) {
parsedData = JSON.parse(body);
console.log('解析JSON成功:', parsedData);
} else if (contentType.includes('application/x-www-form-urlencoded')) {
parsedData = querystring.parse(body);
console.log('解析Form Data成功:', parsedData);
} else {
console.log('未识别的Content-Type,将原始body作为数据');
parsedData = { rawBody: body };
}
if (parsedData && parsedData.resBody) {
resBody = parsedData.resBody;
}
} catch (e) {
console.error('解析请求体失败:', e);
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
error: 'Invalid request body format.',
details: e.message
}));
return;
}
// 返回结果给前端
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
message: 'POST request successful!',
resBody: resBody
}));
});
} else {
// 处理其他非GET/POST的请求
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'chentest-get-1;chentest-get-2' }));
}
});
const PORT = 3001;
server.listen(PORT, '0.0.0.0', () => {
console.log(`Server is running at http://0.0.0.0:${PORT}/`);
});
启动burp5005端口远程调试(有需要可以用,可以和开发工具,如idea一起调试代码)
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar burpsuite_community.jar
插件代码与成品插件:对应源码中的kpx_burp.zip文件。
文件包含自定义代码的主要结构
结构
|-KPX_MD5(实现MD5计算)
|-MyHttpHandler(实现repeater的security MD5计算,并添加为请求头)
|-MyHttpHandler1(未进行repeater的security MD5计算,与MyHttpHandler做对比用)
|-MyProxyHttpRequestHandler (实现proxy的security MD5计算,并添加为请求头)
|-MyRegister(初始化与注册接口)
maven打包,使用了插件,将第三方依赖打包进插件中。
<build>
<plugins>
<!-- Maven Shade Plugin: 用于创建包含所有依赖的 "uber-jar" -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<!-- 绑定到 Maven 的 "package" 生命周期阶段 -->
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<!--
(可选) 如果你的插件有主类(可以直接运行),可以在这里配置。
Burp插件通常没有主类,所以这部分可以保持注释状态。
-->
<!--
(推荐) 防止打包时因重复的 META-INF 文件(如签名文件)导致问题。
这对于包含多个依赖的项目非常重要。
-->
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
前端生成逻辑分析
security请求头字段,用来防止json格式请求体被篡改。通过跟栈分析发现经过2轮MD5加密。(对应MD5加密)
第一轮加密代码,mblNum+loginTime+tripbiz,计算MD5,取8-24个字符
public static String getmblNumloginTimeMD5(String mblNum,String loginTime){
//String mblNum = "13585587065";
//String loginTime = "1759208633762";
// 截取第8到第16个字符(索引从0开始)
String MD51 = DigestUtils.md5Hex(mblNum + loginTime + "tripbiz").substring(8,24);
//获取token的security
//String MD52 = DigestUtils.md5Hex("{}"+MD51).substring(8,24);
return MD51;
}
第二轮加密代码,getReqBodyJson+第一轮的结果(getmblNumloginTimeMD5),计算MD5,取8-24个字符
public static String getSecurityMD5(String getReqBodyJson,String getmblNumloginTimeMD5) {
String SecurityMD5 = DigestUtils.md5Hex(getReqBodyJson+getmblNumloginTimeMD5).substring(8,24);
return SecurityMD5;
}
其中getReqBodyJson需注意,如果请求体只有params一个参数,则取其值,如果是其他的情况,则整体输入。
public static String getReqBodyJson(String data) {
try {
JSONObject json = new JSONObject(data);
//System.out.println("执行JSONObject json = new JSONObject(data);");
return json.has("params")
? json.get("params").toString()
: data;
} catch (Exception e) {
e.printStackTrace();
return "3";
}
基本使用
加载插件,启动客户端,服务端,可以看到效果。

1、启动客户端
python -m http.server xxxx

2、启动服务端
node xxx.js

3、配置好基础环境,加载插件,启动监听等。
效果
使用proxy拦截,添加请求头,修改请求体。(参考如下)

案例使用的数值为
loginTime: 1759208633762
mblNum: 13585587065
{"params":"{}"}
由于注册了proxy接口和http接口(http会处理所有请求),所以有security有2个值,可以添加判断来规避这种情况

如果使用repeater/intruder(http接口会处理所有请求,包括intruder),则security有1个值(因为未经过proxy拦截,只触发了http接口)

4345





