burpsuite插件proxy和http模块的基础使用

前言

这篇文章适合有一定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&timestamp=' + 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接口)

 

 

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值