BurpSuite 插件开发 – JAVA
对 JAVA 的了解仅限于基础语法,如何傻瓜式地使用 JAVA 编写一个 Burp 插件,并带 GUI 界面?
环境:IntelliJ IDEA、JDK 1.8
新建项目
首先新建一个项目,填写一个插件的名字
然后在 Burp 中将 JAVA API 导出到刚新建项目的 /src
目录当中,此时我们调用 Burp 相应方法时便会获得提示
并且在 /src/burp
目录下新建一个名为 BurpExtender
的 JAVACLASS
设置导出为 JAR 文件
在 BurpSuite 中引用插件需要以 jar 格式才行
首先打开 File->Project Structure
,项目结构设置
首先看到 Project Settings->Modules
,确保有一个与项目名称相同的 Module(正常来说肯定会有)
然后再看到 Project Settings->Artifacts
,点击+号,新建一个导出格式
点击 OK 即可
得到,也是点击 OK 即可
尝试导出 JAR
点击 Build->Build Artifacts
,即可导出 jar 文件
需要注意的是,由于此时我们所编写的 BurpExtender 类为空,所以无法导入到 Burp 中
开始编写插件核心功能
首先为了能让插件导入到 Burp 中不报错,我们首先得让 BurpExtender 类实现 IBurpExtender 接口,并初始化一下,输出一点东西
// BurpExtender.javapackage burp;// 插件的入口固定为 BurpExtenderpublic class BurpExtender implements IBurpExtender{ @Override public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { callbacks.printOutput("Hava Fun!"); }}
重新 Build 后,再在 Burp 中加载
可以看到已经成功创建了插件,接下来我们便可以逐步开始完善核心功能,打造一个 SRC 挖掘机了
GUI新建 UI package 并创建 GUI From
首先在 /src
目录下新建一个与 /burp
同级的 Package,例如 ui
然后在 /ui
目录下通过 IDEA 创建 GUI Form
最终得到:
拖拽构建页面
首先选中页面中的 Jpanel 组件,给 field name 取一个名字,如 root,然后在代码框中将 root 变量的 private 改为 public(为了后续在 Burp 中使用)。然后便可以将一些组件直接拖入到页面中,构建自定义UI,如此处显示一句话 Hello GUI From
在 Burp 中显示该组件
首先,初始化 UI 组件,获取到 UI 中主界面变量(此时为 root),并注册到 Burp
BurpGui UI = new BurpGui();jPanelMain = UI.root;callbacks.customizeUiComponent(jPanelMain);
然后使 BurpExtender 类实现 ITab 接口
//返回 Tab 的名字@Overridepublic String getTabCaption() {return "liteTools";}//返回 UI 的主界面变量@Overridepublic Component getUiComponent() {return jPanelMain;}
此时,重新 Build 一下项目,在 Burp 中导入即可得到以下结果
实现点击事件监听
通常,UI 页面是用来交互的,那么如何在 GUI From 中实现呢?
首先简单拖入一个按钮,并给 field name 取名为 buttom(截图时写错了button)
此时右键 buttom 组件,点击 Create Listener->ActionLister,即可为按钮创建事件监听函数
例如,点击按钮修改显示文本
package ui;import javax.swing.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;public class BurpGui { public JPanel root; private JLabel text; private JButton button; public BurpGui() { buttom.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { text.setText("震惊!大黑阔!!!"); } }); }}
此时,重新 Build 并在 Burp 中导入,即可看到如下效果
使用 Burp 的 UI 组件
如下所示,首先需要自定义实现 UI 父组件(也就是 jPanel root),并实现相应的接口。同时也需要实现构造函数,引入 IBurpExtenderCallbacks 工具类
附代码:
//BurpGUI.javapackage ui;import javax.swing.*;import burp.IHttpService;import burp.IMessageEditor;import burp.IBurpExtenderCallbacks;import burp.IMessageEditorController;public class BurpGUI implements IMessageEditorController { public JPanel root; private IMessageEditor reqMessageEditor; private IBurpExtenderCallbacks callbacks; public BurpGui(IBurpExtenderCallbacks cb) { callbacks = cb; } private void createUIComponents() { // TODO: place custom component creation code here root = new JPanel(); reqMessageEditor = this.callbacks.createMessageEditor(BurpGUI.this, false); root.add(reqMessageEditor.getComponent()); } @Override public IHttpService getHttpService() { //自定义逻辑返回 Service 信息 return null; } @Override public byte[] getRequest() { //自定义逻辑返回请求信息 return new byte[0]; } @Override public byte[] getResponse() { //自定义逻辑返回响应信息 return new byte[0]; }}
//BurpExtender.javapackage burp;import javax.swing.*;import java.awt.*;import ui.*;public class BurpExtender implements IBurpExtender,ITab{ private JPanel jPanelMain; private IMessageEditor reqMessageEditor; @Override public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { callbacks.setExtensionName("liteTools"); callbacks.printOutput("Hava Fun!"); BurpGui UI = new BurpGui(callbacks); jPanelMain = UI.root; callbacks.customizeUiComponent(jPanelMain); callbacks.addSuiteTab(this); } @Override public String getTabCaption() { return "liteTools"; } @Override public Component getUiComponent() { return jPanelMain; }}
当什么也没写就得到了这么个玩意
附常用接口、类插件入口类IBurpExtender
所有插件都必须实现该接口
void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks)
该接口下面只有如上一个方法,并只会在启动插件的时候调用一次。因此常在该方法内执行一些初始化操作,如定义插件名称、初始化UI、注册全局变量、注册监听器等
工具类IBurpExtenderCallbacks
Burp Suite 使用此接口将一组回调方法传递给扩展,扩展可以使用这些方法在 Burp 中执行各种操作。可能不是很好理解,简单来说就是该类提供了很多方法,用来告诉BURP应该在什么时候干什么事的
常量
查看API文档首先可以看到,该类下面有一些静态变量。这些变量常用来判断HTTP消息是从哪个地方传过来的,如 Proxy、Repeater、Intruder等,常在IHttpListener的processHttpMessage方法内用到
方法
只列举常用方法讲解
printError
void printError(java.lang.String error)
输出错误信息
printOutput
void printOutput(java.lang.String output)
输出一般信息
setExtensionName
voidsetExtensionName(java.lang.String name)
用于设置插件名称
getProxyHistory
IHttpRequestResponse[]getProxyHistory()
用于获取 Proxy 模块内所有的历史请求,并返回一个 IHttpRequestResponse 类型的数组。如果想在安装插件的时候自动扫描历史请求,可以使用这个方法
registerProxyListener
voidregisterProxyListener(IProxyListener listener)
用于注册Proxy监听器,监听Proxy模块正在处理的请求和响应的通知。
registerHttpListener
voidregisterHttpListener(IHttpListener listener)
用于注册HTTP监听器,监听所有模块(Proxy、Repeater等模块)正在处理的请求和响应的通知。
registerExtensionStateListener
voidregisterExtensionStateListener(IExtensionStateListener listener)
用于注册插件状态监听(在卸载插件时需要)
registerContextMenuFactory
voidregisterContextMenuFactory(IContextMenuFactory factory)
用于为自定义上下文菜单项注册工厂(注册右键菜单时需要)
还有很多类似的register方法
saveBuffersToTempFiles
IHttpRequestResponsePersistedsaveBuffersToTempFiles(IHttpRequestResponse httpRequestResponse)
用于将请求、响应消息对象保存到临时文件中,减少内存开销
saveToTempFile
ITempFilesaveToTempFile(byte[] buffer)
用于将传入的buffer保存到临时文件,如byte类型请求响应等,并返回一个ITempFile对象
saveConfigAsJson
java.lang.StringsaveConfigAsJson(java.lang.String... configPaths)
用于获取BURP项目级别的配置,传入configPaths可获取指定配置,可配合saveTempfile保存文件
loadConfigFromJson
voidloadConfigFromJson(java.lang.String config)
导入项目配置文件
makeHttpRequest
IHttpRequestResponsemakeHttpRequest(IHttpService httpService, byte[] request)IHttpRequestResponsemakeHttpRequest(IHttpService httpService, byte[] request, boolean forceHttp1)byte[]makeHttpRequest(java.lang.String host, int port, boolean useHttps, byte[] request)byte[]makeHttpRequest(java.lang.String host, int port, boolean useHttps, byte[] request, boolean forceHttp1)
用于 发起 HTTP/1请求
makeHttp2Request
byte[]makeHttp2Request(IHttpService httpService, java.util.List headers, byte[] body)byte[]makeHttp2Request(IHttpService httpService, java.util.List headers, byte[] body, boolean forceHttp2)byte[]makeHttp2Request(IHttpService httpService, java.util.List headers, byte[] body, boolean forceHttp2, java.lang.String connectionIdentifier)
用于 发起 HTTP/2请求
getHelpers
IExtensionHelpersgetHelpers()
此方法用于获取 IExtensionHelpers 对象,扩展程序可以使用该对象来执行许多有用的任务。
是一个小工具,里面有很多常用的方法供我们使用,让插件编写更加方便
IExtensionHelpers
该接口包含许多辅助方法,扩展可以使用这些方法来协助 Burp 扩展出现的各种常见任务
analyzeRequest
IRequestInfoanalyzeRequest(byte[] request)IRequestInfoanalyzeRequest(IHttpRequestResponse request)IRequestInfoanalyzeRequest(IHttpService httpService, byte[] request)
可以看到,该方法重载了三次。传入不同的参数类型,最终得到的都是一个IRequestInfo对象(请求对象)
这里有一个坑,第一个跟第二个得到的对象存在一点区别
analyzeResponse
IResponseInfoanalyzeResponse(byte[] response)
得到一个IResponseInfo对象(响应对象)
编码相关
# base64编码解码byte[]base64Decode(byte[] data)byte[]base64Decode(java.lang.String data)java.lang.Stringbase64Encode(byte[] data)java.lang.Stringbase64Encode(java.lang.String data)# URL编码解码byte[]urlDecode(byte[] data)java.lang.StringurlDecode(java.lang.String data)byte[]urlEncode(byte[] data)java.lang.StringurlEncode(java.lang.String data)# 字符与byte相互转换java.lang.StringbytesToString(byte[] data)byte[]stringToBytes(java.lang.String data)
buildHttpMessage
byte[]buildHttpMessage(java.util.List headers, byte[] body)
构建一个请求、响应消息,常用于拦截请求并修改其中的参数时会使用
HTTP消息类IInterceptedProxyMessage
该类表示一条被Proxy模块拦截的请求
getClientIpAddress
java.net.InetAddressgetClientIpAddress()
获取被拦截请求的客户端IP,也就是请求来源IP
getListenerInterface
java.lang.StringgetListenerInterface()
获取被拦截请求的Proxy Listener,返回值:127.0.0.1:8080
可用来针对不同来源的请求做特殊化处理,如PC/手机
getMessageInfo
IHttpRequestResponsegetMessageInfo()
获取被拦截的请求/响应消息的IHttpRequestResponse对象
IHttpRequestResponse
此接口用来获取&更新请求响应
setHighlight
voidsetHighlight(java.lang.String color)
高亮请求响应,可传入red、orange、yellow、green、cyan、blue、pink、magenta、gray值,传空值则表示清除高亮设置
getHighlight
java.lang.StringgetHighlight()
获取请求是否高亮,若有则返回对应颜色,若没有则返回None
setComment
voidsetComment(java.lang.String comment)
设置备注
getComment
java.lang.StringgetComment()
获取备注
setHttpService
voidsetHttpService(IHttpService httpService)
设置请求的服务器地址,需要传入一个IHttpService对象
getHttpService
IHttpServicegetHttpService()
获取请求的服务器地址IHttpService对象
setRequest
voidsetRequest(byte[] message)
设置请求,在需要修改请求内容时用到
getRequest
byte[]getRequest()
获取请求,注意此时是byte数组,一般需要用IExtensionHelpers.analyzeRequest(byte[] request)方法转成IRequestInfo对象
setResponse
voidsetResponse(byte[] message)
设置响应,在需要修改响应内容时用到
getResponse
byte[]getResponse()
获取响应,注意此时是byte数组,一般需要用IExtensionHelpers.analyzeRespnse(byte[] response)方法转成IResponseInfo对象
IRequestInfo
HTTP请求对象
getMethod
java.lang.StringgetMethod()
获取请求方法,GET、POST、PUT······
getUrl
java.net.URLgetUrl()
获取请求的URL,注意此处返回值是一个java.net.URL对象,需要再调用具体的方法获取URL相关内容
getHeaders
java.util.ListgetHeaders()
以数组方式返回请求行与请求头,如["GET / HTTP/1.1", "Host: example.com"]
getParameters
java.util.ListgetParameters()
获取所有请求参数,包含了JSON数据中的参数,返回一个IParameter类型的数组
getContentType
bytegetContentType()
获取请求头中的Content-Type
,注意此处返回的是一个整形数字,可以与该类中的静态变量对比
getBodyOffset
intgetBodyOffset()
获取请求body开始时的偏移量(索引值),常使用如request[analyzedRequest.getBodyOffset():].tostring()
获取整个请求body
IResponseInfo
HTTP响应对象
getStatusCode
shortgetStatusCode()
获取响应状态码
getHeaders
java.util.ListgetHeaders()
以数组方式返回请求行与请求头,如["HTTP/1.1 200 OK", "Content-Length: 1256"]
getCookies
java.util.ListgetCookies()
获取服务器返回的Set-Cookie字段的ICookie对象列表
getStatedMimeType
java.lang.StringgetStatedMimeType()
获取响应头中标注的 Content-Type,注意此处返回的是string类型,与 IRequestInfo 中的稍有区别
getInferredMimeType
java.lang.StringgetInferredMimeType()
获取BURP自动判断响应内容的Content-Type
getBodyOffset
intgetBodyOffset()
获取响应body开始时的偏移量(索引值),常使用如response[analyzedResponse.getBodyOffset():].tostring()
获取整个响应body
IParameter
用于获取请求参数的详情,Key和Value等。This interface is used to hold details about an HTTP request parameter,由官方文档可以看出来,其并未考虑目前前后端分离网站的情况,未提供获取响应中的参数,因此只能直接写获取响应参数的方法
getName
java.lang.StringgetName()
获取参数名称
getValue
java.lang.StringgetValue()
获取参数值
getType
bytegetType()
检索参数类型,比如说是JSON内的参数还是URL里传输的参数,具体可以该类中的静态变量对比
参数名索引
intgetNameStart()intgetNameEnd()
获取参数名开始与结束的偏移量(索引)
值索引
intgetValueStart()intgetValueEnd()
获取参数值开始与结束的偏移量(索引)
IHttpHeadergetName
java.lang.StringgetName()
获取请求头名称
getValue
java.lang.StringgetValue()
获取请求头的值
ICookiegetName
java.lang.StringgetName()
获取Cookie名称
getValue
java.lang.StringgetValue()
获取Cookie值
getDomain
java.lang.StringgetDomain()
获取Cookie适用的域名
getPath
java.lang.StringgetPath()
获取Cookie使用的PATH
getExpiration
java.util.DategetExpiration()
获取Cookie过期时间
IHttpServicegetHost
java.lang.StringgetHost()
获取服务器域名
getPort
intgetPort()
获取服务器端口
getProtocol
java.lang.StringgetProtocol()
获取服务器使用的协议,HTTP、HTTPS
监听类IProxyListener
Proxy 消息监听类。使用
IBurpExtenderCallbacks.registerProxyListener()
方法注册该监听器后,便可监听所有流经Proxy的请求与响应
所有请求/响应均会被
processProxyMessage
voidprocessProxyMessage(boolean messageIsRequest, IInterceptedProxyMessage message)# messageIsRequest 表示此次处理的是请求还是响应# IInterceptedProxyMessage message 表示一个被拦截HTTP消息对象,可通过该对象的getMessageInfo()方法获取到具体的请求、响应详情信息
方法捕获到,然后在其中完成用户自定的处理行为
例如:我们想要查看所有请求中是否有使用shiro的系统,并将该请求高亮显示,便可以这样实现
图片是网上找的,没太多时间重新搭环境
package burp;import java.util.List;public class BurpExtender implements IBurpExtender,IProxyListener{ private IExtensionHelpers helper; @Override public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { callbacks.setExtensionName("liteTools"); this.helper = callbacks.getHelpers(); callbacks.registerProxyListener(this); } @Override public void processProxyMessage(boolean messageIsRequest, IInterceptedProxyMessage message) { if(!messageIsRequest){ IHttpRequestResponse reqrep = message.getMessageInfo(); byte[] reqrep_b = reqrep.getResponse(); IResponseInfo rep = helper.analyzeResponse(reqrep_b); List cookies = rep.getCookies(); for(ICookie cookie:cookies){ if(cookie.getName() == "rememberMe"){ reqrep.setHighlight("red"); } } } }}
可以看到,我们甚至都不需要自己写一些函数来处理请求响应,BURP API提供了很多常用的类与方法
百度主站并没有shrio哈,此处是为了方便插件测试的时候所以判断是不是有 BDSVRTM 这个cookie
IHttpListener
HTTP 消息监听类。使用
IBurpExtenderCallbacks.registerHTTPListener()
方法注册该监听器后,便可监听所有请求与响应,包括Proxy、Repeater、Intruder等等所有的 made by any Burp tool 的流量
请求响应会被
processHttpMessage
voidprocessHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo)# toolFlag表示来源,可在 IBurpExtenderCallbacks 类中看到
方法捕获到,使用toolFlag判断来源,如
package burp;public class BurpExtender implements IBurpExtender,IHttpListener{ IBurpExtenderCallbacks cb; @Override public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { callbacks.setExtensionName("liteTools"); this.cb=callbacks; } @Override public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) { if(toolFlag == cb.TOOL_PROXY){ cb.printOutput("Proxy"); } else if (toolFlag == cb.TOOL_REPEATER) { cb.printOutput("Repeater"); }else{ cb.printOutput("Others"); } }}
IExtensionStateListener
插件状态监听类。通过调用 IBurpExtenderCallbacks.registerExtensionStateListener() 来注册一个扩展状态监听器。 侦听器将收到扩展状态更改的通知。 注意:任何启动后台线程或打开系统资源(例如文件或数据库连接)的扩展都应该注册一个侦听器并在卸载扩展时终止线程/关闭资源。
当插件被卸载时会调用如下方法:注意:退出BURP时也会调用哦
extensionUnloaded
voidextensionUnloaded()
通常我们会在其中完成善后工作,如保存项目配置
package burp;import java.io.*;public class BurpExtender implements IBurpExtender,IExtensionStateListener{ private IBurpExtenderCallbacks cb; private String dir = System.getProperty("user.dir"); @Override public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { callbacks.setExtensionName("liteTools"); callbacks.printOutput("Hava Fun!"); callbacks.registerExtensionStateListener(this); File file = new File(dir + "/config.json"); byte[] filecontent = new byte[(int) file.length()]; try{ FileInputStream in = new FileInputStream(file); in.read(filecontent); in.close(); }catch (IOException e){ cb.printError(e.toString()); } callbacks.loadConfigFromJson(new String(filecontent)); this.cb = callbacks; } @Override public void extensionUnloaded() { String config = cb.saveConfigAsJson(dir); try{ BufferedWriter out = new BufferedWriter(new FileWriter("config.json")); out.write(config); out.close(); cb.printOutput("File Saved at "+ dir); }catch (IOException e){ cb.printError(e.toString()); } }}
从此妈妈再也不用担心我忘记保存配置了
UI类IContextMenuInvocation
当 Burp 使用上下文菜单调用的详细信息调用扩展提供的 IContextMenuFactory 时,将使用此接口。 自定义上下文菜单工厂可以查询此接口以获取调用事件的详细信息,以确定应显示哪些菜单项。
getToolFlag
intgetToolFlag()
获取点击右键动作是在哪个模块内触发的,Proxy、Repeater等
getInvocationContext
bytegetInvocationContext()
获取点击右键动作是在哪里触发的(详细信息),具体可对比
getSelectedMessages
IHttpRequestResponse[]getSelectedMessages()
获取右键内容的的HTTP请求响应(IHttpRequestResponse对象)
getSelectedIssues
IScanIssue[]getSelectedIssues()
获取右键内容的的扫描IScanIssue对象
注意:至于什么时候使用getSelectedMessages或getSelectedIssues,可根据getInvocationContext获取的结果加以判断
IContextMenuFactory
注册右键菜单
该类只有一个方法
createMenuItems
java.util.ListcreateMenuItems(IContextMenuInvocation invocation)
当用户在 Burp 内的任何位置调用上下文菜单(右键)时,该方法将被 Burp 调用
比方说我想实现一个这样的右键菜单,完成一些自定义的事情
那便可以这样写:
package burp;import javax.swing.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.util.ArrayList;import java.util.List;public class BurpExtender implements IBurpExtender,IContextMenuFactory{ IBurpExtenderCallbacks cb; @Override public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { callbacks.setExtensionName("liteTools"); callbacks.registerContextMenuFactory(this); this.cb = callbacks; } @Override public List createMenuItems(IContextMenuInvocation invocation) { ArrayList menus = new ArrayList(); if(invocation.getToolFlag() == cb.TOOL_REPEATER || invocation.getToolFlag() == cb.TOOL_PROXY){ JMenuItem btn = new JMenuItem("PrintHOST"); btn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { IHttpRequestResponse[] reqreps = invocation.getSelectedMessages(); for(IHttpRequestResponse reqrep:reqreps){ cb.printOutput(reqrep.getHttpService().getHost()); } } }); menus.add(btn); } return menus; } }