打造高度自定义的渗透工具-Burp插件开发(一)

2020-01-09 约 3022 字 预计阅读 15 分钟

声明:本文 【打造高度自定义的渗透工具-Burp插件开发(一)】 由作者 p1g3 于 2020-01-09 09:40:57 首发 先知社区 曾经 浏览数 260 次

感谢 p1g3 的辛苦付出!

Burp提供了高度可拓展性,很多我们想实现的功能都可以以插件的形式实现,之前也是因为不懂写插件很多自己的想法无法自动化实现,所以学了一波Burp插件开发,此系列有几篇我也不清楚,随缘写吧。

Burp Extenstion API

前言

本文会略过一些简单步骤如安装插件,安装Jython环境等。

Burp插件提供了几种语言的开发方式:Python、Java、Ruby等,以下文中我会以Python为例子来讲解某个插件该如何使用。

Burp提供了API的介绍文档:Generated Documentation

并且在官方中也有提供例子供我们学习:Burp Suite Extensibility - PortSwigger

此例中提供了我们几个样例,并且给了三种语言的对应版本,我的个人建议是想实现什么功能,就使用某个案例,通过阅读其代码也能慢慢的了解Burp插件的API是如何实现其功能的,如果这些例子中找不到你想实现的功能,不妨去github搜搜别人写好的插件,通过阅读对应的代码也可以了解其某些功能的实现。

介绍

Burp的接口集合:

每个接口都会实现特定的功能,我通过阅读其使用案例的方式来学习每个接口的作用。

IBurpCollaboratorClientContext

在了解此接口之前,我们可以先了解一下Burp的CollaboratorClient这个功能,相当于一个dnslog,我们可以生成某个地址,当我们访问这个地址时,Burp就会将对应请求输出给我们。

当我们点击Copy to clipboard后,就可以拿到一个burp生成的请求地址,当我们访问他之后会输出一个字符串,并且会Burp会收到请求,此时我们如果点击Poll now就可以看到请求了:

Burp收到的请求,会返回响应以及返回包。

接下来了解一下实现这个功能的接口吧,此接口可实现如下方法:

并且由IBurpExtenderCallbacks.createBurpCollaboratorClientContext()创建。

看到这是不是很懵逼?问题不大,我们一个个使用对应的方法,走一遍对应的流程就可以了。

  • generatePayload(boolean includeCollaboratorServerLocation)

IBurpCollaboratorClientContext.py

from burp import IBurpExtender
from burp import IBurpCollaboratorClientContext

class BurpExtender(IBurpExtender,IBurpCollaboratorClientContext):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        callbacks.setExtensionName('IBurpCollaboratorClientContext')
        collaboratorContext = callbacks.createBurpCollaboratorClientContext()
        print(collaboratorContext.generatePayload(True))
        print(collaboratorContext.generatePayload(True))

此方法需要传递一个布尔值,由你传递的值为True或False决定了返回的payload是否带有CollaboratorServerLocation。

我们先看一下上述代码在Output中的输出结果:

再看看当传递的布尔值为False时的输出结果:

可以看到,结果只是是否返回.burpcollaborator.net的区别而已。

  • getCollaboratorServerLocation()

返回Collaborator server的hostname或ip。

一样的,输出出来看看结果:

from burp import IBurpExtender
from burp import IBurpCollaboratorClientContext

class BurpExtender(IBurpExtender,IBurpCollaboratorClientContext):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        callbacks.setExtensionName('IBurpCollaboratorClientContext')
        collaboratorContext = callbacks.createBurpCollaboratorClientContext()
        print(collaboratorContext.getCollaboratorServerLocation())

  • fetchAllCollaboratorInteractions()
from burp import IBurpExtender
from burp import IBurpCollaboratorClientContext
import os

class BurpExtender(IBurpExtender,IBurpCollaboratorClientContext):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        callbacks.setExtensionName('IBurpCollaboratorClientContext')
        collaboratorContext = callbacks.createBurpCollaboratorClientContext()
        payload = collaboratorContext.generatePayload(True)
        print(payload)
        while True:
            print(collaboratorContext.fetchAllCollaboratorInteractions())

此时我为了保证能收到我手动访问的请求,使用了While循环来输出结果:

我使用generatePayload方法生成了一个payload,并在浏览器中手动访问他,过了一会儿之后就可以收到请求了,而此时我们可以使用fetchAllCollaboratorInteractions方法来获取所有payload的请求结果。

  • fetchCollaboratorInteractionsFor(payload)

此方法需要传递某个payload,而这个payload正是我们上文中利用generatePayload方法生成的payload,服务器将检索该payload所处的dns服务器是否收到请求,并将结果返回给我们。

from burp import IBurpExtender
from burp import IBurpCollaboratorClientContext
import os

class BurpExtender(IBurpExtender,IBurpCollaboratorClientContext):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        callbacks.setExtensionName('IBurpCollaboratorClientContext')
        collaboratorContext = callbacks.createBurpCollaboratorClientContext()
        payload = collaboratorContext.generatePayload(True)
        print(payload)
        while True:
            print(collaboratorContext.fetchCollaboratorInteractionsFor(payload))

当我手动访问其生成的payload之后,就可以获取到请求结果:

可以看到此方法与上述方法的区别吗?其区别在于一个是检索单个payload,另外一个是检索在插件运行中生成的所有payload是否被请求。

fetchInfiltratorInteractionsFor与fetchAllInfiltratorInteractions不常用,不予介绍。

学习完了三个主要方法之后,我们就可以开始了解一下使用这些方法可以做什么了。我们需要先想想,Burp的CollaboratorClient相当于一个dnslog,我们一般都用dnslog来测试什么呢?

BLIND XXE、BLIND SSRF、SQL外带、Blind OS INJECTION等。

接下来以github的某个XXE扫描插件来介绍如何使用此api的三个方法实现一个Blind XXE Scanner。

插件地址:XXEPlugin.java

重点关注以下代码:

public void initXXEPayloads() {
        XXEPayloads.add("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<!DOCTYPE test [\n<!ENTITY % remote SYSTEM \"http://{collaboratorPayload}/\">\n%remote;\n]><test>test</test>");
        XXEPayloads.add("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><!DOCTYPE root PUBLIC \"-//B/A/EN\" \"http://{collaboratorPayload}/\"><root>a0e5c</root>");
        XXEPayloads.add("<?xml version=\"1.0\"?><!DOCTYPE foo [<!ENTITY xxe1 \"dryat\"><!ENTITY xxe2 \"0Uct\"><!ENTITY xxe3 \"333\"><!ENTITY xxe \"&xxe1;&xxe3;&xxe2;\">]><methodCall><methodName>BalanceSimple.CreateOrderOrSubscription</methodName><params><param><value><string>&xxe;test</string></value></param><param>x</params></methodCall>");
        XXEPayloads.add("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE tst SYSTEM \"http://{collaboratorPayload}\">\n<tst></tst>");
    }

    @Override
    public List<IScanIssue> doScan(IHttpRequestResponse baseRequestResponse, IScannerInsertionPoint insertionPoint) {
        IResponseInfo resp = helpers.analyzeResponse(baseRequestResponse.getResponse());
        IRequestInfo req = helpers.analyzeRequest(baseRequestResponse.getRequest());
        if (resp == null | req == null) return null;

        URL url = helpers.analyzeRequest(baseRequestResponse).getUrl();
        if (flags.contains(url.toString())) return null;
        else flags.add(url.toString());

        IBurpCollaboratorClientContext collaboratorContext = callbacks.createBurpCollaboratorClientContext();
        String collaboratorPayload = collaboratorContext.generatePayload(true);
        List<IScanIssue> issues = new ArrayList<>();

        for (String xxe : XXEPayloads) {
            xxe = xxe.replace("{collaboratorPayload}", collaboratorPayload);
            List<String> headers = helpers.analyzeRequest(baseRequestResponse).getHeaders();
            headers.set(0, headers.get(0).replace("GET", "POST"));
            headers.removeIf(header -> header != null && header.toLowerCase().startsWith("content-type:"));
            headers.add("Content-type: application/xml");

            byte[] attackBody = helpers.buildHttpMessage(headers, helpers.stringToBytes(xxe));
            IHttpRequestResponse attackRequestResponse = callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), attackBody);
            List<IBurpCollaboratorInteraction> collaboratorInteractions = collaboratorContext.fetchCollaboratorInteractionsFor(collaboratorPayload);

            if (attackRequestResponse != null && attackRequestResponse.getResponse() != null
                    && collaboratorInteractions != null
                    && (!collaboratorInteractions.isEmpty() || helpers.bytesToString(attackRequestResponse.getResponse()).contains("dryat0Uct333"))) {
                String attackDetails = "XXE processing is enabled at: \n" + helpers.analyzeRequest(attackRequestResponse).getUrl().toString();

                issues.add(new CustomScanIssue(attackRequestResponse.getHttpService(),
                        helpers.analyzeRequest(attackRequestResponse).getUrl(),
                        new IHttpRequestResponse[]{callbacks.applyMarkers(attackRequestResponse, null, null)},
                        attackDetails, ISSUE_TYPE, ISSUE_NAME, SEVERITY, CONFIDENCE,
                        "", "", ""));
            }
        }
        return issues.isEmpty() ? null : issues;
    }
}

52-53行代码实现此接口并使用了generatePayload方法来获取一个payload:

IBurpCollaboratorClientContext collaboratorContext = callbacks.createBurpCollaboratorClientContext();
String collaboratorPayload = collaboratorContext.generatePayload(true);

56-65行为使用生成的payload替换xxe中的标志位后发起请求:

for (String xxe : XXEPayloads) {
            xxe = xxe.replace("{collaboratorPayload}", collaboratorPayload);
            List<String> headers = helpers.analyzeRequest(baseRequestResponse).getHeaders();
            headers.set(0, headers.get(0).replace("GET", "POST"));
            headers.removeIf(header -> header != null && header.toLowerCase().startsWith("content-type:"));
            headers.add("Content-type: application/xml");

            byte[] attackBody = helpers.buildHttpMessage(headers, helpers.stringToBytes(xxe));
            IHttpRequestResponse attackRequestResponse = callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), attackBody);
            List<IBurpCollaboratorInteraction> collaboratorInteractions = collaboratorContext.fetchCollaboratorInteractionsFor(collaboratorPayload);

最后一行获取了解析结果,即是否有服务器向payload所处地址发起请求。

并在67-69行代码处判断了是否存在xxe漏洞:

if (attackRequestResponse != null && attackRequestResponse.getResponse() != null
                    && collaboratorInteractions != null
                    && (!collaboratorInteractions.isEmpty())

重点:collaboratorInteractions!=null&&(!collaboratorInteractions.isEmpty())

此行代码用于判断是否存在请求。

通过以上三个步骤即实现了一个简单的blind xxe scanner。

梳理一下具体步骤:

  • 生成payload
  • 替换payload并发起请求
  • 判断是否有服务器向payload所处地址发起请求,如果有则代表漏洞存在

通过此接口,我们实现了一个Blind XXE漏洞扫描器的功能,当然同理也可以实现Blind SSRF等各种Blind漏洞。

IBurpCollaboratorInteraction

留坑...没看懂这玩意干啥用的。

IBurpExtender

Burp明确定义了:所有插件都必须实现这个接口,就已经说明了这个接口的重要性。

  • registerExtenderCallbacks(IBurpExtenderCallbacks callbacks)

当我们在加载插件时,默认会调用IBurpExtender类下的registerExtenderCallbacks方法,并传递一个IBurpExtenderCallbacks对象,此对象在编写插件时会经常用到。

官方描述该方法的功能:它注册该IBurpExtenderCallbacks接口的一个实例 ,提供可由扩展调用的方法以执行各种操作。

IBurpExtender.py:

class BurpExtender(IBurpExtender,IBurpCollaboratorInteraction):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        callbacks.setExtensionName('IBurpExtender')

上述代码中的callbacks是加载插件时默认传递的,其为IBurpExtenderCallbacks的实例,这个在后续会讲到。

IBurpExtenderCallbacks

在上面讲到了,加载插件时默认会调用registerExtenderCallbacks方法并传递一个实例,而这个实例就是IBurpExtenderCallbacks对象的实例。

这个接口就非常之牛逼了,其内置了许多成员属性以及方法,在后续我们都会经常用到。

先看看其内置的属性吧:

public static final int TOOL_COMPARER   512
public static final int TOOL_DECODER    256
public static final int TOOL_EXTENDER   1024
public static final int TOOL_INTRUDER   32
public static final int TOOL_PROXY  4
public static final int TOOL_REPEATER   64
public static final int TOOL_SCANNER    16
public static final int TOOL_SEQUENCER  128
public static final int TOOL_SPIDER 8
public static final int TOOL_SUITE  1
public static final int TOOL_TARGET 2

这一块属性其实都是一些标志位,当我们在处理Burp传递过来的http请求时,需要判断这些请求是在哪里传递的,比如PROXY、SCANNER、TARGET、REPEATER等,简单的来说,这些属性的功能就是用来判断我们想要处理的请求能不能正确的处理,比如我们想要处理的是REPEATER的请求,此时其余无关请求就可以抛掉,比如PROXY的请求,下面以一个代码来演示一下吧。

IBurpExtenderCallbacks.py:

from burp import IBurpExtender
from burp import IHttpListener

class BurpExtender(IBurpExtender,IHttpListener):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        callbacks.setExtensionName('IBurpExtenderCallbacks')
        callbacks.registerHttpListener(self)

    def processHttpMessage(self, toolFlag, messageIsRequest, messageinfo):
        if toolFlag == self._callbacks.TOOL_REPEATER :
            print("you are using repeater")

此时当我们在repeater中发出请求时,就可以在output下看到如下输出:

接下来开始介绍一下该接口下的方法,其方法有点多,我就不截图了,可以在以下地址中查看:

Interface IBurpExtenderCallbacks

addScanIssue(IScanIssue issue)

此方法要求我们传入IScanIssue的实例化,用于生成自定义的扫描问题,就像是我们在主页中看到的各种漏洞一样,直接演示一下:

from burp import IBurpExtender
from burp import IHttpListener
from burp import IScanIssue

class CustomIssue(IScanIssue):
    def __init__(self, BasePair, Confidence='Certain', IssueBackground=None, IssueDetail=None, IssueName='Python Scripter generated issue', RemediationBackground=None, RemediationDetail=None, Severity='High'):
        self.HttpMessages=[BasePair]
        self.HttpService=BasePair.getHttpService()
        self.Url=BasePair.getUrl() 
        self.Confidence = Confidence
        self.IssueBackground = IssueBackground 
        self.IssueDetail = IssueDetail
        self.IssueName = IssueName
        self.IssueType = 134217728 
        self.RemediationBackground = RemediationBackground 
        self.RemediationDetail = RemediationDetail 
        self.Severity = Severity 

    def getHttpMessages(self):
        return self.HttpMessages

    def getHttpService(self):
        return self.HttpService

    def getUrl(self):
        return self.Url

    def getConfidence(self):
        return self.Confidence

    def getIssueBackground(self):
        return self.IssueBackground

    def getIssueDetail(self):
        return self.IssueDetail

    def getIssueName(self):
        return self.IssueName

    def getIssueType(self):
        return self.IssueType

    def getRemediationBackground(self):
        return self.RemediationBackground

    def getRemediationDetail(self):
        return self.RemediationDetail

    def getSeverity(self):
        return self.Severity

class BurpExtender(IBurpExtender,IHttpListener):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        callbacks.setExtensionName('IBurpExtenderCallbacks')
        callbacks.registerHttpListener(self)

    def processHttpMessage(self, toolFlag, messageIsRequest, messageinfo):
        if toolFlag == self._callbacks.TOOL_REPEATER :
            if messageIsRequest: 
                issue = CustomIssue(
                    BasePair=messageinfo,
                    IssueName='HTTP REQUESTS',
                    IssueDetail='addScanIssue Testing',
                    Severity='High',
                    Confidence='Certain'
                )
                self._callbacks.addScanIssue(issue)
        return

此时当我们在repeater中发出请求时,主页便会多了一个我们自定义的扫描问题:

漏洞详情以及危害等级都是我们可以自定义的,此方法多用于在测试漏洞时,如果不想添加tab来显示存在漏洞的地址,则使用addScanIssue方法来添加一个自定义问题。

  • addSuiteTab

该接口实现创建一个自定义选项卡到Burp的选项栏中,默认我们的输出只会在插件的output处输出,如果想输出在界面中,就需要实现此接口。

这方面涉及到java的图形化开发,其实和burp就没多大关系了,不懂也没关系,我们照葫芦画瓢就可以了。

在实现这块之前,请读者先思考一下,我们实现一个图形化的界面有几种目的,在这里我先说一下我的个人观点。

1.实现插件与用户交互
2.更好的显示漏洞细节

其实实现图形化无非就这两点,所以我们现在来尝试实现这两个功能。

以Burp的官方案例CustomLogger为例:custom-logger

整个插件实现起来的效果如下:

其实就是展示了我们的数据包是从burp的哪个模块发出去的,并且显示出数据包的详细内容。

如果我们想改改,将此处整成展示漏洞详情的页面,需要怎么改呢?

要实现这个功能,我们需要修改几个地方。

1.将Tool修改为param(即存在漏洞的参数)
2.只将存在漏洞的页面显示在这里,其他的不显示。

通过全局搜索的方式,我们可以找到定义Tool的地方:

此时我们将TOOL修改为PARAM,就算简单完成了第一步:

def getColumnName(self, columnIndex):
    if columnIndex == 0:
        return "PARAM"
    if columnIndex == 1:
        return "URL"
    return ""

但是我们要注意的是,现在只是修改了其对应的标签名,并没有修改其对应输出的内容,此处输出的内容依旧是toolflag对应的burp组件(如果不知道toolflag是什么,请仔细阅读前文):

如果此处我们想显示为当前url的query,如何做到呢?将想法分割为多个小步,是我实现一个目的的基本办法,所以我们将显示为当前url的query分成如下小步来实现:

1.找到显示此处的位置
2.修改显示内容

第一个问题和第二个问题都很容易解决,我们只需要找到输出此处内容的方法即可:

columnIndex=0对应着PARAM,而columnIndex=1对应着URL,此处稍微修改一下:

def getValueAt(self, rowIndex, columnIndex):
    logEntry = self._log.get(rowIndex)
    url = logEntry._url.toString()
    if urlparse(url).query =='':
        return
    if columnIndex == 0:
        return urlparse(url).query
    if columnIndex == 1:
        return logEntry._url.toString()
    return ""

实现效果:

此时虽然只显示出了带有参数的URL,但是却多了很多空白行,这样看起来很丑,所以我们需要找到是哪个方法调用了getValueAt方法,并修改对应点。

通过全局搜索getValueAt这个关键字,我发现并没有任何地方显式的调用了getValueAt方法:

那么很明显了,此处就是隐式的调用了getValueAt方法:

此处为burp处理请求的方法,而使用者也已经注释的很清楚了,这里就是添加log的地方,由于此处只有一个图形化界面的操作fireTableRowsInserted,所以推测就是此处对表格进行插入的,所以我们只需要在插入之前判断一下url是否带有query即可:

此时重新加载一次插件,就可以看到我们想要的效果了:

以上只是简单的以一个不懂任何图形化界面的新手来完成这些操作,通过此例,我们可以知道即使不会任何图形化界面的开发也不必要,照葫芦画瓢即可,实在不行直接看官方文档,一样是可以实现我们想实现的功能的。

通过以上案例,我们实现了更好的显示漏洞细节,那么如何与用户交互呢,一样的,让我们来照葫芦画瓢。

先思考与用户交互需要什么,无非就是一个输入框,一个确认按钮,程序通过监听按钮来获取用户输入,代入到程序的执行过程中。

以lufe1师傅的xxe检测插件为例:burp插件LFI scanner第二版

先看看图形化实现效果:

此处只说明图形化相关的配置:

public class XxeOption implements ITab, ActionListener {
    JPanel jp;
    JTextField jtfId,jtfToken;
    JButton jb;
    JLabel jlId,jlToken;
    private final IBurpExtenderCallbacks callbacks;


    public XxeOption(final IBurpExtenderCallbacks callbacks) {
        this.callbacks = callbacks;


        jp = new JPanel();


        jlId = new JLabel("Identifier:");
        jlToken = new JLabel("API Token:");
        jtfId = new JTextField(10);
        jtfToken = new JTextField(20);


        //设置Id,Token文本框
        File file = new File("xxe.config");
        if(file.exists()){
            String info = ReadConfig();
            if(info.contains("|"))
            {
                jtfId.setText(info.split("\\|")[0]);
                jtfToken.setText(info.split("\\|")[1]);
            }

        }

        jb = new JButton("保存");
        jb.addActionListener(this);

        jp.add(jlId);
        jp.add(jtfId);
        jp.add(jlToken);
        jp.add(jtfToken);
        jp.add(jb);


        callbacks.customizeUiComponent(jtfToken);
        callbacks.addSuiteTab(XxeOption.this);
    }

    //写入配置
    public void WriteConfig(String data)
    {
        try{

            File file = new File("xxe.config");

            //if file doesnt exists, then create it
            if(!file.exists()){
                file.createNewFile();
            }

            //true = append file
            FileWriter fileWritter = new FileWriter(file.getName(),false);
            BufferedWriter bufferWritter = new BufferedWriter(fileWritter);
            bufferWritter.write(data);
            bufferWritter.close();

        }catch(IOException e){

            e.printStackTrace();
        }

    }

    //读取配置
    public String ReadConfig()
    {
        StringBuilder result = new StringBuilder();
        try{
            BufferedReader br = new BufferedReader(new FileReader("xxe.config"));//构造一个BufferedReader类来读取文件
            String s = null;
            while((s = br.readLine())!=null){//使用readLine方法,一次读一行
                result.append(s);
            }
            br.close();
        }catch(Exception e){
            e.printStackTrace();
        }
        return result.toString();
    }



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

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

    @Override
    public void actionPerformed(ActionEvent e) {
        if((jtfId.getText() != "") && (jtfToken.getText() != ""))
        {
            String path = "";
            File directory  = new File(".");
            try {
                path = directory.getCanonicalPath() + "\\xxe.config";
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            WriteConfig(jtfId.getText() + "|" +jtfToken.getText());
        }else {
            JOptionPane.showMessageDialog(jp, "ID 和 Token不能为空", "提示",JOptionPane.WARNING_MESSAGE);
        }


    }
}

此处实现了两个接口:ITab, ActionListener

重点在XxeOption这个方法:

public XxeOption(final IBurpExtenderCallbacks callbacks) {
        this.callbacks = callbacks;


        jp = new JPanel();


        jlId = new JLabel("Identifier:");
        jlToken = new JLabel("API Token:");
        jtfId = new JTextField(10);
        jtfToken = new JTextField(20);


        //设置Id,Token文本框
        File file = new File("xxe.config");
        if(file.exists()){
            String info = ReadConfig();
            if(info.contains("|"))
            {
                jtfId.setText(info.split("\\|")[0]);
                jtfToken.setText(info.split("\\|")[1]);
            }

        }

        jb = new JButton("保存");
        jb.addActionListener(this);

        jp.add(jlId);
        jp.add(jtfId);
        jp.add(jlToken);
        jp.add(jtfToken);
        jp.add(jb);


        callbacks.customizeUiComponent(jtfToken);
        callbacks.addSuiteTab(XxeOption.this);
    }

    //写入配置
    public void WriteConfig(String data)
    {
        try{

            File file = new File("xxe.config");

            //if file doesnt exists, then create it
            if(!file.exists()){
                file.createNewFile();
            }

            //true = append file
            FileWriter fileWritter = new FileWriter(file.getName(),false);
            BufferedWriter bufferWritter = new BufferedWriter(fileWritter);
            bufferWritter.write(data);
            bufferWritter.close();

        }catch(IOException e){

            e.printStackTrace();
        }

    }

先是new了一个JPanel类,24-27行设置了文本内容以及文本框长度:

jlId = new JLabel("Identifier:");
jlToken = new JLabel("API Token:");
jtfId = new JTextField(10);
jtfToken = new JTextField(20);

JLabel传入一个字符串代表创建一个标签,标签的名称为传入的字符串,JTextField(10)代表长度为10的文本框。

31-40行用于判断有无历史配置文件,如果有则自动载入配置文件中的内容到文本框中:

File file = new File("xxe.config");
if(file.exists()){
    String info = ReadConfig();
    if(info.contains("|"))
    {
        jtfId.setText(info.split("\\|")[0]);
        jtfToken.setText(info.split("\\|")[1]);
    }

}

42-43行用于创建一个按钮,并监听此按钮的事件:

jb = new JButton("保存");
jb.addActionListener(this);

当点击此按钮后,会触发actionPerformed方法,在110-128行重写了此方法:

@Override
    public void actionPerformed(ActionEvent e) {
        if((jtfId.getText() != "") && (jtfToken.getText() != ""))
        {
            String path = "";
            File directory  = new File(".");
            try {
                path = directory.getCanonicalPath() + "\\xxe.config";
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            WriteConfig(jtfId.getText() + "|" +jtfToken.getText());
        }else {
            JOptionPane.showMessageDialog(jp, "ID 和 Token不能为空", "提示",JOptionPane.WARNING_MESSAGE);
        }


    }
}

45-47行添加了组件:

jp.add(jlId);
jp.add(jtfId);
jp.add(jlToken);
jp.add(jtfToken);
jp.add(jb);

53行代码在burp中创建了一个自定义选项卡:

callbacks.addSuiteTab(XxeOption.this);

以上代码实现了所有图形化的界面,那么我们在插件代码中如何与用户交互?

来到插件入口处:BurpExtender.java

在43行代码中实例化了XxeOption这个类:

xxeOption = new XxeOption(callbacks);

并在被动扫描插件处调用了他:

byte[] xxePayload = ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<!DOCTYPE root [\n" +
                "<!ENTITY % remote SYSTEM \"http://" + xxeOption.jtfId.getText() + "/" + flag + "\">\n" +
                "%remote;]>\n" +
                "<root/>").getBytes();

以上代码实现了与用户传递进行交互的功能。

  • removeSuiteTab

从字面意思理解,addSuiterTab用于添加自定义选项卡,那么removeSuiteTab就是用于删除自定义选项卡了,这功能基本用不到,不作讲解。

大概到用途就是给一个按钮,比如一个关闭选项卡的按钮,点击后触发removeSuiteTab方法并传日当前的ITab实例化对象。

  • customizeUiComponent

这个方法不需要实现,可以用ITab.getUiComponent来代替,没怎么用过emmm。

  • addToSiteMap

简而言之,此方法就是用于将某个数据包添加到burp的sitemap中的。

参考代码:

from burp import IBurpExtender
from burp import IHttpListener
from burp import IBurpExtenderCallbacks

class BurpExtender(IBurpExtender, IHttpListener, IBurpExtenderCallbacks):

    def registerExtenderCallbacks( self, callbacks):
        self._helpers = callbacks.getHelpers()

        self._callbacks = callbacks

        # set our extension name
        callbacks.setExtensionName("Repeater to Sitemap")
        callbacks.registerHttpListener(self)
        return

    def  processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
        if toolFlag == 64: #Repeater
            if messageIsRequest == False:
                self._callbacks.addToSiteMap(messageInfo)

上述代码的功能是将repeater中的数据包添加到sitemap中:

当我们在repeater中发送一个数据包后,就可以在sitemap中看到:

  • applyMarkers

这个方法是用来标记返回包中敏感信息的,一般搭配IScanIssue使用。

给个demo来理解一下:

from burp import IBurpExtender
from burp import IHttpListener
from burp import IBurpExtenderCallbacks
from burp import IScanIssue,IScannerCheck,IScannerInsertionPoint
from array import array

class BurpExtender(IBurpExtender, IHttpListener, IBurpExtenderCallbacks,IScannerCheck,IScannerInsertionPoint):

    def registerExtenderCallbacks( self, callbacks):
        self._helpers = callbacks.getHelpers()

        self._callbacks = callbacks

        # set our extension name
        callbacks.setExtensionName("Repeater to Sitemap")
        callbacks.registerScannerCheck(self)
        return

    def getMatches(self, response, match):
      '''This finds our pattern match in the request/response and returns an int array'''
      start = 0
      count = 0
      matches = [array('i')]
      while start < len(response):
        start=self._helpers.indexOf(response, match, True, start, len(response))
        if start == -1:
          break
        try:
          matches[count]
        except:
          matches.append(array('i'))
        matches[count].append(start)
        matches[count].append(start+len(match))
        start += len(match)
        count += 1

      return matches

    def doPassiveScan(self, baseRequestResponse):
        PATTERN="phpinfo"
        ISSUE_NAME="phpinfo found in HTTP Response"
        ISSUE_DETAIL="HTTP Response contains this pattern: " + PATTERN
        ISSUE_BACKGROUND="The web site has exposed sensitive information"
        REMEDIATION_BACKGROUND="Sensitive information"
        REMEDIATION_DETAIL="Ensure sensitive information is only shown to authorized users"
        SEVERITY="Information"
        CONFIDENCE="Certain"
        issue = list()
        match = self.getMatches(baseRequestResponse.getResponse(), PATTERN)
        if len(match) > 0:
            httpmsgs = [self._callbacks.applyMarkers(baseRequestResponse,None,match)]
            issue.append(ScanIssue(baseRequestResponse.getHttpService(), self._helpers.analyzeRequest(baseRequestResponse).getUrl(), httpmsgs, ISSUE_NAME, ISSUE_DETAIL, SEVERITY, CONFIDENCE, REMEDIATION_DETAIL, ISSUE_BACKGROUND, REMEDIATION_BACKGROUND))
        return issue
    def doActiveScan(self, baseRequestResponse, insertionPoint):
        pass

class ScanIssue(IScanIssue):
  '''This is our custom IScanIssue class implementation.'''
  def __init__(self, httpService, url, httpMessages, issueName, issueDetail, severity, confidence, remediationDetail, issueBackground, remediationBackground):
      self._issueName = issueName
      self._httpService = httpService
      self._url = url
      self._httpMessages = httpMessages
      self._issueDetail = issueDetail
      self._severity = severity
      self._confidence = confidence
      self._remediationDetail = remediationDetail
      self._issueBackground = issueBackground
      self._remediationBackground = remediationBackground


  def getConfidence(self):
      return self._confidence

  def getHttpMessages(self):
      return self._httpMessages
      #return None

  def getHttpService(self):
      return self._httpService

  def getIssueBackground(self):
      return self._issueBackground

  def getIssueDetail(self):
      return self._issueDetail

  def getIssueName(self):
      return self._issueName

  def getIssueType(self):
      return 0

  def getRemediationBackground(self):
      return self._remediationBackground

  def getRemediationDetail(self):
      return self._remediationDetail

  def getSeverity(self):
      return self._severity

  def getUrl(self):
      return self._url

  def getHost(self):
      return 'localhost'

  def getPort(self):
      return int(80)

参考:CustomScanner.py

该插件的功能是当返回包中含有phpinfo这个字符串时,添加一个Issue并高亮返回包中的phpinfo字符串,效果如下:

当然也可以高亮request中的字符串,参考:

用法:

self._callbacks.applyMarkers(baseRequestResponse,match,None)

当然也可以两个都高亮:

self._callbacks.applyMarkers(baseRequestResponse,match1,match2)
  • createBurpCollaboratorClientContext

这个方法之前介绍过了,用来创建IBurpCollaboratorClientContext实例并生成payload的。

  • createMessageEditor

此方法返回实现了IMessageEditor接口的对象,方便在自己插件的UI中使用,具体使用方法参考上面的图形化界面相关(addSuiteTab)。

  • createTextEditor

和上边那个作用是一样的,只是返回的对象实现的接口不同,所以可调用的方法也有几个不同。

参考:HelloWorldBurpTabExtender.py

  • customizeUiComponent

我的理解是这个插件是用来对自定义组件传入其他任意的自定义子组件,也是用来对UI进行操作的。

相当于一个input标签里还可以定义name属性value属性等这个意思。

  • doActiveScan

此方法用于对传入的数据包进行主动扫描,并返回当前正在扫描的队列。

参考:AutoScanWithBurp.py

该插件的功能是将爬虫的数据包丢到主动扫描模块进行扫描。

  • doPassiveScan

没怎么用过这个方法,不太了解,大概看了一些网上的demo,推测是使用burp来进行转发,将burp代理的流量转发到扫描器上进行被动扫描。

  • excludeFromScope

从burp的scope中排除指定的url。

  • exitSuite

以编程形式关闭burp,传入参数为0和1,代表用户是否选择关闭burp的布尔标志。

  • generateScanReport

将issues以某种格式导出到某个文件,issues是个列表,里边可以存放多个issue。

此方法一般用于导出漏洞扫描报告或问题集合。

  • getCookieJarContents

官方说明是为了处理会话异常,没找到实用的demo,本地测试了一下:

from burp import IBurpExtender,ICookie




class BurpExtender(IBurpExtender,ICookie):

    def registerExtenderCallbacks(self, callbacks):
        self.callbacks = callbacks
        self.helpers = callbacks.getHelpers()
        self.callbacks.setExtensionName('IBurp')
        cookies = self.callbacks.getCookieJarContents()
        for cookie in cookies:
            print(cookie.value)

当然也可以获取cookie所在域,使用cookie.domain即可。

  • getBurpVersion

此方法用于返回Burp相关的信息集合:

  • getCommandLineArguments

此方法返回启动burp时使用的参数列表。

这玩意基本不会用。。

  • getContextMenuFactories

没用过,不予以介绍。

  • getExtensionFilename

此方法返回插件所处的绝对路径:

from burp import IBurpExtender,ICookie




class BurpExtender(IBurpExtender,ICookie):

    def registerExtenderCallbacks(self, callbacks):
        self.callbacks = callbacks
        self.helpers = callbacks.getHelpers()
        self.callbacks.setExtensionName('IBurp')
        print(self.callbacks.getExtensionFilename())

  • getExtensionStateListeners

此方法返回该拓展注册的监听器,什么是监听器一会会讲到,比如上文有一个callbacks.registerHttpListener,这就注册了一个请求&响应监听器。

  • getHelpers

此方法用于获取IExtensionHelpers对象的实例,这个实例很有用,之后对数据包的很多处理都会用到他。

具体的IExtensionHelpers对象可使用的方法可以看这里:Interface IExtensionHelpers

  • getHttpListeners

此方法返回当前注册的HTTP监听器列表。

示例代码;

from burp import IBurpExtender,ICookie,IScannerCheck,IHttpListener




class BurpExtender(IBurpExtender,ICookie,IScannerCheck,IHttpListener):

    def registerExtenderCallbacks(self, callbacks):
        self.callbacks = callbacks
        self.helpers = callbacks.getHelpers()
        self.callbacks.setExtensionName('IBurp')
        callbacks.registerScannerCheck(self)
        callbacks.registerHttpListener(self)
        print(callbacks.getHttpListeners())

    def processHttpMessage(self, toolFlag, messageIsRequest, messageinfo):
        if toolFlag == 4 :
            if messageIsRequest:
                return

    def doActiveScan(self, baseRequestResponse, insertionPoint):
        pass

    def doPassiveScan(self, baseRequestResponse):
        pass

输出:

PS:以下几个方法都是一个意思,不再重复叙述。

添加上一个getProxyListeners()方法。

  • getProxyHistory

此方法返回history中的数据包列表,返回的是IHttpRequestResponse的实例,可调用其中的方法。

  • getScanIssues

此方法返回某个url的issus列表,列表中的每一项为IScanIssue的实例,可调用其中的方法。

示例:

class BurpExtender(IBurpExtender,ICookie,IScannerCheck,IHttpListener):

    def registerExtenderCallbacks(self, callbacks):
        self.callbacks = callbacks
        self.helpers = callbacks.getHelpers()
        self.callbacks.setExtensionName('IBurp')
        callbacks.registerScannerCheck(self)
        callbacks.registerHttpListener(self)
        issue_list = callbacks.getScanIssues('http://ctf.localhost.com')
        for issue in issue_list:
            print(issue.getIssueName())

输出:

其中的issueName是在issue列表中获得的:

  • getSiteMap

此方法用于从sitemap中提取指定url的sitemap子集,官方描述是可以匹配以某个特定字符开头的站点,意思就是可以用正则这样子。

返回是IHttpRequestResponse的实例,可以调用其中的方法。

示例:

class BurpExtender(IBurpExtender,ICookie,IScannerCheck,IHttpListener):

    def registerExtenderCallbacks(self, callbacks):
        self.callbacks = callbacks
        self.helpers = callbacks.getHelpers()
        self.callbacks.setExtensionName('IBurp')
        callbacks.registerScannerCheck(self)
        callbacks.registerHttpListener(self)
        site_requests = callbacks.getSiteMap("http://ctf.localhost.com")
        for request in site_requests:
            print(request.getHttpService())

输出:

  • 四个不懂用的方法

这四个方法是我不知道干啥用的,不予介绍。

  • getToolName

此方法返回toolflag对应的toolname,如果不知道toolflag是啥的回头仔细看看就知道了。

示例:

from burp import IBurpExtender,ICookie,IScannerCheck,IHttpListener




class BurpExtender(IBurpExtender,ICookie,IScannerCheck,IHttpListener):

    def registerExtenderCallbacks(self, callbacks):
        self.callbacks = callbacks
        self.helpers = callbacks.getHelpers()
        self.callbacks.setExtensionName('IBurp')
        callbacks.registerScannerCheck(self)
        callbacks.registerHttpListener(self)
        print(callbacks.getToolName(4))

输出:

  • includeInScope

我的理解是将某个匹配规则添加进scope中。

  • isExtensionBapp

判断当前插件是否已经上架到burp的bapp store里了,如果上架了则返回true,反之返回false。

  • isInScope

查询指定的url规则是否在scope中,一般和includeInScope配套使用。

  • issueAlert

用于在burp的选项卡中输出消息。

示例:

from burp import IBurpExtender,ICookie,IScannerCheck,IHttpListener




class BurpExtender(IBurpExtender,ICookie,IScannerCheck,IHttpListener):

    def registerExtenderCallbacks(self, callbacks):
        self.callbacks = callbacks
        self.helpers = callbacks.getHelpers()
        self.callbacks.setExtensionName('IBurp')
        callbacks.registerScannerCheck(self)
        callbacks.registerHttpListener(self)
        callbacks.issueAlert("test")

输出:

  • makeHttpRequest

这个方法还挺常用的,简单一点来说就是修改数据包了之后本地重新再发一次,一般用在漏洞检测这块。

从上面第一个图也看到了,这个方法有两种用法:

两种方法都是用来重新发包的,只是返回值不同,第一种返回的是IHttpRequestResponse的实例,可以使用getResponse()方法直接获取响应结果,而第二种返回的数据类型为bytes,一般使用该方法获取响应结果:

againRes = self._callbacks.makeHttpRequest(host, port, ishttps, againReq)
link = againReqHeaders[0].split(' ')[1]
host = againReqHeaders[1].split(' ')[1]
analyzedrep = self._helpers.analyzeResponse(againRes)
againResBodys = againRes[analyzedrep.getBodyOffset():].tostring()

这个方法是很重要的,以后会经常用到,不懂的可以多去搜下demo。

  • printError

不怎么用这个方法,感觉就和抛出一个自定义错误一样的。

示例:

from burp import IBurpExtender,ICookie,IScannerCheck,IHttpListener




class BurpExtender(IBurpExtender,ICookie,IScannerCheck,IHttpListener):

    def registerExtenderCallbacks(self, callbacks):
        self.callbacks = callbacks
        self.helpers = callbacks.getHelpers()
        self.callbacks.setExtensionName('IBurp')
        callbacks.registerScannerCheck(self)
        callbacks.registerHttpListener(self)
        callbacks.printError("123")

输出:

  • printOutput

emm,感觉这个方法就是为了代替使用print?

示例:

class BurpExtender(IBurpExtender,ICookie,IScannerCheck,IHttpListener):

    def registerExtenderCallbacks(self, callbacks):
        self.callbacks = callbacks
        self.helpers = callbacks.getHelpers()
        self.callbacks.setExtensionName('IBurp')
        callbacks.registerScannerCheck(self)
        callbacks.registerHttpListener(self)
        callbacks.printOutput("123")

输出:

  • 一系列register

这些registerxxx的方法,我的个人理解就是你注册了某个类,你就可以重写某个类的方法,这些类的方法在使用burp进行某些特定操作时会自动调用。

以registerHttpListener为例:

从上图中可以看出,其注册的实际是IHttpListener这个接口,让我们来看看此接口下的方法:

官方描述是,任何Burp收到或发出的请求以及响应都将触发此方法,所以这个方法也同样可以用来当漏扫,只不过一般没人这么干,因为只有此方法执行完后,浏览器的页面才会返回结果。

示例:

from burp import IBurpExtender,ICookie,IScannerCheck,IHttpListener




class BurpExtender(IBurpExtender,ICookie,IScannerCheck,IHttpListener):

    def registerExtenderCallbacks(self, callbacks):
        self.callbacks = callbacks
        self.helpers = callbacks.getHelpers()
        self.callbacks.setExtensionName('IBurp')
        callbacks.registerHttpListener(self)


    def processHttpMessage(self, toolFlag, messageIsRequest, messageinfo):
        if toolFlag == 4 :
            if messageIsRequest:
                return
            else:
                print("you are loading:  " + messageinfo.getHttpService().toString())

此时当我们用burp代理发出请求时,就会自动调用该方法,输出如下:

  • 一系列remove

这些方法对应着上面的register,即删除插件所注册的某些接口。

字面翻译就可以啦。

  • saveBuffersToTempFiles

将IHttpRequestResponse对象的请求以及响应存储到临时文件中,确保其不存在于内存中。emmm不知道这个的作用是什么。

返回实现了IHttpRequestResponse接口的对象,我们可以调用如下方法:

  • saveConfigAsJson&&loadConfigFromJson

官方描述如上,我也不清楚是怎么使用的,不予介绍。

  • 一系列send

字面意思,就是把某个数据包发送到burp的某个模块里,这里以sendToRepeater为例:

from burp import IBurpExtender,ICookie,IScannerCheck,IHttpListener




class BurpExtender(IBurpExtender,ICookie,IScannerCheck,IHttpListener):

    def registerExtenderCallbacks(self, callbacks):
        self.callbacks = callbacks
        self.helpers = callbacks.getHelpers()
        self.callbacks.setExtensionName('IBurp')
        callbacks.registerHttpListener(self)


    def processHttpMessage(self, toolFlag, messageIsRequest, messageinfo):
        if messageinfo.getHttpService().getProtocol() == 'https://':
            is_https = 1
        else:
            is_https = 0
        headers = list(self.helpers.analyzeRequest(messageinfo).getHeaders())
        newMessage = self.helpers.buildHttpMessage(headers, None)
        self.callbacks.sendToRepeater(messageinfo.getHttpService().getHost(),messageinfo.getHttpService().getPort(),is_https,newMessage,None)

此时当burp代理发出请求或收到响应时,都会调用processHttpMessage,我在里边实现了一个简单的sendToRepeater方法,此时可以看到我的repeater里有巨多请求:

感觉这几个sendtoxxx的方法都需要配合button这样的监听事件来实现。

  • setExtensionName

顾名思义,就是用来设置插件名称的。

示例:

class BurpExtender(IBurpExtender,ICookie,IScannerCheck,IHttpListener):

    def registerExtenderCallbacks(self, callbacks):
        self.callbacks = callbacks
        self.helpers = callbacks.getHelpers()
        self.callbacks.setExtensionName('IBurp')

此时插件的名称会展示在extender-extensions中:

  • setProxyInterceptionEnabled

没用过,不清楚如何使用,本地测试了一下也没搞明白。

  • unloadExtension

此方法用于从拓展中去除当前使用的拓展,即不使用该拓展,也是要配合button使用才有意义的。

  • updateCookieJar

没使用过。


以上即为IBurpExtenderCallbacks接口中实现的所有方法,在写这篇文章的时候,有的方法我也没使用过,写起来很费事,不过如果真的想了解burp插件的一套调用流程,这个接口是你必须要认真去阅读的。

参考:Interface IBurpExtenderCallbacks

IContextMenuFactory

此接口由IBurpExtenderCallbacks.registerContextMenuFactory()注册,其需要实现的方法只有一个,返回的是IContextMenuInvocation的实例,可以调用其中的方法。

这个接口是用来实现菜单与一系列操作联合起来的,具体看个demo:

示例:

class BurpExtender(IBurpExtender, IContextMenuFactory):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers   = callbacks.getHelpers()
        self._context   = None

        # set up the extension
        callbacks.setExtensionName('IBurp')
        callbacks.registerContextMenuFactory(self)

    def createMenuItems(self,invocation):
        menu= []
        menu.append(JMenuItem("Test Menu", None, actionPerformed=self.testmenu()))
        print(invocation.getSelectedMessages())
        return menu

    def testmenu(self):
        return 1

此时我在Burp的上下文菜单中创建了一个名为Test Menu的菜单,我重写他的 createMenuItems方法,此时在Burp的任意地方调用上下文菜单都会触发此方法。

实例效果:

可以看到这里出现了一个我们自定义的菜单选项,一般这个选项是要配合其他的操作来进行的,下面以sahildhar师傅的shodan信息探测插件来讲一下这个api要怎么用。

项目地址:shodanapi.py

在23行注册了此接口:self.callbacks.registerContextMenuFactory(self)

26-29行重写了此方法:

def createMenuItems(self,invocation):
        menu_list = []
        menu_list.append(JMenuItem("Scan with Shodan",None,actionPerformed= lambda x, inv=invocation:self.startThreaded(self.start_scan,inv)))
        return menu_list

此时创建了一个菜单选项名为Scan with Shodan,而actionPerformed是用来指定点击了这个选项后会触发的方法。

此时点击这个菜单选项后会触发start_scan方法并传递invocation为参数进去。

35-48行是核心代码,也就是从shodan获取信息的方法:

def start_scan(self,invocation):
        http_traffic = invocation.getSelectedMessages()
        if len(http_traffic) !=0:
                service = http_traffic[0].getHttpService()
                hostname = service.getHost()
                ip = socket.gethostbyname(hostname)
                req = urllib2.Request("https://api.shodan.io/shodan/host/"+ip+"?key=<api_key>")
                response = json.loads(urllib2.urlopen(req).read())
                print "This report is last updated on  %s" % str(response['last_update'])
                print "IP - %s" %str(response['ip_str'])
                print "ISP - %s" %str(response['isp'])
                print "City - %s" %str(response['city'])
                print "Possible Vulns - %s" %str(response['vulns'])
                print "Open Ports -  %s" % str(response['ports'])

我们知道invocation是IContextMenuInvocation的实例,所以我们可以调用其中的任何方法,让我们看看有哪些方法:

在代码中作者调用了getSelectedMessages方法,此方法返回调用上下文菜单时用户所选择的请求或响应的详细数据包,返回IHttpRequestResponse对象的实例。

有了IHttpRequestResponse的实例,我们就可以调用其中的方法,作者只是调用了getHttpService这个方法。

后面一系列操作就是取hostname,取ip,传给shodan,解析请求结果了。

如果细心看过前面内容的同学,就可以知道burp的菜单实际上都是可以用此方法进行创建的:

下面来演示一个Send to Repeater beta。

from burp import IBurpExtender 
from burp import IContextMenuFactory

from javax.swing import JMenuItem
from java.util import List, ArrayList


class BurpExtender(IBurpExtender, IContextMenuFactory):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers   = callbacks.getHelpers()
        self._context   = None

        # set up the extension
        callbacks.setExtensionName('IBurp')
        callbacks.registerContextMenuFactory(self)

    def createMenuItems(self,invocation):
        menu_list = ArrayList()
        inv = invocation.getSelectedMessages()
        self.inv = inv
        menu_list.add(JMenuItem("send to repeater beta", actionPerformed=self.sendtorepeater))
        return menu_list

    def sendtorepeater(self,event):

        inv = self.inv[0]
        service = inv.getHttpService()
        host = service.getHost()
        port = service.getPort()
        protocol = service.getProtocol()
        if protocol == 'https':
            is_https = 1
        else:
            is_https = 0
        req = inv.getRequest()
        self._callbacks.sendToRepeater(host,port,is_https,req,None)

我首先注册了该接口并重写了createMenuItems方法,通过JMenuItem实现了一个按钮,当用户在按钮处释放光标后会触发actionPerformed指定的方法并传递给其event参数。

这个参数不需要管,我们只需要处理invocation就好,我通过getSelectedMessages来获取菜单所处的请求以及响应的详细信息,并通过数组取值的方式仅取出请求。后续实际上就是在利用IHttpRequestResponse接口以及IHttpService接口来获取一些我们需要的参数而已了。

最后使用self._callbacks.sendToRepeater方法将请求传递到repeater中。

实现效果:

点击后repeater就多了一项:

可以点击go来获取响应数据包:

IContextMenuInvocation

这个接口在上文中其实已经介绍过了,当Burp在调用IContextMenuInvocation接口时,就会使用此到此接口,我们上文中一直用的invocation,其实就是IContextMenuInvocation的实例。

在这里我会详细的解释这个接口的每个参数是如何作用的。

首先看看此接口中定义的一些属性:

emmm其实这些都是类似toolflag一样的标志位,没什么好讲的,一般用于判断你想处理的请求是否是你想处理的请求。

这么说可能有点抽象,比如你想处理的是proxy的请求,却处理了response的请求,差不多就是这么个意思。

接着看看他的方法吧:

  • getInputEvent

这个一般不会用到,用来获取鼠标事件的一些详细信息,打印出来看看就知道了。

示例:

class BurpExtender(IBurpExtender, IContextMenuFactory):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers   = callbacks.getHelpers()
        self._context   = None

        # set up the extension
        callbacks.setExtensionName('IBurp')
        callbacks.registerContextMenuFactory(self)

    def createMenuItems(self,invocation):
        menu_list = ArrayList()
        print(invocation.getInputEvent())

输出:

  • getInvocationContext

用来获取调用该菜单时的上下文,给个demo你就清楚啦:

from burp import IBurpExtender 
from burp import IContextMenuFactory

from javax.swing import JMenuItem
from java.util import List, ArrayList


class BurpExtender(IBurpExtender, IContextMenuFactory):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers   = callbacks.getHelpers()
        self._context   = None

        # set up the extension
        callbacks.setExtensionName('IBurp')
        callbacks.registerContextMenuFactory(self)

    def createMenuItems(self,invocation):
        menu_list = ArrayList()
        print(invocation.getInvocationContext())

此时如果我在proxy_history中调用,打印出来的结果是6,其值实际上是对应着这里的:

同样的,也是相当于toolflag一样的作用。

  • getToolFlag

顾名思义,返回的是调用菜单时所处的工具的toolflag。

demo:

from burp import IBurpExtender 
from burp import IContextMenuFactory

from javax.swing import JMenuItem
from java.util import List, ArrayList


class BurpExtender(IBurpExtender, IContextMenuFactory):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers   = callbacks.getHelpers()
        self._context   = None

        # set up the extension
        callbacks.setExtensionName('IBurp')
        callbacks.registerContextMenuFactory(self)

    def createMenuItems(self,invocation):
        menu_list = ArrayList()
        print(invocation.getToolFlag())

如果我在proxy_history中调用,打印出来的结果是4,对应着这块:

  • getSelectedIssues

此方法用于返回菜单中所选项的详细issue信息,一般在下图这个位置调用:

返回的是IScanIssue的实例,我们可以调用其中的方法来获取issue的详细信息比如名字啊啥的。

  • getSelectedMessages

此方法返回当前菜单所选项对应的HTTP请求以及响应的详细信息,返回的IHttpRequestResponse的实例,我们可以使用其中的方法来获取当前数据包中的详细信息,这个其实我在上面已经用过了,不了解的可以去看看。

  • getSelectionBounds

不常用,不做介绍。

ICookie

留坑。

IExtensionHelpers

这个接口很重要,会经常用到,如果仔细留意我上边的代码,就会发现我会在每个插件里写上一句:

self._helpers   = callbacks.getHelpers()

getHelpers方法实际上就是获取此接口的实例,此接口中可使用的方法比较多,截不完,可以来这看:Interface IExtensionHelpers

下面开始逐个介绍此接口中的每个方法。

  • analyzeRequest

此方法可传递三种形式的参数,返回的是IRequestInfo的实例,我们通常使用此方法来分析请求数据包。

示例:

from burp import IBurpExtender 
from burp import IContextMenuFactory

from burp import IHttpListener


class BurpExtender(IBurpExtender,IHttpListener):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers   = callbacks.getHelpers()
        self._context   = None

        # set up the extension
        callbacks.setExtensionName('IBurp')
        callbacks.registerHttpListener(self)

    def processHttpMessage(self,toolflag,messageIsRequest,messageInfo):
        service = messageInfo.getHttpService()
        request = messageInfo.getRequest()
        analyze_req = self._helpers.analyzeRequest(service,request)
        params = analyze_req.getParameters()
        new_params = ''
        for param in params:
            k = param.getName().encode("utf-8")
            v = param.getValue().encode("utf-8")
            new_params += k + '=' + v +'&'
        new_params = new_params[:-1]
        print('Params is '+ new_params)

输出:

上述代码用于获取请求中的参数列表,包括cookie的以及query,如果不想要cookie的参数,可以使用一个判断:

if param.getType() == IParameter.PARAM_COOKIE:
                continue

参考:

  • analyzeResponse

此方法返回一个IResponseInfo实例,我们可以使用此实例来分析响应数据包。

示例:

from burp import IBurpExtender 
from burp import IContextMenuFactory

from burp import IHttpListener


class BurpExtender(IBurpExtender,IHttpListener):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers   = callbacks.getHelpers()
        self._context   = None

        # set up the extension
        callbacks.setExtensionName('IBurp')
        callbacks.registerHttpListener(self)

    def processHttpMessage(self,toolflag,messageIsRequest,messageInfo):
        response = messageInfo.getResponse()
        analyze_response = self._helpers.analyzeResponse(response)
        print("status code:"+str(analyze_response.getStatusCode()))

输出:

  • getRequestParameter

此方法用于获取request中特定参数的详细信息,返回IParameter实例。

示例:

from burp import IBurpExtender 
from burp import IContextMenuFactory

from burp import IHttpListener


class BurpExtender(IBurpExtender,IHttpListener):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers   = callbacks.getHelpers()
        self._context   = None

        # set up the extension
        callbacks.setExtensionName('IBurp')
        callbacks.registerHttpListener(self)

    def processHttpMessage(self,toolflag,messageIsRequest,messageInfo):
        request = messageInfo.getRequest()
        analyze_param = self._helpers.getRequestParameter(request,"v")
  • urlDecode

顾名思义,传入一个字符串,返回url解码后的字符串,同样也可以传入bytes的数据,返回被解码后的数据。

  • urlEncode

一样的,传入一个字符串,返回被url编码后的字符串,同上,也可以传入bytes的数据,返回被编码的字符串。

  • base64Decode&base64Encode&stringToBytes&bytesToString

这几个编码解码都是一样的,不重复赘述了。

  • indexOf

直接看图吧,这个方法和Python中的index的使用方式是一样的。

  • buildHttpMessage

这个方法挺重要的,在更新请求的时候会经常用到。

以github上的某个插件为例介绍此方法:CaidaoExt.py

在57-66行获取了请求的详细信息并使用buildHttpMessage方法创建一个更新后的请求:

info = getInfo(messageInfo, messageIsRequest, self._helpers)
            headers = info.getHeaders()

            # get the body
            body = getBody(messageInfo.getRequest(), messageIsRequest, self._helpers)

            # encrypt the caidao post body
            encryptedBody = encryptJython(body, key, iv)

            newMSG = self._helpers.buildHttpMessage(headers, encryptedBody)

67行使用messageInfo.setRequest更新了请求消息主体,此时发出去的请求即为加密后的请求。

由于buildHttpMessage返回的是byte的数据类型,所以同样也可以用于漏扫时的重新发包:

againReq = self._helpers.buildHttpMessage(againReqHeaders, reqBodys)
ishttps = False
if protocol == 'https':
ishttps = True
againRes = self._callbacks.makeHttpRequest(host, port, ishttps, againReq)
  • buildHttpRequest

同样的,此方法也可以用来使用于当漏扫时重新发包:

java_URL = URL(http_url)
port = 443 if java_URL.protocol == 'https' else 80
port = java_URL.port if java_URL.port != -1 else port
httpService = self.helpers.buildHttpService(java_URL.host, port, java_URL.protocol)
httpRequest = self.helpers.buildHttpRequest(URL(http_url))
self.callbacks.makeHttpRequest(httpService, httpRequest))
  • addParameter

看官方描述就清楚了,此方法是用于添加request中的参数的,需要传入一个byte的数据包以及IParameter实例的参数或参数集合。

返回一个完整的数据包,数据类型为byte。

  • removeParameter

同上,传递的参数都是一样的,只不过会移除指定的参数或参数集。

返回一个完整的数据包,数据类型为byte。

  • updateParameter

同上,用于更新请求中的参数,目前支持更新cookie,postdata,url中的参数。

返回更新后的完整数据包,数据类型为byte。

  • toggleRequestMethod

此方法用于切换GET/POST请求,类似以下这个按钮:

返回切换方法后的完整数据包,数据类型为byte。

  • buildHttpService

这个方法在官方案例中有提到:TrafficRedirector.py

个人理解是用来更新httpservice的,一般搭配setHttpService来使用。

  • buildParameter

传入param的name以及value,最后一个type实际上就是IParameter内置的几个type:

返回一个IParameter实例,配合前面的updateParameter或addParameter使用。

  • makeScannerInsertionPoint&analyzeResponseVariations&analyzeResponseKeywords

留个坑,这几个方法没用过,不清楚细节,不在这儿班门弄斧了。

IExtensionStateListener

此接口通过调用IBurpExtenderCallbacks.registerExtensionStateListener()来实现。

需要实现的方法只有一个:extensionUnloaded

当插件被取消使用或者被卸载时,都将触发此方法。

IHttpListener

这个接口挺重要的,可以通过调用IBurpExtenderCallbacks.registerHttpListener()来实现此接口。

  • processHttpMessage

这是此接口中唯一一个需要实现的方法,当通过IBurpExtenderCallbacks.registerHttpListener()注册了HTTP监听器后,所有的流量都会先传入processHttpMessage方法中进行处理。

传递进来的值有这么几个:

1.toolFlag (标志位)
2.messageIsRequest (标记处理的数据包是request还是response)
3.messageInfo (一个IHttpRequestResponse实例,包含当前请求的详细信息)

需要注意的是,浏览器访问某个页面时,流量会先经过此方法,当此方法运行结束后,浏览器才会返回页面,所以如果我们的漏扫是需要发很多次包的,就不要使用这个接口,或者进行某些判断后才触发漏扫的流程。

我们一般使用这个方法来进行如下几个操作:

1.替换请求or响应主体内容
2.进行一次性漏洞扫描 (如xxe这种只需要发一次包的)

IHttpRequestResponse

此接口用于检索和更新有关数据包详细信息,具体可使用的方法如下:

PS:getxxx的方法只能在响应结束后使用,而setxxx的方法也只能在响应前使用。

下面挑几个经常用的来介绍。

  • getHttpService

返回IHttpService的实例,通过此方法我们可以获得一些请求中的信息,包括域名、端口、协议等。

示例:

from burp import IBurpExtender 
from burp import IContextMenuFactory

from burp import IHttpListener


class BurpExtender(IBurpExtender,IHttpListener):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers   = callbacks.getHelpers()
        self._context   = None

        # set up the extension
        callbacks.setExtensionName('IBurp')
        callbacks.registerHttpListener(self)

    def processHttpMessage(self,toolflag,messageIsRequest,messageInfo):
        service = messageInfo.getHttpService()
        host = service.getHost()
        port = service.getPort()
        protocol = service.getProtocol()
        complete_url = str(protocol) + '://' + str(host) + ':' + str(port)
        print("You are loading: " + complete_url)

输出:

  • getRequest&getResponse

这两个方法用于获取请求主体或响应主体,返回的数据类型为bytes,一般配合analyzeRequest以及analyzeResponse来解析请求或响应主体中的信息。

  • setHighlight

高亮某个请求信息,color可以自定义,一般用于高亮存在敏感信息的数据包等。

示例:

from burp import IBurpExtender 
from burp import IContextMenuFactory

from burp import IHttpListener


class BurpExtender(IBurpExtender,IHttpListener):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers   = callbacks.getHelpers()
        self._context   = None

        # set up the extension
        callbacks.setExtensionName('IBurp')
        callbacks.registerHttpListener(self)

    def processHttpMessage(self,toolflag,messageIsRequest,messageInfo):
        messageInfo.setHighlight('red')

此时我们在history中就可以看到被高亮的请求了,效果如下:

  • setRequest&setResponse

两个set的作用都是一样的,一个是用来替换请求主体的信息,一个是用来替换响应主体的信息,拿一个做介绍,以一个解析unicode编码的插件为例:changeu.py

29行开始处理数据包,使用if not messageIsRequest来确保处理的只是response。

42-50行处理header中的content-type:

for header in headers:
                    # Look for Content-Type Header)
                    if header.startswith("Content-Type:"):
                        # Look for HTML response
                        # header.replace('iso-8859-1', 'utf-8')
                        # print header
                        new_headers.append(header.replace('iso-8859-1', 'utf-8'))
                    else:
                        new_headers.append(header)

将编码替换为utf-8。

54-62行处理response中的body:

body = response[analyzedResponse.getBodyOffset():]
                body_string = body.tostring()
                # print body_string
                u_char_escape = re.search( r'(?:\\u[\d\w]{4})+', body_string)
                if u_char_escape:
                    # print u_char_escape.group()
                    u_char = u_char_escape.group().decode('unicode_escape').encode('utf8')
                    new_body_string = body_string.replace(u_char_escape.group(),'--u--'+u_char+'--u--')
                    new_body = self._helpers.bytesToString(new_body_string)

将unicode解码后替换为原响应body,在64行更新了响应结果:

messageInfo.setResponse(self._helpers.buildHttpMessage(new_headers, new_body))

实现效果:

IHttpService

这个之前讲其他接口的时候用到了,这里不赘述了,一般是要配合其他接口来使用的,获取详细的请求地址。

IInterceptedProxyMessage&IProxyListener

IInterceptedProxyMessage这个接口是由注册IProxyListener来实现获取数据包的详细信息的。

先看看IProxyListener吧,这个其实和IHttpListener有点像,只不过IHttpListener是获取burp所有模块的数据包,而IProxyListener仅获取proxy模块的数据包。

接口描述如下:

传入的message就是IInterceptedProxyMessage的实例,其他的都和IHttpListener是一样的。

接着转回来看IInterceptedProxyMessage实例,其可以使用的方法如下:

这里的getMessageInfo方法,最终得到的就是IHttpListener中传入的message。

  • getClientIpAddress

此方法返回客户端的请求地址,一般是127.0.0.1。

  • getListenerInterface

此方法返回代理地址,默认为127.0.0.1:8080。

  • getMessageInfo

此方法返回一个IHttpRequestResponse实例,相当于IHttpListener中传入的message,可以调用其实例中存在的方法。

IIntruderxxx

这几个接口留个坑,下次讲吧,没用到过。

registerMenuItem

此接口用于实现自定义菜单上下文,并通过重写menuItemClicked方法来实现一些自定义操作。

通过Callbacks.registerMenuItem()来重写。

示例:

#! /usr/bin/python
# A sample burp extention in python (needs jython) which extracts hostname from the request (Target Tab).
from burp import IBurpExtender
from burp import IMenuItemHandler
import re
import urllib2
class BurpExtender(IBurpExtender):
    def registerExtenderCallbacks(self, callbacks):
        self.mCallBacks = callbacks
        self.mCallBacks.registerMenuItem("Sample Extention", hostnamefunc())

class hostnamefunc(IMenuItemHandler):
    def menuItemClicked(self, menuItemCaption, messageInfo):
        print "--- Hostname Extract ---"

        if messageInfo:

            request1=HttpRequest(messageInfo[0].getRequest())
            req=request1.request
            host=req[1]    
            print host
            print "DONE"
class HttpRequest:
    def __init__(self, request):
        self.request=request.tostring().splitlines()

加载此插件后可以看到在右键菜单中多了一个选项:

通过点击就可以触发menuItemClicked方法,实现对应的功能,这里实现的功能是gethostname。

输出:

Another

剩下的就是上图中的接口了,基本上是之前接口的方法中引入的实例或对象等。想了解这些方法的话,对应方法对应去看即可。

学习方法

个人建议是对着别人写过的工具理解其中的代码,哪里不理解的可以去看官方接口,或者去github搜某些接口的用法,本地多改改就能理解代码的含义了,Burp写插件其实就是在实现Burp自带的接口,重写其中的一些方法以达成某些想法。

总结

在写这篇文章的过程中,很多方法我是一边实现一边写的,导致有的地方可能写的不正确(因为是按照个人理解写的),如果有错误的地方,希望师傅们能指出来,我会及时更正文中的错误点

关键词:[‘渗透测试’, ‘渗透测试’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now