└── README.md
/README.md:
--------------------------------------------------------------------------------
1 | # burpDevNote
2 | burp插件开发笔记,简单记一下方便查阅。刚入门最好的建议就是模仿,多看别人写的,尝试着修改,尝试着自己写一个。
3 |
4 |
5 |
6 |
7 |
8 | ## 基础知识
9 |
10 | burp suite支持三种编程语言开发的插件:
11 |
12 | - Java
13 | - python
14 | - ruby
15 |
16 | 我选java
17 |
18 | 官方各种示例代码:https://portswigger.net/burp/extender
19 |
20 | 官方API文档:https://portswigger.net/burp/extender/api/
21 |
22 |
23 |
24 | maven依赖
25 |
26 | ```xml
27 |
28 | net.portswigger.burp.extender
29 | burp-extender-api
30 | 1.7.22
31 |
32 | ```
33 |
34 |
35 |
36 | - 所有的burp插件都必须实现IBurpExtender这个接口
37 | - 实现类的包名称必须是burp
38 | - 实现类的名称必须是BurpExtender,burp插件入口
39 | - 实现类比较是public的
40 | - 实现类必须有默认构造函数(public,无参),如果没有定义构造函数就是默认构造函数
41 |
42 |
43 |
44 | ### callbacks对象的作用
45 |
46 | 通过 callbacks 这个实例对象,传递给插件一系列burp的原生方法。我们需要实现的很多功能都需要调用这些方法。当前拓展用来和burp进行交互,如各种注册,设置拓展名字,获取helper,添加菜单等等。
47 |
48 |
49 |
50 | ### 需要主动注册的对象
51 |
52 |
53 |
54 | 也就是需要写在registerExtenderCallbacks方法里面的
55 |
56 | 他们的共通特定是:会被burp主程序主动调用或者主动通知,burp主程序必须知道它们的存在,才能完成对应的功能。
57 |
58 | #### 各种事件监听器
59 |
60 | ExtensionStateListener
61 |
62 | HttpListener
63 |
64 | ProxyListener
65 |
66 | ScannerListener
67 |
68 | ScopeChangeListener
69 |
70 | #### 各种对象构造工厂
71 |
72 | ContextMenuFactory
73 |
74 | MessageEditorTabFactory
75 |
76 | IntruderPayloadGeneratorFactory
77 |
78 | #### 其他
79 |
80 | ScannerInsertionPointProvider
81 |
82 | ScannerCheck
83 |
84 | IntruderPayloadProcessor
85 |
86 | SessionHandlingAction
87 |
88 | MenuItem
89 |
90 |
91 |
92 | ## 关键对象
93 |
94 |
95 |
96 | ### IExtensionHelpers
97 |
98 | 用途:主要用来处理数据包
99 |
100 | 初始化,一般写在registerExtenderCallbacks方法里面
101 |
102 | ```java
103 | IExtensionHelpers helpers = callbacks.getHelpers();
104 | ```
105 |
106 | Request和Response获取
107 |
108 | ```
109 | IRequestInfo analyzeRequest = helpers.analyzeRequest(requestOrResponse);
110 | IResponseInfo analyzeResponse = helpers.analyzeResponse(requestOrResponse);
111 | ```
112 |
113 | 获取header
114 |
115 | ```
116 | IRequestInfo analyzeRequest = helpers.analyzeRequest(requestOrResponse);
117 | List headers = analyzeRequest.getHeaders();
118 | ```
119 |
120 | 获取body
121 |
122 | ```java
123 | int bodyOffset = -1;
124 | if(isRequest) {
125 | IRequestInfo analyzeRequest = helpers.analyzeRequest(requestOrResponse);
126 | bodyOffset = analyzeRequest.getBodyOffset();
127 | }else {
128 | IResponseInfo analyzeResponse = helpers.analyzeResponse(requestOrResponse);
129 | bodyOffset = analyzeResponse.getBodyOffset();
130 | }
131 | byte[] byte_body = Arrays.copyOfRange(requestOrResponse, bodyOffset, requestOrResponse.length);
132 | //not length-1
133 | //String body = new String(byte_body); //byte[] to String
134 | return byte_body;
135 | ```
136 |
137 |
138 |
139 | 修改http包
140 |
141 | ```
142 | byte[] RequestOrResponse = helpers.buildHttpMessage(headers, body);
143 | ```
144 |
145 |
146 |
147 | 具体看大佬封装的工具类https://github.com/bit4woo/burp-api-common/blob/master/src/main/java/burp/HelperPlus.java
148 |
149 |
150 |
151 |
152 |
153 | ### IHttpListener
154 |
155 | 用途:任何组件产生的请求和响应都会触发http监听器,有点被动扫描的味道。
156 |
157 | 必须实现processHttpMessage方法
158 |
159 | ```java
160 | processHttpMessage(int toolFlag,boolean messageIsRequest,IHttpRequestResponse messageInfo)
161 | ```
162 |
163 | toolFlag
164 |
165 | 不同的toolFlag代表了不同的burp组件,一共如下
166 |
167 | https://portswigger.net/burp/extender/api/constant-values.html#burp.IBurpExtenderCallbacks
168 |
169 | 
170 |
171 | messageIsRequest,判断当前是不是request
172 |
173 | IHttpRequestResponse,具体的消息体对象。可用`helpers#analyzeRequest`对消息体进行解析,messageInfo是整个HTTP请求和响应消息体的总和,各种HTTP相关信息的获取都来自于它,HTTP流量的修改都是围绕它进行的。
174 |
175 |
176 |
177 |
178 |
179 | ```java
180 | package burp;
181 |
182 | import java.io.PrintWriter;
183 | import java.util.Arrays;
184 | import java.util.List;
185 |
186 | public class BurpExtender implements IBurpExtender, IHttpListener
187 | {//所有burp插件都必须实现IBurpExtender接口,而且实现的类必须叫做BurpExtender
188 | private IBurpExtenderCallbacks callbacks;
189 | private IExtensionHelpers helpers;
190 |
191 | private PrintWriter stdout;
192 | private PrintWriter stderr;
193 | private String ExtenderName = "burp extender api drops by bit4woo";
194 |
195 | @Override
196 | public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks)
197 | {//IBurpExtender必须实现的方法
198 | stdout = new PrintWriter(callbacks.getStdout(), true);
199 | stderr = new PrintWriter(callbacks.getStderr(), true);
200 | callbacks.printOutput(ExtenderName);
201 | //stdout.println(ExtenderName);
202 | this.callbacks = callbacks;
203 | helpers = callbacks.getHelpers();
204 | callbacks.setExtensionName(ExtenderName);
205 | callbacks.registerHttpListener(this); //如果没有注册,下面的processHttpMessage方法是不会生效的。处理请求和响应包的插件,这个应该是必要的
206 | }
207 |
208 | @Override
209 | public void processHttpMessage(int toolFlag,boolean messageIsRequest,IHttpRequestResponse messageInfo)
210 | {
211 | if (toolFlag == IBurpExtenderCallbacks.TOOL_PROXY){
212 | //不同的toolFlag代表了不同的burp组件 https://portswigger.net/burp/extender/api/constant-values.html#burp.IBurpExtenderCallbacks
213 | if (messageIsRequest){ //对请求包进行处理
214 | IRequestInfo analyzeRequest = helpers.analyzeRequest(messageInfo);
215 | //对消息体进行解析,messageInfo是整个HTTP请求和响应消息体的总和,各种HTTP相关信息的获取都来自于它,HTTP流量的修改都是围绕它进行的。
216 |
217 | /*****************获取参数**********************/
218 | List paraList = analyzeRequest.getParameters();
219 | //获取参数的方法
220 | //当body是json格式的时候,这个方法也可以正常获取到键值对;但是PARAM_JSON等格式不能通过updateParameter方法来更新。
221 | //如果在url中的参数的值是 key=json格式的字符串 这种形式的时候,getParameters应该是无法获取到最底层的键值对的。
222 |
223 | for (IParameter para : paraList){// 循环获取参数,判断类型,进行加密处理后,再构造新的参数,合并到新的请求包中。
224 | String key = para.getName(); //获取参数的名称
225 | String value = para.getValue(); //获取参数的值
226 | int type = para.getType();
227 | stdout.println("参数 key value type: "+key+" "+value+" "+type);
228 | }
229 |
230 | /*****************修改并更新参数**********************/
231 | IParameter newPara = helpers.buildParameter("testKey", "testValue", IParameter.PARAM_BODY); //构造新的参数
232 | byte[] new_Request = messageInfo.getRequest();
233 | new_Request = helpers.updateParameter(new_Request, newPara); //构造新的请求包
234 | messageInfo.setRequest(new_Request);//设置最终新的请求包
235 |
236 | /*****************删除参数**********************/
237 | for (IParameter para : paraList){// 循环获取参数,判断类型,进行加密处理后,再构造新的参数,合并到新的请求包中。
238 | String key = para.getName(); //获取参数的名称
239 | if (key.equals("aaa")) {
240 | new_Request = helpers.removeParameter(new_Request, para); //构造新的请求包
241 | }
242 | }
243 |
244 |
245 | /*****************获取header**********************/
246 | List headers = analyzeRequest.getHeaders();
247 |
248 | for (String header : headers){// 循环获取参数,判断类型,进行加密处理后,再构造新的参数,合并到新的请求包中。
249 | stdout.println("header "+header);
250 | if (header.startsWith("referer")) {
251 | /*****************删除header**********************/
252 | headers.remove(header);
253 | }
254 | }
255 |
256 | /*****************新增header**********************/
257 | headers.add("myheader: balalbala");
258 |
259 |
260 | /*****************获取body 方法一**********************/
261 | int bodyOffset = analyzeRequest.getBodyOffset();
262 | byte[] byte_Request = messageInfo.getRequest();
263 |
264 | String request = new String(byte_Request); //byte[] to String
265 | String body = request.substring(bodyOffset);
266 | byte[] byte_body = body.getBytes(); //String to byte[]
267 |
268 | /*****************获取body 方法二**********************/
269 |
270 | int len = byte_Request.length;
271 | byte[] byte_body1 = Arrays.copyOfRange(byte_Request, bodyOffset, len);
272 |
273 | new_Request = helpers.buildHttpMessage(headers, byte_body);
274 | //如果修改了header或者数修改了body,不能通过updateParameter,使用这个方法。
275 | messageInfo.setRequest(new_Request);//设置最终新的请求包
276 | }
277 | }
278 | else{//处理响应包
279 | IResponseInfo analyzedResponse = helpers.analyzeResponse(messageInfo.getResponse()); //getResponse获得的是字节序列
280 | short statusCode = analyzedResponse.getStatusCode();
281 | List headers = analyzedResponse.getHeaders();
282 | String resp = new String(messageInfo.getResponse());
283 | int bodyOffset = analyzedResponse.getBodyOffset();//响应包是没有参数的概念的,大多需要修改的内容都在body中
284 | String body = resp.substring(bodyOffset);
285 |
286 | if (statusCode==200){
287 | String newBody= body+"&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&";
288 | byte[] bodybyte = newBody.getBytes();
289 | messageInfo.setResponse(helpers.buildHttpMessage(headers, bodybyte));
290 | }
291 | }
292 | }
293 | }
294 | ```
295 |
296 |
297 |
298 | ### IContextMenuFactory
299 |
300 | 
301 |
302 | 用途:注册右键菜单,必须实现createMenuItems方法。
303 |
304 | 如下创建菜单,添加点击事件
305 |
306 | ```java
307 | @Override
308 | public List createMenuItems(IContextMenuInvocation invocation) {
309 |
310 | ArrayList menu_item_list = new ArrayList();
311 |
312 | //常用
313 | JMenuItem printEmails = new JMenuItem("Print Emails");
314 | printEmails.addActionListener(new printEmails(invocation));
315 | menu_item_list.add(printEmails);
316 |
317 | JMenuItem printCookie = new JMenuItem("find last cookie of Url");
318 | printCookie.addActionListener(new printCookies(invocation));
319 | menu_item_list.add(printCookie);
320 |
321 | JMenuItem scan = new JMenuItem("scan this url");
322 | scan.addActionListener(new printCookies(invocation));
323 | menu_item_list.add(scan);
324 |
325 | return menu_item_list;
326 |
327 | }
328 | ```
329 |
330 |
331 |
332 | - 鼠标右键的创建
333 | - 从Scanner issues中收集邮箱地址
334 | - 从Proxy history中查找最新cookie
335 | - 发起扫描任务、发起爬行任务
336 | - 查询和更新scope
337 |
338 | ```java
339 | package burp;
340 |
341 | import java.awt.MenuItem;
342 | import java.awt.event.ActionEvent;
343 | import java.awt.event.ActionListener;
344 | import java.io.PrintWriter;
345 | import java.lang.reflect.Array;
346 | import java.net.URL;
347 | import java.net.URLDecoder;
348 | import java.util.ArrayList;
349 | import java.util.Arrays;
350 | import java.util.List;
351 | import java.util.regex.Matcher;
352 | import java.util.regex.Pattern;
353 |
354 | import javax.swing.JMenuItem;
355 |
356 |
357 | public class BurpExtender implements IBurpExtender, IContextMenuFactory
358 | {//所有burp插件都必须实现IBurpExtender接口,而且实现的类必须叫做BurpExtender
359 | private IBurpExtenderCallbacks callbacks;
360 | private IExtensionHelpers helpers;
361 |
362 | private PrintWriter stdout;
363 | private PrintWriter stderr;
364 | private String ExtenderName = "burp extender api drops by bit4woo";
365 |
366 | @Override
367 | public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks)
368 | {//IBurpExtender必须实现的方法
369 | stdout = new PrintWriter(callbacks.getStdout(), true);
370 | stderr = new PrintWriter(callbacks.getStderr(), true);
371 | callbacks.printOutput(ExtenderName);
372 | //stdout.println(ExtenderName);
373 | this.callbacks = callbacks;
374 | helpers = callbacks.getHelpers();
375 | callbacks.setExtensionName(ExtenderName);
376 | callbacks.registerContextMenuFactory(this);
377 | }
378 |
379 | @Override
380 | public List createMenuItems(IContextMenuInvocation invocation) {
381 |
382 | ArrayList menu_item_list = new ArrayList();
383 |
384 | //常用
385 | JMenuItem printEmails = new JMenuItem("Print Emails");
386 | printEmails.addActionListener(new printEmails(invocation));
387 | menu_item_list.add(printEmails);
388 |
389 | JMenuItem printCookie = new JMenuItem("find last cookie of Url");
390 | printCookie.addActionListener(new printCookies(invocation));
391 | menu_item_list.add(printCookie);
392 |
393 | JMenuItem scan = new JMenuItem("scan this url");
394 | scan.addActionListener(new printCookies(invocation));
395 | menu_item_list.add(scan);
396 |
397 | return menu_item_list;
398 |
399 | }
400 |
401 | public class printEmails implements ActionListener{
402 | private IContextMenuInvocation invocation;
403 |
404 | public printEmails(IContextMenuInvocation invocation) {
405 | this.invocation = invocation;
406 | }
407 |
408 | @Override
409 | public void actionPerformed(ActionEvent event) {
410 | IScanIssue[] issues = callbacks.getScanIssues(null);
411 |
412 | final String REGEX_EMAIL = "[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+";
413 | Pattern pDomainNameOnly = Pattern.compile(REGEX_EMAIL);
414 |
415 | for (IScanIssue issue:issues) {
416 | if (issue.getIssueName().equalsIgnoreCase("Email addresses disclosed")) {
417 | String detail = issue.getIssueDetail();
418 | Matcher matcher = pDomainNameOnly.matcher(detail);
419 | while (matcher.find()) {//多次查找
420 | String email = matcher.group();
421 | System.out.println(matcher.group());
422 | }
423 | }
424 | }
425 | }
426 | }
427 |
428 | public class printCookies implements ActionListener{
429 | private IContextMenuInvocation invocation;
430 |
431 | public printCookies(IContextMenuInvocation invocation) {
432 | this.invocation = invocation;
433 | }
434 |
435 | @Override
436 | public void actionPerformed(ActionEvent event) {
437 | try {
438 | IHttpRequestResponse[] messages = invocation.getSelectedMessages();
439 | byte[] req = messages[0].getRequest();
440 | String currentShortUrl = messages[0].getHttpService().toString();
441 | stdout.println(currentShortUrl);
442 |
443 | /*******************从Proxy history中查找最新cookie***************************/
444 | IHttpRequestResponse[] historyMessages = callbacks.getProxyHistory();
445 | int len = historyMessages.length;
446 | for (int index=len; index >=0; index--) {
447 | IHttpRequestResponse item = historyMessages[index];
448 |
449 | String hisShortUrl = item.getHttpService().toString();
450 | if (currentShortUrl.equals(hisShortUrl)) {
451 | IRequestInfo hisanalyzedRequest = helpers.analyzeRequest(item);
452 | List headers = hisanalyzedRequest.getHeaders();
453 |
454 | for (String header:headers) {
455 | if (header.startsWith("Cookie:")) {
456 | stdout.println("找到cookie---"+header);
457 | }
458 | }
459 | }
460 | }
461 | } catch (Exception e) {
462 | callbacks.printError(e.getMessage());
463 | }
464 | }
465 | }
466 |
467 | public class scan implements ActionListener{
468 | private IContextMenuInvocation invocation;
469 |
470 | public scan(IContextMenuInvocation invocation) {
471 | this.invocation = invocation;
472 | }
473 |
474 | @Override
475 | public void actionPerformed(ActionEvent event) {
476 | try {
477 | IHttpRequestResponse[] messages = invocation.getSelectedMessages();
478 | for (IHttpRequestResponse message:messages) {
479 | byte[] req = message.getRequest();
480 | IRequestInfo analyzedRequest = helpers.analyzeRequest(req);
481 | URL url = analyzedRequest.getUrl();
482 | IHttpService service = message.getHttpService();
483 | boolean useHttps = service.getProtocol().equalsIgnoreCase("https");
484 | /******************发起扫描任务************************/
485 | callbacks.doActiveScan(service.getHost(), service.getPort(), useHttps, req);
486 | stdout.println(url.toString()+"被加入了扫描队列");
487 | /******************发起爬行任务************************/
488 | callbacks.sendToSpider(url);
489 |
490 | /******************查询URL是否在scope中************************/
491 | if (callbacks.isInScope(url)) {
492 | /******************从scope中移除************************/
493 | callbacks.excludeFromScope(url);
494 | }
495 | URL shortUrl = new URL(service.toString());
496 | /******************加入scope中************************/
497 | callbacks.includeInScope(shortUrl);
498 | }
499 | } catch (Exception e) {
500 | callbacks.printError(e.getMessage());
501 | }
502 | }
503 | }
504 | }
505 | ```
506 |
507 | ### IMessageEditorTab
508 |
509 | 用途:有点像repeater模块,主要就是用来展示和编辑request和response。
510 |
511 |
512 |
513 |
514 |
515 | ```java
516 | //添加一个editor,用于显示HTTP数据包
517 | IMessageEditor editor = BurpExtender.getCallbacks().createMessageEditor(this, false);
518 | contentPane.add(editor.getComponent(), BorderLayout.CENTER);
519 |
520 | JButton btnNewButton = new JButton("displayRequest");
521 | btnNewButton.addActionListener(new ActionListener() {
522 | public void actionPerformed(ActionEvent e) {
523 | editor.setMessage(getRequest(), true);//调用IMessageEditorController的函数来显示请求包
524 | }
525 | });
526 | ```
527 |
528 |
529 |
530 | ```java
531 | public Tags(final IBurpExtenderCallbacks callbacks, String name) {
532 | this.callbacks = callbacks;
533 | this.tagName = name;
534 |
535 | SwingUtilities.invokeLater(new Runnable() {
536 |
537 |
538 | @Override
539 | public void run() {
540 |
541 |
542 | jPanel = new JPanel();
543 |
544 | splitPanel = new JSplitPane(0);
545 | splitPanel.setDividerLocation(0.5D);
546 | jTabbedPane = new JTabbedPane();
547 | jTabbedPane1 = new JTabbedPane();
548 | request = callbacks.createMessageEditor(Tags.this, false);
549 | response = callbacks.createMessageEditor(Tags.this,false);
550 | jTabbedPane.addTab("Request",request.getComponent());
551 | jTabbedPane.addTab("Response",response.getComponent());
552 | splitPanel.add(jTabbedPane);
553 | splitPanel.setTopComponent(jTabbedPane1);
554 | splitPanel.setBottomComponent(jTabbedPane);
555 | jPanel.add(splitPanel);
556 |
557 | // // 设置自定义组件并添加标签
558 | callbacks.customizeUiComponent(jPanel);//根据Burp的UI样式自定义UI组件,包括字体大小、颜色、表格行距等。
559 | callbacks.addSuiteTab(Tags.this);
560 |
561 | }
562 | });
563 | }
564 | ```
565 |
566 |
567 |
568 | ### IBurpCollaboratorClientContext
569 |
570 | 如何与burp自生DNSlog进行交互
571 |
572 | ```java
573 | IBurpCollaboratorClientContext ccc = callbacks.createBurpCollaboratorClientContext();
574 |
575 | //返回结果类似:053bsqoev8gezev8oq59zylgv71xpm, 053bsqoev8gezev8oq59zylgv71xpm.burpcollaborator.net
576 | public static String[] getFullDnsDomain(){
577 | String subdomain = ccc.generatePayload(false);
578 | String interactionID = subdomain;
579 | String server = ccc.getCollaboratorServerLocation();
580 | String fullPayload = subdomain+"."+server;
581 | String[] result = {interactionID,fullPayload};
582 | return result;
583 | }
584 | ```
585 |
586 |
587 |
588 |
589 |
590 | ## 插件解析
591 |
592 | 解析P喵呜大佬写的,shiro被动扫描插件。
593 |
594 | 前提:了解shiro反序列化漏洞原理,了解burp插件开发。
595 |
596 |
597 |
598 | https://github.com/pmiaowu/BurpShiroPassiveScan.git
599 |
600 |
601 |
602 | 找到入口类开始看,发现继承了IScannerCheck。
603 |
604 | IScannerCheck和IHttpListener有点相似,简单说下我理解的区别,如有不对欢迎指出。
605 |
606 | IHttpListener有点像proxy抓包拦截,能在收到某某request或者response就马上进行处理。而IScannerCheck则是发现你请求了某某url之后,单独对这个url进行漏洞扫描。
607 |
608 | 
609 |
610 |
611 |
612 | 先看关键注册方法,主要是一些初始化操作
613 |
614 | 
615 |
616 |
617 |
618 | 初始化关键对象
619 |
620 | 
621 |
622 | 初始化两个用于存放扫描url和domain的对象
623 |
624 | 
625 |
626 | 这两个对象主要是用了单例各种维护一个了map
627 |
628 | 
629 |
630 |
631 |
632 | 初始化插件界面,设置插件名称,注册扫描,打印一些作者信息。
633 |
634 | 
635 |
636 |
637 |
638 | 看看插件界面代码,就是表格加req和res
639 |
640 | 
641 |
642 |
643 |
644 | 接下来看,被动扫描。核心代码都在这。
645 |
646 | 
647 |
648 | 先判断域名有没有被扫描过
649 |
650 | 
651 |
652 | 再检测url有没有被扫描过(此处有重复代码的感觉,离谱)
653 |
654 | 
655 |
656 |
657 |
658 | 都没有扫描过,则加到前面初始化的map里面去。(大佬居然管这个叫数组,有点离谱,问题不大)
659 |
660 | 
661 |
662 |
663 |
664 | 开始核心的各种检测
665 |
666 | 
667 |
668 |
669 |
670 | 后面就是一些添加issues,爆破key什么的,不想看了。
671 |
672 | 突然发现写这种东西,对我完全没任何好处。而且还及其浪费时间,疯狂截图,各种写。
673 |
674 |
675 |
676 | ## 致谢
677 |
678 | 感谢大佬开源,向大佬致敬。
679 |
680 | - https://github.com/bit4woo/burp-api-drops
681 | - https://xz.aliyun.com/t/7065
682 |
--------------------------------------------------------------------------------