(接上文)
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是经过编码处理的,所以,未经解码时,看上去就是一团乱码。
HTTP History中的“Decrypted”选项卡
此外,Repeater中也会用到它:
Repeater中的“Decrypted”选项卡
如果我们修改选项卡中的内容,它将更新原始消息:
Base64解码/编码
看起来很不错。那么,就让我们继续考察解密事项吧。
Python原型
在典型的安全审计过程中,我通常会创建一个原型来解密示例消息。在本例中,我将创建一个Python原型,而不是Go原型,为啥?换换口味呗。相关文件,可以从1-prototype中找到。
Python没有提供现成的AES。当然,这方面有大量的库可以供我们使用,不过,其中大多数都是基于OpenSSL或其他C库。这一点非常关键,稍后将详细介绍。
在上一篇文章中,我使用的是Pycrypto。后来,有位读者提示说应该使用一个新一点的代码库。虽然这是一个不错的建议,但并不能解决我们的主要问题。实际上,我不需要安装第三方库来获得AES这样的基本功能。这里,我将使用cryptography.io,您可以通过pip进行安装。
如果您对AES-CFB及其工作机制感兴趣的话,建议阅读下文:
这里的Python原型与上面链接的文章中的原型非常相似。
使用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脚本。如果使用本机代码可执行文件的话,速度会相应的快一些。
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
在这个版本的插件中,我只是将旧的加密/解密函数进行了替换。
这个版本要快得多。
Jython版本在速度上要快得多
这样,下一次您就不必用Java编写插件了。拿走,不谢。
我们今天在这里学到了些什么?
· 您可以在Burp插件中使用外部程序/实用程序/服务,但这样的话,会牺牲一些速度。
· 利用Burp插件,我们可以像测试“普通”Web服务一样来测试加密/编码的流量。
· 可以借助Jython的标准库来使用Java类。
· 库/模块不必位于插件模块路径中,它们可以存储在原始插件的旁边。
· 若插件涉及加密,则不必用Java编写。