burpsuite插件开发安全指南

   我想写一篇文章,关于burpsuite插件开发入门。去年我写了一些burp插件,用于辅助渗透和漏洞挖掘,这给我带来了很多方便,可以捡到一些安全漏洞。

   本人以第一视角说下本人是如何学习burpsuite插件开发的。本文只是入门,如果想要深入学习插件开发,还需要更多的学习和参考。

   1.环境配置和搭建  

idea+maven+jdk14(可按照需求,自定义设置jdk版本):

(1)idea创建maven项目  忽略步骤:

  

 

 

   删除掉<properties>标签里的内容

  (2)新增Burp API依赖,pom中引入相关依赖:

   

 

 

   

<dependencies>
        <!-- https://mvnrepository.com/artifact/net.portswigger.burp.extender/burp-extender-api -->
        <dependency>
            <groupId>net.portswigger.burp.extender</groupId>
            <artifactId>burp-extender-api</artifactId>
            <version>1.7.22</version>
        </dependency>
    </dependencies>

 

 刷新maven加载jar包:

  

 

 

 

 至此引入依赖成功:

 

 2. 插件开发基础部分:

 需求:UI控制台打印hello world:  

package com.test.DevDemo;

import burp.IBurpExtender;
import burp.IBurpExtenderCallbacks;
import burp.IExtensionHelpers;

import java.io.PrintWriter;

public class BurpExtender implements IBurpExtender {
    private IExtensionHelpers helpers;
    private  IBurpExtenderCallbacks callbacks;
    private PrintWriter stdout;
    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks iBurpExtenderCallbacks) {
        this.callbacks = iBurpExtenderCallbacks;
        //设置插件名字
        this.callbacks.setExtensionName("第一个程序,这是我们的插件名字");
        //打印信息在UI控制台页面,打印内容为hello world
        this.stdout = new PrintWriter(callbacks.getStdout(),true);
        stdout.println("hello world");
    }
}

 

目录名必须设置成burp目录,类名即文件名必须是BurpExtender:

 

 

 

(2)mvn打包jar包,配置打包jar包所需的依赖:

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
        </plugins>
    </build>

 

 

 

 

(3)idea打开当前终端,输入打包jar包命令:

mvn clean install

 

 

 

 

(4)打开当前目录→target

open . 打开当前目录

 

 

 

点击target目录,选择BurpPlugDev-1.0-SNAPSHOT-jar-with-dependencies.jar 这个带dependencies的jar包

解释:

BurpPlugDev-1.0-SNAPSHOT.jar 为不带依赖的jar包
BurpPlugDev-1.0-SNAPSHOT-jar-with-dependencies.jar 为带依赖的jar包

 

 

(5)burpsuite引入我们打包的jar包

1.选择插件,选择ADD添加

 

 

 

 

2.选择jar包,点击NEXT下一步

 

 

 

3.加载成功,显示插件名字并且打印输出hello world

 

 

第一部分结束。

 

Burp监听器,侦听器 测试demo使用:

 

 

 

IExtensionStateListener
IHttpListener
IProxyListener
IScannerListener
IScopeChangeListener

 

编写测试代码,参考官方案例:

https://github.com/PortSwigger/example-event-listeners/blob/master/java/BurpExtender.java

复制粘贴抄下:

package burp;

import burp.IBurpExtender;
import burp.IBurpExtenderCallbacks;
import burp.IExtensionHelpers;

import java.io.PrintWriter;

public class BurpExtender implements IBurpExtender, IHttpListener,
        IProxyListener, IScannerListener, IExtensionStateListener{
    private IExtensionHelpers helpers;
    private  IBurpExtenderCallbacks callbacks;
    private PrintWriter stdout;
    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks iBurpExtenderCallbacks) {
        this.callbacks = iBurpExtenderCallbacks;
        //设置插件名字
        this.callbacks.setExtensionName("第一个程序,这是我们的插件名字");
        //打印信息在UI控制台页面,打印内容为hello world
        this.stdout = new PrintWriter(callbacks.getStdout(),true);
        stdout.println("hello world");
        //一定要注册监听器,不然下面的函数无法生效
        callbacks.registerHttpListener(this);
        callbacks.registerProxyListener(this);
        callbacks.registerScannerListener(this);
        callbacks.registerExtensionStateListener(this);
    }

    @Override
    public void extensionUnloaded() {
        stdout.println("Extension was uploaded");
    }

    @Override
    public void processHttpMessage(int i, boolean b, IHttpRequestResponse iHttpRequestResponse) {
        stdout.println(
                (b ? "HTTP request to ":"HTTP response from ")+iHttpRequestResponse.getHttpService()+"["+callbacks.getToolName(i)+"]"
        );
    }

    @Override
    public void processProxyMessage(boolean b, IInterceptedProxyMessage iInterceptedProxyMessage) {
        stdout.println(
                (b ? "Proxy request to ":"Proxy response from ")+iInterceptedProxyMessage.getMessageInfo().getHttpService()
        );
    }

    @Override
    public void newScanIssue(IScanIssue iScanIssue) {
        stdout.println("New scan issue "+ iScanIssue.getIssueName());
    }
}

 

按照前面说的方法,进行打包运行:

挂上代理访问网站:

可以发现在控制台可以看到Proxy请求信息和HTTP request的请求信息

 

 

 

发现其他信息并没有打印输出?

首先是IScannerListener接口含义,简单来说就是想触发这个接口,需要调度Burpsuite的Scanner扫描功能

 

 

 

其他的类似接口是类似的情况。需要特定场景才可以触发。

结论:访问一个http/https请求,默认触发IHttpListener和IProxyListener

如果写测试案例的话,需要大量依赖这两个监听器。

 

 

插件开发第二部分:http数据包获取

http请求数据包的几个部分

http请求:
1.header 请求头
2.body 请求body->非get/options请求,多于POST,PUT等
3.parameter 请求参数 
4.主机头端口协议

http响应:
1.header 请求头
2.body
3.主机头端口协议

测试用例,只涉及查,不涉及修改确认等操作,仔细看注释部分:

package burp;

import burp.IBurpExtender;
import burp.IBurpExtenderCallbacks;
import burp.IExtensionHelpers;

import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;

public class BurpExtender implements IBurpExtender, IHttpListener{
    private IExtensionHelpers helpers;
    private  IBurpExtenderCallbacks callbacks;
    private PrintWriter stdout;
    private PrintWriter stderr;
    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks iBurpExtenderCallbacks) {
        this.callbacks = iBurpExtenderCallbacks;
        //设置插件名字
        callbacks.setExtensionName("熟悉HTTP数据包各种操作");
        this.helpers = callbacks.getHelpers();
        //打印信息在UI控制台页面,打印内容为hello world
        this.stdout = new PrintWriter(callbacks.getStdout(),true);
        this.stderr = new PrintWriter(callbacks.getStderr(),true);
        stdout.println("hello world");
        //一定要注册监听器,不然下面的函数无法生效
        callbacks.registerHttpListener(this);
    }


    @Override
    public void processHttpMessage(int i, boolean b, IHttpRequestResponse iHttpRequestResponse) {
       //切换http监听模块为Burpsuiteproxy模块
        if(i==IBurpExtenderCallbacks.TOOL_PROXY){
            //对请求包进行处理
            if(b){
                //对消息体进行解析,messageInfo是整个HTTP请求和响应消息体的总和,各种HTTP相关信息的获取都来自于它,HTTP流量的修改都是围绕它进行的。
                IRequestInfo analyzeRequest = helpers.analyzeRequest(iHttpRequestResponse);
                /*****************获取参数**********************/
                List<IParameter> parameList = analyzeRequest.getParameters();
                //获取参数的方法
                //遍历参数
                for(IParameter para:parameList){
                    //获取参数
                    String key = para.getName();
                    //获取参数值(value)
                    String value = para.getValue();
                    int type = para.getType();
                    stdout.println("参数key value type:"+key+" "+value+" "+type);
                }

                //获取headers方法:
                List<String> headers = analyzeRequest.getHeaders();
                //新增header
                headers.add("myheader:hello world");
                //遍历请求头
                for(String header:headers){
                    stdout.println("header: "+header);
                }

                //获取协议 端口 和主机名
                IHttpService service = iHttpRequestResponse.getHttpService();
                stdout.println("协议 主机 端口 "+service.getProtocol()+" "+service.getHost()+" "+service.getPort());
            }

        }else{//这个逻辑是处理响应包
            IResponseInfo analyzeResponse = helpers.analyzeResponse(iHttpRequestResponse.getResponse());
            //获取响应码信息
            short statusCode = analyzeResponse.getStatusCode();
            stdout.println("status= "+statusCode);
            //获取响应头信息
            List<String> headers = analyzeResponse.getHeaders();
            for(String header:headers){
                stdout.println("header:"+header);
            }
            // 获取响应信息
            String resp = new String(iHttpRequestResponse.getResponse());
            int bodyOffset = analyzeResponse.getBodyOffset();
            String body = resp.substring(bodyOffset);
            stdout.println("response body="+body);
        }
    }
}

 

一图胜千言,图片和代码参考于:https://github.com/bit4woo/burp-api-drops

演示运行效果,当访问网站时,插件中会显示详细信息:

包含(参数,类型,请求头,主机端口协议等)

 

 

 

                  

 

 

     学到了这里,我们直接分析一个现成的安全burp插件工具:

     实战案例分析2个burp安全插件:

 

      分析案例1:https://github.com/Daybr4ak/ShiroScan

   

  

   这款工具主要用于检测是否使用Shiro框架,以及是否存在shiro key,如果存在key,我们才好方便下一步跑利用链。

 直接下载源码:

   主要看BurpExtender.java这个文件,idea导入相关代码文件

   发现除了BurpExtender类,其余都是接口类。它这个项目没有使用maven工程,使用的是导入接口

    

  首先是查看实现类:

  继承和实现了如下接口

  

   

extends AbstractTableModel implements IBurpExtender, IScannerCheck, ITab, IMessageEditorController

  我们知道java实现接口,必须实现接口的对应方法:

   

其中doPassiveScan为被动扫描,doActiveScan为主动扫描,因为shiroscan是被动扫描器,所以这里的大部分业务逻辑都会写在doPassiveScan中。

再往下说:

其中比较重要的就是IScannerCheck接口

ITab, IMessageEditorController为GUI设计,我们只需要copy。

自定义部分字段:

    

  大部分都是GUI需要使用的字段,可以学习了解,也可以不学习,其余定义的字段,在前面我们已经学到。

  字段定义如下:  

    private IBurpExtenderCallbacks callbacks;
    private IExtensionHelpers helpers;
    private PrintWriter stdout;
    private JSplitPane mjSplitPane;
    private List<TablesData> Udatas = new ArrayList<TablesData>();
    private List<Ulist> ulists = new ArrayList<Ulist>();
    private IMessageEditor HRequestTextEditor;
    private IMessageEditor HResponseTextEditor;
    private IHttpRequestResponse currentlyDisplayedItem;
    private URLTable Utable;
    private JScrollPane UscrollPane;
    private JSplitPane HjSplitPane;
    private JPanel mjPane;
    private JTabbedPane Ltable;
    private JTabbedPane Rtable;

  查看接口所需要实现的方法:

  

 

   

 

  加载插件就会显示这部分内容,即上面标注的代码含义。

  继续往下看代码:

     

  为ui图形化界面,这块先忽略,晚点再看。继续往下看代码:

  注册扫描器,注释写的很清楚了。因为想要使用被动/主动扫描功能,必须先注册。

  往下看被动扫描操作函数,因为shiro检测是被动扫描,所以几乎所有核心逻辑的实现都会在doPassiveScan函数内:

  我们都知道shiro检测的特征是:cookie中输入rememberMe=1,响应中输出rememberMe=deleteMe;

  

 

  那么我们的检测逻辑很简单:cookie中植入rememberMe=1,响应中如果包rememberMe=deleteMe

  即存在使用了shiro框架,存在shiro框架后,随之检测key

  核心逻辑第一部分:

  

 

        byte[] request = baseRequestResponse.getRequest();
        // 设置参数
        IParameter newParameter = helpers.buildParameter("rememberMe","1", (byte) 2);
        // 为request包添加设置好的参数
        byte[] newRequest = helpers.updateParameter(request, newParameter);
        // 创建一个新的HTTP请求
        IHttpService httpService = baseRequestResponse.getHttpService();
        IHttpRequestResponse newIHttpRequestResponse = callbacks.makeHttpRequest(httpService,newRequest);
        // 获取新HTTP请求的响应
        byte[] response = newIHttpRequestResponse.getResponse();

  

  这段很重要,代表着设置cookie中,植入参数rememberMe=1

IParameter newParameter = helpers.buildParameter("rememberMe","1", (byte) 2);

  

  这个2,代表声明的参数类型是cookie。

  

  所以这里的代码还可以这样写:

  

  继续往下解读:

  带着新的rememberMe=1后,开始走到判断响应包含逻辑:

  

if (getRememberMeNumber(response) > 0 && checUrl(httpService.getHost(), httpService.getPort()))

  

   重复url检测,对url做去重:

   

  符合条件就新增host和端口到gui中:

  

  都是gui操作,继续往下看,看业务逻辑,发现shiro框架后,下面就是检测key:

  检测出来,就在Scanner的issue中新增,在GUI中新增

  

 

List<Object> mes = FindKey(newIHttpRequestResponse, getRememberMeNumber(response)); 

  跟进FinkKey:

public List<Object> FindKey(IHttpRequestResponse baseRequestResponse, int num){
        try {
            SimplePrincipalCollection simplePrincipalCollection = new SimplePrincipalCollection();
            byte[] exp = getBytes(simplePrincipalCollection);
            String[] keys = new String[]{
                    "kPH+bIxk5D2deZiIxcaaaA==", "Z3VucwAAAAAAAAAAAAAAAA==", "wGiHplamyXlVB11UXWol8g==", "2AvVhdsgUs0FSA3SDFAdag==", "3AvVhmFLUs0KTA3Kprsdag==", "4AvVhmFLUs0KTA3Kprsdag==", "bWljcm9zAAAAAAAAAAAAAA==", "WcfHGU25gNnTxTlmJMeSpw==", "fCq+/xW488hMTCD+cmJ3aQ==", "kPv59vyqzj00x11LXJZTjJ2UHW48jzHN",
                    "6ZmI6I2j5Y+R5aSn5ZOlAA==", "1QWLxg+NYmxraMoxAXu/Iw==", "a2VlcE9uR29pbmdBbmRGaQ==", "5aaC5qKm5oqA5pyvAAAAAA==", "1AvVhdsgUs0FSA3SDFAdag==", "5RC7uBZLkByfFfJm22q/Zw==", "3AvVhdAgUs0FSA4SDFAdBg==", "a3dvbmcAAAAAAAAAAAAAAA==", "eXNmAAAAAAAAAAAAAAAAAA==", "U0hGX2d1bnMAAAAAAAAAAA==",
                    "Ymx1ZXdoYWxlAAAAAAAAAA==", "L7RioUULEFhRyxM7a2R/Yg==", "UGlzMjAxNiVLeUVlXiEjLw==", "bWluZS1hc3NldC1rZXk6QQ==", "ZUdsaGJuSmxibVI2ZHc9PQ==", "7AvVhmFLUs0KTA3Kprsdag==", "MTIzNDU2Nzg5MGFiY2RlZg==", "OY//C4rhfwNxCQAQCrQQ1Q==", "bTBANVpaOUw0ampRWG43TVJFcF5iXjdJ", "FP7qKJzdJOGkzoQzo2wTmA==",
                    "nhNhwZ6X7xzgXnnZBxWFQLwCGQtJojL3", "LEGEND-CAMPUS-CIPHERKEY==", "r0e3c16IdVkouZgk1TKVMg==", "ZWvohmPdUsAWT3=KpPqda", "k3+XHEg6D8tb2mGm7VJ3nQ==", "U3ByaW5nQmxhZGUAAAAAAA==", "tiVV6g3uZBGfgshesAQbjA==", "ZAvph3dsQs0FSL3SDFAdag==", "0AvVhmFLUs0KTA3Kprsdag==", "25BsmdYwjnfcWmnhAciDDg==",
                    "3JvYhmBLUs0ETA5Kprsdag==", "5AvVhmFLUs0KTA3Kprsdag==", "6AvVhmFLUs0KTA3Kprsdag==", "6NfXkC7YVCV5DASIrEm1Rg==", "cmVtZW1iZXJNZQAAAAAAAA==", "8AvVhmFLUs0KTA3Kprsdag==", "8BvVhmFLUs0KTA3Kprsdag==", "9AvVhmFLUs0KTA3Kprsdag==", "OUHYQzxQ/W9e/UjiAGu6rg==", "aU1pcmFjbGVpTWlyYWNsZQ==",
                    "bXRvbnMAAAAAAAAAAAAAAA==", "5J7bIJIV0LQSN3c9LPitBQ==", "bya2HkYo57u6fWh5theAWw==", "f/SY5TIve5WWzT4aQlABJA==", "WuB+y2gcHRnY2Lg9+Aqmqg==", "3qDVdLawoIr1xFd6ietnwg==", "YI1+nBV//m7ELrIyDHm6DQ==", "6Zm+6I2j5Y+R5aS+5ZOlAA==", "2A2V+RFLUs+eTA3Kpr+dag==", "6ZmI6I2j3Y+R1aSn5BOlAA==",
                    "SkZpbmFsQmxhZGUAAAAAAA==", "2cVtiE83c4lIrELJwKGJUw==", "fsHspZw/92PrS3XrPW+vxw==", "XTx6CKLo/SdSgub+OPHSrw==", "sHdIjUN6tzhl8xZMG3ULCQ==", "O4pdf+7e+mZe8NyxMTPJmQ==", "HWrBltGvEZc14h9VpMvZWw==", "rPNqM6uKFCyaL10AK51UkQ==", "Y1JxNSPXVwMkyvES/kJGeQ==", "lT2UvDUmQwewm6mMoiw4Ig==",
                    "MPdCMZ9urzEA50JDlDYYDg==", "xVmmoltfpb8tTceuT5R7Bw==", "c+3hFGPjbgzGdrC+MHgoRQ==", "ClLk69oNcA3m+s0jIMIkpg==", "Bf7MfkNR0axGGptozrebag==", "1tC/xrDYs8ey+sa3emtiYw==", "ZmFsYWRvLnh5ei5zaGlybw==", "cGhyYWNrY3RmREUhfiMkZA==", "IduElDUpDDXE677ZkhhKnQ==", "yeAAo1E8BOeAYfBlm4NG9Q==",
                    "cGljYXMAAAAAAAAAAAAAAA==", "2itfW92XazYRi5ltW0M2yA==", "XgGkgqGqYrix9lI6vxcrRw==", "ertVhmFLUs0KTA3Kprsdag==", "5AvVhmFLUS0ATA4Kprsdag==", "s0KTA3mFLUprK4AvVhsdag==", "hBlzKg78ajaZuTE0VLzDDg==", "9FvVhtFLUs0KnA3Kprsdyg==", "d2ViUmVtZW1iZXJNZUtleQ==", "yNeUgSzL/CfiWw1GALg6Ag==",
                    "NGk/3cQ6F5/UNPRh8LpMIg==", "4BvVhmFLUs0KTA3Kprsdag==", "MzVeSkYyWTI2OFVLZjRzZg==", "CrownKey==a12d/dakdad", "empodDEyMwAAAAAAAAAAAA==", "A7UzJgh1+EWj5oBFi+mSgw==", "c2hpcm9fYmF0aXMzMgAAAA==", "i45FVt72K2kLgvFrJtoZRw==", "66v1O8keKNV3TTcGPK1wzg==", "U3BAbW5nQmxhZGUAAAAAAA==",
                    "ZnJlc2h6Y24xMjM0NTY3OA==", "Jt3C93kMR9D5e8QzwfsiMw==", "MTIzNDU2NzgxMjM0NTY3OA==", "vXP33AonIp9bFwGl7aT7rA==", "V2hhdCBUaGUgSGVsbAAAAA==", "Q01TX0JGTFlLRVlfMjAxOQ==", "Is9zJ3pzNh2cgTHB4ua3+Q==", "SDKOLKn2J1j/2BHjeZwAoQ==", "NsZXjXVklWPZwOfkvk6kUA==", "GAevYnznvgNCURavBhCr1w=="};
            for (int i = 0; i < keys.length; i++) {
                String rememberMe = shiroEncrypt(keys[i], exp);
                IParameter newParameter = helpers.buildParameter("rememberMe", rememberMe, (byte) 2);
                byte[] newRequest = helpers.updateParameter(baseRequestResponse.getRequest(), newParameter);
                IHttpService httpService = baseRequestResponse.getHttpService();
                IHttpRequestResponse newIHttpRequestResponse = callbacks.makeHttpRequest(httpService, newRequest);
                byte[] response = newIHttpRequestResponse.getResponse();
                if (getRememberMeNumber(response) < num) {
                    return Arrays.asList("[+] Found Shiro Key:" + keys[i],newIHttpRequestResponse);
                }
            }
        } catch (Exception e) {
            stdout.println(e);
        }
        return Arrays.asList("[-] Not Found Shiro Key...", baseRequestResponse);
    }

 

  这里的检测逻辑还是容易看懂的

1.构造一个继承 PrincipalCollection 的序列化对象。
2.key正确情况下不返回 deleteMe ,key错误情况下返回 deleteMe 。

  所以会有这个逻辑

  

  key检测参考:http://www.lmxspace.com/2020/08/24/一种另类的shiro检测方式/

  核心逻辑到此结束。

  再往下就是GUI自定义Table和显示数据存储,直接copy即可。

  

  数据存储:

  

  总结:

1.GUI可以抄
2.修改参数->重放->生成新的数据 (buildParameter,updateParameter,makeHttpRequest,getResponse)

 

分析burp插件2:https://github.com/YoDiDi/cors-jsonpz

  这里不看GUI了,GUI代码直接copy即可。看核心逻辑怎么写的

  首先是继承:

extends AbstractTableModel implements IBurpExtender, IScannerCheck, ITab, IMessageEditorController

 

  看下GUI定义类:

 

  发现GUI声明类几乎一致。说明作者有参考另一个作者的代码

  因为是检测jsonp,那么稍微要比shiro检测复杂一些,shiro检测的话,cookie里面输入rememberMe即可,而jsonp检测,肯定要处理下请求后缀:

  这里的作者是获取请求头的第一行:

 

 

        String firstrequest_header = request_header.get(0); //第一行请求

        if(firstrequest_header.contains(".png") || firstrequest_header.contains(".js") || firstrequest_header.contains(".jpg") || firstrequest_header.contains(".jpeg") || firstrequest_header.contains(".svg")  || firstrequest_header.contains(".mp4") || firstrequest_header.contains(".css") || firstrequest_header.contains(".mp3")        ){
            return null;
        }

else里面的逻辑

            String[] firstheaders = firstrequest_header.split(" ");
            if(firstheaders[1].contains("callback="))
                firstheaders[1] = firstheaders[1].replace("callback=","callback=testjsonp"); // 原始请求含有callback,直接替换
            else {
                if (firstheaders[1].endsWith("?"))
                    firstheaders[1] = firstheaders[1] + "callback=testjsonp"; // 含有参数的项,?结尾
                else if (firstheaders[1].contains("?") && !firstheaders[1].endsWith("?"))
                    firstheaders[1] = firstheaders[1] + "&callback=testjsonp"; // 含有参数的项,含有?且不是?结尾
                else
                    firstheaders[1] = firstheaders[1] + "?callback=testjsonp"; // 含有参数的项,直接参数后面加callback参数
            }
                        //重新设置header,封装到request_header中
                        request_header.set(0,firstheaders[0] + " " + firstheaders[1] + " " + firstheaders[2]);

  

  这里作者的注释写的很清楚了。

  三种情况

?callback=* 替换成 callback=testjsonp
?a=1&callback=testjsonp
?callback=testjsonp

  简单解释代码含义:

  以空格分割 

String firstrequest_header = request_header.get(0);
String[] firstheaders = firstrequest_header.split(" ");

 

GET /ssrf.php?a=1 HTTP/1.1

 

  

 

 

  按照burpsuite的显示方式,firstheaders[1]一定是路径,对路径进行判断即可。

  因为他又做了cors劫持的检测:

            // 去除源请求包里的Origin参数
            /*****************删除header**********************/
            request_header.removeIf(header -> header.startsWith("Origin"));
            request_header.add("Origin: baitdu.com"); // 请求头增加

 

    

 

  注释写的很清楚。这块代码,自己在平时开发的时候,也能用得上。

  修改了headers,删除了原有的请求头,修改成了新的唯一性的Origin,下面就是具体的逻辑判断

  正常的获取body方法,这段代码可以在https://github.com/bit4woo/burp-api-drops中找到:

  

   作者这里采用了获取body方法1

  

 

  再往下是核心逻辑:重新构建整个请求header

    String reqMethod = this.helpers.analyzeRequest(baseRequestResponse).getMethod();
    //stdout.println(newParameter);
    ////构建一个请求、响应消息,常用于拦截请求并修改其中的参数时会使用
    byte[] newRequest = this.helpers.buildHttpMessage(request_header, request_bodys);
    IHttpService httpService = baseRequestResponse.getHttpService();
    IHttpRequestResponse newIHttpRequestResponse = this.callbacks.makeHttpRequest(httpService, newRequest);
    byte[] response = newIHttpRequestResponse.getResponse();
    IResponseInfo analyzedResponse = helpers.analyzeResponse(response);

 

  buildHttpMessage然后makeHttpRequest:

  此方法生成包含指定标头和消息体的 HTTP 消息。如果适用,将根据主体的长度添加或更新 Content-Length 标头

  

 

 

 

  

 

 

 

  可参考:

https://portswigger.net/burp/extender/api/burp/iextensionhelpers.html

https://portswigger.net/burp/extender/api/burp/iburpextendercallbacks.html

 修改重建请求后,就是做判断逻辑:

 定义了三个变量

 

int IsCorsControl = 0;
int IsCorstrue = 0;
int IsJsonp = 0;

 

  然后遍历请求headers,符合这些条件就变量就自增

 

  通过这段代码,我们就能知道了cors检测的逻辑,这里不多说了,代码写的很清楚了。

  继续往下看

  如果变量>0,就在GUI中输出信息

this.Udatas.add(new TablesData(row, reqMethod, url.toString(), this.helpers.analyzeResponse(response).getStatusCode() + "", "Cors vuln  " + corsword, newIHttpRequestResponse, httpService.getHost(), httpService.getPort()));

 

  其实他可以直接输出结果的,但是他做了一层标识符的操作

  这层标识符的深意:jsonp劫持和cors劫持误报很多。只有存在敏感信息的response,才有价值。

    

 

    这里的检测逻辑是这样的

1.替换header,让特殊变量自增
2.拿到特殊的自增变量,二次判断输出,输出的是必须有敏感标识符的jsonp和cors劫持

 

    

 

  如果IsJsonp>1,说明是存在jsonp劫持的:

  

  这个插件的判断劣势如下:

1.存在callback,但是不一定存在jsonp劫持,可以说存在jsonp
2.如果要jsonp劫持的话,需要删除referer
3.需要判断请求header[1]中是否存在csrf_token等字段
4.最后的标识符判断可以修改成regexp正则匹配
。。。。

 

  自己动手写一个简单的漏洞检测程序,案例如下:

    web缓存xfh头攻击

  

 

  原理很简单,请求头插入X-Forwarded-Host: asdasd.com ,如果response中包含asdasd.com即存在web cache xfh头注入

      来试试

  

 

 

  

 

 

  

  GUI部分直接依葫芦画瓢抄即可。

  完整代码:  

package burp;

import burp.IBurpExtender;
import burp.IBurpExtenderCallbacks;
import burp.IExtensionHelpers;

import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import java.awt.*;
import java.io.PrintWriter;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

public class BurpExtender extends AbstractTableModel implements IBurpExtender, IScannerCheck, ITab, IMessageEditorController{
    private IBurpExtenderCallbacks callbacks;

    private IExtensionHelpers helpers;

    private PrintWriter stdout;

    private JSplitPane mjSplitPane;

    private List<TablesData> Udatas = new ArrayList<>();

    private IMessageEditor HRequestTextEditor;

    private IMessageEditor HResponseTextEditor;

    private IHttpRequestResponse currentlyDisplayedItem;

    private URLTable Utable;

    private JScrollPane UscrollPane;

    private JSplitPane HjSplitPane;

    private JSplitPane HjSplitPane2;

    private JPanel mjPane;

    private JTabbedPane Ltable;

    private JTabbedPane Rtable;

    private JPanel panel1;
    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
        this.callbacks = callbacks;
        this.helpers = callbacks.getHelpers();
        this.stdout = new PrintWriter(callbacks.getStdout(), true);
        callbacks.setExtensionName("cache_vuln_test");
        this.stdout.println("===========================");
        this.stdout.println("[+]   load successful!     ");
        this.stdout.println("[+]   cache vulnerability test v0.1       ");
        this.stdout.println("[+]   code by test     ");
        this.stdout.println("[+]  ");
        this.stdout.println("===========================");
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                mjSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);

                Utable = new URLTable(BurpExtender.this);
                UscrollPane = new JScrollPane(Utable);

                HjSplitPane = new JSplitPane();
                HjSplitPane.setDividerLocation(0.5D);
                Ltable = new JTabbedPane();
                HRequestTextEditor = BurpExtender.this.callbacks.createMessageEditor(BurpExtender.this,false);
                Ltable.addTab("Request",HRequestTextEditor.getComponent());
                Rtable = new JTabbedPane();
                HResponseTextEditor = BurpExtender.this.callbacks.createMessageEditor(BurpExtender.this,false);
                Rtable.addTab("Response",HResponseTextEditor.getComponent());
                HjSplitPane.add(Ltable,"left");
                HjSplitPane.add(Rtable,"right");

                mjSplitPane.add(UscrollPane,"left");
                mjSplitPane.add(HjSplitPane,"right");
                BurpExtender.this.callbacks.customizeUiComponent(mjSplitPane);
                BurpExtender.this.callbacks.addSuiteTab(BurpExtender.this);
            }
        });
        callbacks.registerScannerCheck(this);
    }




    @Override
    public IHttpService getHttpService() {
        return this.currentlyDisplayedItem.getHttpService();
    }

    @Override
    public byte[] getRequest() {
        return this.currentlyDisplayedItem.getRequest();
    }

    @Override
    public byte[] getResponse() {
        return currentlyDisplayedItem.getResponse();
    }

    @Override
    public List<IScanIssue> doPassiveScan(IHttpRequestResponse iHttpRequestResponse) {
        byte[] request = iHttpRequestResponse.getRequest();
        URL url = this.helpers.analyzeRequest(iHttpRequestResponse).getUrl();
        String method = this.helpers.analyzeRequest(iHttpRequestResponse).getMethod();
        IRequestInfo iRequestInfo = this.helpers.analyzeRequest(request);
        List<String> headers = this.helpers.analyzeRequest(request).getHeaders();
        //删除header
        headers.removeIf(header -> header.startsWith("X-Forwarded-Host"));
        //新增一个header头
        headers.add("X-Forwarded-Host: asdasd.com");
        int bodyOffset = iRequestInfo.getBodyOffset();
        byte[] byte_Request = iHttpRequestResponse.getRequest();
        String request2 = new String(byte_Request);
        String body = request2.substring(bodyOffset);
        byte[] byte_body = body.getBytes();

        byte[] newRequest = this.helpers.buildHttpMessage(headers, byte_body);
        IHttpService httpService = iHttpRequestResponse.getHttpService();
        IHttpRequestResponse newIHttpRequestResponse = this.callbacks.makeHttpRequest(httpService, newRequest);
        byte[] response = newIHttpRequestResponse.getResponse();
        String string_response = new String(response);
        this.stdout.println(string_response);
        if(string_response.contains("asdasd.com")){
            synchronized (this.Udatas) {
                int row = this.Udatas.size();
                this.Udatas.add(new TablesData(row, method, url.toString(), this.helpers.analyzeResponse(response).getStatusCode() + "", "cache vuln" , newIHttpRequestResponse));
                fireTableRowsInserted(row, row);
                List<IScanIssue> issues = new ArrayList<>(1);
                return issues;
            }
        }
        return null;
    }

    @Override
    public List<IScanIssue> doActiveScan(IHttpRequestResponse iHttpRequestResponse, IScannerInsertionPoint iScannerInsertionPoint) {
        byte[] request = iHttpRequestResponse.getRequest();

        return null;
    }

    @Override
    public int consolidateDuplicateIssues(IScanIssue existingIssue, IScanIssue newIssue) {
        if (existingIssue.getIssueName().equals(newIssue.getIssueName()))
            return -1;
        else return 0;
    }

    @Override
    public String getTabCaption() {
        return "cache_scan";
    }

    @Override
    public Component getUiComponent() {
        return mjSplitPane;
    }

    @Override
    public int getRowCount() {
        return this.Udatas.size();
    }

    @Override
    public int getColumnCount() {
        return 5;
    }

    public String getColumnName(int columnIndex) {
        switch (columnIndex) {
            case 0:
                return "#";
            case 1:
                return "Method";
            case 2:
                return "URL";
            case 3:
                return "Status";
            case 4:
                return "Issue";
        }
        return null;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        TablesData datas = this.Udatas.get(rowIndex);
        switch (columnIndex) {
            case 0:
                return Integer.valueOf(datas.Id);
            case 1:
                return datas.Method;
            case 2:
                return datas.URL;
            case 3:
                return datas.Status;
            case 4:
                return datas.issue;
        }
        return null;
    }

public class URLTable extends JTable{
    public URLTable(TableModel tableModel) {
        super(tableModel);
    }

    public void changeSelection(int row, int col, boolean toggle, boolean extend) {
        TablesData dataEntry = BurpExtender.this.Udatas.get(convertRowIndexToModel(row));
        HRequestTextEditor.setMessage(dataEntry.requestResponse.getRequest(), true);
        HResponseTextEditor.setMessage(dataEntry.requestResponse.getResponse(),false);
        currentlyDisplayedItem = dataEntry.requestResponse;
        super.changeSelection(row, col, toggle, extend);
    }
}

public static class TablesData {
    final int Id;
    final String Method;
    final String URL;
    final String Status;
    final String issue;
    final IHttpRequestResponse requestResponse;
//        final String host;
//        final int port;

    public TablesData(int id, String method, String url, String status, String issue,IHttpRequestResponse requestResponse) {
        this.Id = id;
        this.Method = method;
        this.URL = url;
        this.Status = status;
        this.issue = issue;
        this.requestResponse = requestResponse;
//            this.host = host;
//            this.port = port;
    }
    }
}

  演示运行效果:

  测试demo:http://119.45.227.86/header.php

  

 

  抓包访问,查看GUI:

    

 

  至此,本人先分享到这里。

    此文只能算burpsuite插件入门文章,想要更深入的学习,了解burpsuite插件开发,可以移步到:https://github.com/pmiaowu  进行深层次的学习,参考其他人写的burpsuite插件也可以。   

 

 

 

 

 

posted @ 2022-11-08 15:16  飘渺红尘✨  阅读(3799)  评论(0编辑  收藏  举报
Title