利用Python编写具有加密和解密功能的Burp插件 (下)

fanyeee 技术 2019年1月8日发布
Favorite收藏

导语:本文将为读者详细介绍如何利用Python编写具有加密和解密功能的Burp插件 。

接上文

getMessage

当选项卡可编辑时(例如,Repeater),可使用SetMessage更新请求。如果修改了Decrypted选项卡中的内容并切换回原选项卡,系统将使用该函数对其进行更新。

def getMessage(self):
    # determine whether the user modified the data
    if self._txtInput.isTextModified():
        # Parsia: if text has changed, encode it and make it the new body of the message
        modified = self._txtInput.getText()
        encodedModified = encode64(modified, self.helpers)
 
        # Parsia: create a new message with the new body and return that
        info = getInfo(self._currentMessage, True, self.helpers)
        headers = info.getHeaders()
        return self.helpers.buildHttpMessage(headers, encodedModified)
    else:
        # Parsia: if nothing is modified, return the current message so nothing gets updated
        return self._currentMessage

如果选项卡的文本已被修改,那么,IstExtModified()就会返回true。然后:

· 获取选项卡的修改内容:Modified=self._txtInput.getText()。

· 对其进行Base64解码:EncodedModified=Encode64(Modified,Self.Helpers)。

接下来,我将使用修改后的主体创建一条消息。这时,self._currentMessage = content就会发挥作用了。由于将该消息存到了相应的字段中,这样,就可以获取消息的头部,并将其添加到新消息中了。

获取消息的info:info = getInfo(self._currentMessage, True, self.helpers)。

我们不知道自己是否位于请求中,所以,我们假设自己总是在一个请求中。在这里,这并不重要,因为我们的请求和响应看起来是一样的(因为主体中没有命名参数和payload)。

获取消息头部:headers=info.getHeaders()。

使用原来的消息头部和新内容合成新的消息:self.helpers.BuildHttpMessage(Headers,EncodedModified)。

最后,如果没有发生任何变动的话,则返回未经修改的消息。

解码器在行动

该插件需要对收到的数据进行Base64解码,这是因为payload是经过编码处理的,所以,未经解码时,看上去就是一团乱码。

1.png

HTTP History中的“Decrypted”选项卡

此外,Repeater中也会用到它:

07-v0-repeater.png

Repeater中的“Decrypted”选项卡

如果我们修改选项卡中的内容,它将更新原始消息:

 1.gif

Base64解码/编码

看起来很不错。那么,就让我们继续考察解密事项吧。

Python原型

在典型的安全审计过程中,我通常会创建一个原型来解密示例消息。在本例中,我将创建一个Python原型,而不是Go原型,为啥?换换口味呗。相关文件,可以从1-prototype中找到。

Python没有提供现成的AES。当然,这方面有大量的库可以供我们使用,不过,其中大多数都是基于OpenSSL或其他C库。这一点非常关键,稍后将详细介绍。

在上一篇文章中,我使用的是Pycrypto。后来,有位读者提示说应该使用一个新一点的代码库。虽然这是一个不错的建议,但并不能解决我们的主要问题。实际上,我不需要安装第三方库来获得AES这样的基本功能。这里,我将使用cryptography.io,您可以通过pip进行安装。

如果您对AES-CFB及其工作机制感兴趣的话,建议阅读下文:

AES-CFB128: PyCrypto vs. Go 

这里的Python原型与上面链接的文章中的原型非常相似。

1.png

使用Python原型进行加密和解密

使用外部程序

既然我们的原型可以正常使用,那么,我们只需将其转换为一个Burp插件即可。需要注意的是,将代码转换为Burp插件后,原来好好的代码有可能突然就无法正常工作了。Burp提示说,它找不到cryptography。这是咋回事呢?

大多数依赖于OpenSSL或C扩展的库在Jython中都不受支持(因为它会将其视为依赖于CGO)。例如,按照这个GitHub issue的说法,cryptography是基于CFFI的。此外,Pycrypto也有类似的问题。

在处理这个问题的过程中,我学会了一些窍门。其中,第一个窍门是从依赖于外部可执行文件/程序的Burp插件那里学来的。利用这一个窍门,可以从Burp内部执行我们的原型,并通过命令行将payload传给它。也就是说,我们可以将其视为一个小型的CGI(CGI==公共网关接口)。这里有几个极其相似的例子,具体参见以下链接: 

· https://github.com/externalist/aes-encrypt-decrypt-burp-extender-plugin-example

· https://externalist.blogspot.com/2015/11/decrypting-modifying-encrypted-web-data.html

相关的文件,可以在2-external目录下面找到。

library.py

我在这个代码库中添加了3个新函数:

# runExternal executes an external python script with two arguments and returns the output
def runExternal(script, arg1, arg2):
    proc = Popen(["python", script, arg1, arg2], stdout=PIPE, stderr=PIPE)
    output = proc.stdout.read()
    proc.stdout.close()
    err = proc.stderr.read()
    proc.stderr.close()
    sys.stdout.write(err)
    return output
 
# encrypt uses the external prototype to encrypt the payload
def encrypt(payload):
    return runExternal("crypto.py", "encrypt", payload.tostring())
 
# decrypt uses the external prototype to decrypt the payload
def decrypt(payload):
    return runExternal("crypto.py", "decrypt", payload.tostring())

唯一复杂一点的地方在于,需要将来自getBody的payload以字符串的形式传递给Popen。getBody将返回一个由b(带符号字符)组成的array.array。在传递给runExternal并最终到达Popen之前,它将被转换为String()。

您可能好奇,为什么我会在crypto.py中使用Base64编码和解码技术呢?实际上,原因很简单,那就是将Base64编码值传递给命令行下的可执行文件非常简单。并且,这样做的话,因特殊字符而搞砸事情的几率要小得多。

extension.py

这个版本的扩展多少有点不同。在这里,我直接从library.py中调用encrypt和decrypt来完成繁重的工作。

    In setMessage: decryptedBody = decrypt(body)
    In getMessage: encryptedModified = encrypt(modified)
 
def setMessage(self, content, isRequest):
    if content is None:
        # clear our display
        self._txtInput.setText(None)
        self._txtInput.setEditable(False)
   
    # Parsia: if tab has content
    else:
        # get the body
        body = getBody(content, isRequest, self.helpers)
        # decrypt does the base64 decoding so the extension does not have to
        decryptedBody = decrypt(body)
        # set the body as text of message box
        self._txtInput.setText(decryptedBody)
        # this keeps the message box edit value to whatever it was
        self._txtInput.setEditable(self._editable)
   
    # remember the displayed content
    self._currentMessage = content
 
def getMessage(self):
    # determine whether the user modified the data
    if self._txtInput.isTextModified():
        # Parsia: if text has changed, encode it and make it the new body of the message
        modified = self._txtInput.getText()
        # encrypt and decrypt do the base64 transformation
        encryptedModified = encrypt(modified)
       
        # Parsia: create a new message with the new body and return that
        info = getInfo(self._currentMessage, True, self.helpers)
        headers = info.getHeaders()
        return self.helpers.buildHttpMessage(headers, encryptedModified)
    else:
        # Parsia: if nothing is modified, return the current message so nothing gets updated
        return self._currentMessage

crypto.py在行动

如果历史记录中已经有十几个请求了,则加载插件就需要花上几秒钟的时间。因为Burp需要为每个请求调用两次外部Python脚本。如果使用本机代码可执行文件的话,速度会相应的快一些。

1.gif

Repeater中的加密和解密操作

我们应该在什么情况下使用外部程序呢?

老实说,只要您愿意,啥时候都行。就我来说,我曾在下列情况下使用过外部程序:

该插件依赖于某些外部信源。例如远程Web服务或本地文件/数据库。

不过,更简单的方法是使用外部可执行文件,例如,当别人早已创建了一个可以完成解码/解密/解析工作的实用程序的时候。其中,反序列化就是一个很好的例子。

实际上,我们的目的很明确,那就是只要能完成任务就行,至于原型吗,用与不用倒是其次。这一点在我的日常工作中尤其明显,因为我从事的是咨询工作。别忘了,Burp插件是手段,而不是目的。在报告中显摆一个炫酷的插件自然很有面子,但是,调查结果才是最重要的。

使用Jython

对于前面介绍的技术来说,其运行速度确实太慢了点。不过,我们可以使用Jython,这样的话,速度能有明显提升。此外,许多人会用Java语言来编写与加密相关的插件。但是,我们可以只导入和使用那些可供所有Java插件使用的Java类。最后,相关文件可以在3-Jython目录中找到。

另外,如果读者希望熟悉一下Jython和Java的话,可以访问:

http://www.jython.org/jythonbook/en/1.0/JythonAndJavaIntegration.html

library.py

我正在向库中添加一些新函数,以便使用Java类来进行加密/解密。

Base64

Java8已经通过java.util.Base64来提供Base64编码和解码功能:

from java.util import Base64

encoded = Base64.getEncoder().encode(text)

decoded = Base64.getDecoder().decode(encoded)

AES/CFB/NOPADDING

要进行加密/解密,需要创建以下对象:

· java.crypto.Cipher用于提供加密功能(例如AES)

· javax.crypto.spec.IvParameterSpec用来创建初始化向量(IV)

· javax.crypto.spec.SecretKeySpec用于创建密钥

# encryptJython uses javax.crypto.Cipher to encrypt payload with key/iv
# using AES/CFB/NOPADDING
def encryptJython(payload, key, iv):
    aesKey = SecretKeySpec(key, "AES")
    aesIV = IvParameterSpec(iv)
    cipher = Cipher.getInstance("AES/CFB/NOPADDING")
    cipher.init(Cipher.ENCRYPT_MODE, aesKey, aesIV)
    encrypted = cipher.doFinal(payload)
    return Base64.getEncoder().encode(encrypted)
 
# decryptJython uses javax.crypto.Cipher to decrypt payload with key/iv
# using AES/CFB/NOPADDING
def decryptJython(payload, key, iv):
    decoded = Base64.getDecoder().decode(payload)
    aesKey = SecretKeySpec(key, "AES")
    aesIV = IvParameterSpec(iv)
    cipher = Cipher.getInstance("AES/CFB/NOPADDING")
    cipher.init(Cipher.DECRYPT_MODE, aesKey, aesIV)
    return cipher.doFinal(decoded)

extension.py

在这个版本的插件中,我只是将旧的加密/解密函数进行了替换。

这个版本要快得多。

1.gif

Jython版本在速度上要快得多

这样,下一次您就不必用Java编写插件了。拿走,不谢。

我们今天在这里学到了些什么?

· 您可以在Burp插件中使用外部程序/实用程序/服务,但这样的话,会牺牲一些速度。

· 利用Burp插件,我们可以像测试“普通”Web服务一样来测试加密/编码的流量。

· 可以借助Jython的标准库来使用Java类。

· 库/模块不必位于插件模块路径中,它们可以存储在原始插件的旁边。

· 若插件涉及加密,则不必用Java编写。

本文翻译自:https://parsiya.net/blog/2018-12-24-cryptography-in-python-burp-extensions/如若转载,请注明原文地址: https://www.4hou.com/technology/15502.html
点赞 0
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论