我想写一篇文章,关于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包命令:
(4)打开当前目录→target
点击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插件也可以。