文章总结: 本文详细介绍了如何使用BurpSuiteMontoyaAPI创建自定义选项卡来处理AES加密的HTTP请求和响应。通过具体移动应用测试场景,演示了实现HttpRequestEditor/HttpResponseEditor插件的完整流程,包括解密显示、编辑后自动重新加密等功能。文章提供了完整的Flask后端示例和Java插件代码实现,帮助安全测试人员高效处理加密流量。 综合评分: 85 文章分类: WEB安全,安全工具,应用安全,移动安全,安全开发
扩展Burp Suite:用Montoya API给你的工具加个”解密”标签(第四部分)
幻泉之洲
2026年5月9日 10:02 北京
在小说阅读器读本章
去阅读
本文教你用Burp Suite Montoya API创建自定义选项卡,一键解密HTTP请求和响应。通过一个AES加密的移动应用场景,手把手实现HttpRequestEditor/HttpResponseEditor插件,让安全测试不用每次手动加解密。
Hi,各位。
今天聊怎么给Burp Suite界面加自定义组件,方便处理各种加密场景。重点放在创建新选项卡,用来处理HTTP请求和响应。
老规矩,先讲一个具体场景。假设你在测一个移动应用,它对HTTP请求体和响应体都做了AES加密。客户端发请求前用AES加密体,收到响应后用同样方式解密——后端也是这么干的。移动端常见这情况,但Web端也有,浏览器用JavaScript库做加解密。
我给你写个简单的Flask Python应用,演示这个过程(加解密代码直接抄的StackOverflow,需要的包:flask和pycryptodome):
import flask from flask import request import base64 from Crypto import Random from Crypto.Cipher import AES
app = flask.Flask(__name__)
bs = AES.block_size key = bytes.fromhex(“eeb27c55483270a92682dab01b85fdea”) iv = bytes.fromhex(“ecbc1312cfdc2a0e1027b1eaf577dce8”)
def encrypt(raw): raw = _pad(raw) cipher = AES.new(key, AES.MODE_CBC, iv) return base64.b64encode(cipher.encrypt(raw.encode()))
def decrypt(enc): enc = base64.b64decode(enc) cipher = AES.new(key, AES.MODE_CBC, iv) return _unpad(cipher.decrypt(enc)).decode(‘utf-8’)
def _pad(s): return s + (bs – len(s) % bs) * chr(bs – len(s) % bs)
def _unpad(s): return s[:-ord(s[len(s)-1:])]
@app.route(‘/’, methods=[‘POST’]) def handle_request(): encrypted_body = request.get_data() decrypted_body = decrypt(encrypted_body) response = “Your request was: \”” + decrypted_body + “\”” encrypted_response = encrypt(response) return encrypted_response
app.run(host=”127.0.0.1″, port=5000, debug=True)
这个应用接收加密的请求体(AES/CBC,固定Key和IV,Base64编码),返回同样加密的响应。所以请求和响应都是密文,没Burp插件根本没法测。
懒得写个移动应用demo,直接在Burp Repeater里伪造一个HTTP请求。用CyberChef加密请求体,再解密响应。等插件写好,这些脏活就交给插件了。
先用固定Key和IV加密一个测试句子:
然后通过Burp Repeater发给后端:
再用CyberChef解密响应:
好,后端代码没毛病。
现在想想怎么解决问题。目标是方便地分析应用,不用每次输完payload手动加密请求、解密响应看攻击结果。
一个思路是实现HttpHandler插件(参考本系列第二部分),透明地解密进入Burp的请求,再自动重新加密发送出去。这样Burp里看到的流量就像没加密一样,好处是Scanner也能无缝工作。但我通常只在Scanner和Intruder用这种插件,Proxy和Repeater不用。为啥?我想保留原始流量记录,而不是经过插件处理后的,方便后续回看。
下面说两种替代方案:用HttpRequestEditor/HttpResponseEditor插件(我常用的),和ContextMenuItem插件(灵活性差点,但适合请求格式变化大的场景)。这两种都要从UserInterface对象注册,UserInterface从MontoyaApi拿到。
这篇文章重点讲HttpRequestEditor/HttpResponseEditor。ContextMenuItem留到下一篇。
这类插件可以在Burp显示请求/响应的区域加一个选项卡。点一下,插件就解密请求并展示解密版本(响应同理)。要是在支持修改的工具里(比如Repeater或Intercept),你还能修改解密后的内容,插件自动重新加密再发出去。这样既保留了原始流量,又像没有加密层一样工作。实际效果长这样:
从第一部分的Hello World框架开始:
package org.fd.montoyatutorial;
import burp.api.montoya.BurpExtension; import burp.api.montoya.MontoyaApi; import burp.api.montoya.logging.Logging;
public class HttpRequestResponseEditorExample implements BurpExtension {
MontoyaApi api; Logging logging;
@Override public void initialize(MontoyaApi api) { this.api = api; this.logging = api.logging(); api.extension().setName(“Montoya API tutorial – HttpRequestResponseEditorExample”); this.logging.logToOutput(“*** Montoya API tutorial – HttpRequestResponseEditorExample loaded ***”); // TODO – 注册监听器 } }
扩展需要注册两个监听器,一个处理请求,一个处理响应。看文档:
每个监听器需要实现特定接口的对象:
两个接口都很简单,每个只需要一个方法。我打算用一个Java类同时实现两个接口,当然你也可以分开写。
package org.fd.montoyatutorial;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.ui.editor.extension.*;
public class CustomHttpRequestResponseEditor implements HttpRequestEditorProvider, HttpResponseEditorProvider {
MontoyaApi api;
public CustomHttpRequestResponseEditor(MontoyaApi api) { this.api = api; }
@Override public ExtensionProvidedHttpRequestEditor provideHttpRequestEditor(EditorCreationContext creationContext) { // TODO }
@Override public ExtensionProvidedHttpResponseEditor provideHttpResponseEditor(EditorCreationContext creationContext) { // TODO } }
这两个方法分别返回ExtensionProvidedHttpRequestEditor和ExtensionProvidedHttpResponseEditor对象。看名字你就知道,它们负责创建和返回一个图形选项卡。好消息是Burp API提供了创建选项卡的方法,不用自己跟Java图形库死磕(谢天谢地)。我们只需要用API创建选项卡,在里面实现加解密逻辑。
回到provideHttpRequestEditor和provideHttpResponseEditor,它俩的参数都是EditorCreationContext,包含当前请求/响应的上下文信息(哪个Burp工具生成的,是否可编辑):
下面看怎么创建ExtensionProvidedHttpRequestEditor对象(只讲请求部分,响应完全一样,代码在GitHub仓库里都有)。
需要实现一个接口,定义以下方法:
- caption:返回自定义选项卡的名字(示例里叫”Decrypted”)。
- isEnabledFor:根据当前请求和上下文决定是否显示这个选项卡。
- uiComponent:返回实际的UI组件。不用写Swing代码,Burp有现成方法生成选项卡。
- setRequestResponse:在这里生成选项卡的内容(比如解密请求体放进去)。
- isModified:用户有没有修改过选项卡内容。
- getRequest:当用户离开自定义选项卡回到默认选项卡,或者发送请求时调用。如果用户修改了内容,在这里把编辑后的内容加密,构建新请求。
- selectedData:返回用户在选项卡中选中的数据(如果有)。Burp API生成的选项卡自带方法处理这个,我们这个插件不需要让用户选中部分请求。
先写个骨架,把简单方法实现,然后处理setRequestResponse和getRequest——这里有加解密逻辑。
先写构造函数:
public class CustomHttpRequestEditorTab implements ExtensionProvidedHttpRequestEditor {
static String keyHex = “eeb27c55483270a92682dab01b85fdea”; static String ivHex = “ecbc1312cfdc2a0e1027b1eaf577dce8”;
MontoyaApi api; Logging logging; EditorCreationContext creationContext; RawEditor requestEditorTab; Base64Utils base64Utils;
public CustomHttpRequestEditorTab(MontoyaApi api, EditorCreationContext creationContext) { this.api = api; this.creationContext = creationContext; this.logging = api.logging(); this.base64Utils = api.utilities().base64Utils(); if (creationContext.editorMode() == EditorMode.READ_ONLY) { requestEditorTab = api.userInterface().createRawEditor(EditorOptions.READ_ONLY); } else { requestEditorTab = api.userInterface().createRawEditor(); } } […]
构造函数传入MontoyaApi和上下文,保存一些工具对象。然后创建RawEditor对象——这就是我们的图形选项卡。根据当前请求是否只读(比如History里只读,Repeater或Intercept里可读写),创建只读或可读写编辑器。RawEditor提供了很多方法,后面会用到。
现在实现CustomHttpRequestEditorTab的其他方法:
[…] @Override public boolean isEnabledFor(HttpRequestResponse requestResponse) { // 始终启用 return true; }
@Override public String caption() { return “Decrypted”; }
@Override public Component uiComponent() { return requestEditorTab.uiComponent(); }
@Override public Selection selectedData() { if(requestEditorTab.selection().isPresent()) { return requestEditorTab.selection().get(); } else { return null; } }
@Override public boolean isModified() { return requestEditorTab.isModified(); } […]
这些方法都很简单,因为RawEditor已经实现了底层逻辑。
下面实现setRequestResponse,负责解密内容并放进选项卡:
static String keyHex = “eeb27c55483270a92682dab01b85fdea”; static String ivHex = “ecbc1312cfdc2a0e1027b1eaf577dce8”; HttpRequestResponse currentRequestResponse; […] @Override public void setRequestResponse(HttpRequestResponse requestResponse) { HttpRequest request = requestResponse.request(); ByteArray body = request.body(); // Base64解码 ByteArray decodedBody = this.base64Utils.decode(body); this.currentRequestResponse = requestResponse; try { byte[] iv = HexFormat.of().parseHex(this.ivHex); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); byte[] key = HexFormat.of().parseHex(this.keyHex); SecretKey SecKey = new SecretKeySpec(key, 0, key.length, “AES”); Cipher aesCipher = Cipher.getInstance(“AES/CBC/PKCS5Padding”); aesCipher.init(Cipher.DECRYPT_MODE, SecKey, ivParameterSpec); byte[] decryptedBody = aesCipher.doFinal(decodedBody.getBytes()); this.requestEditorTab.setContents(ByteArray.byteArray(decryptedBody)); } catch (Exception e) { this.logging.logToError(e); } } […]
方法很简单:取出请求体,Base64解码,再用固定Key和IV解密,最后把结果set到选项卡里。同时保存HttpRequestResponse对象,后面修改时需要用它重建请求。
最后是getRequest,用户修改解密内容后,负责重新加密并构建新请求:
[…] @Override public HttpRequest getRequest() { if(isModified()) { ByteArray newBody = requestEditorTab.getContents(); try { byte[] iv = HexFormat.of().parseHex(this.ivHex); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); byte[] key = HexFormat.of().parseHex(this.keyHex); SecretKey SecKey = new SecretKeySpec(key, 0, key.length, “AES”); Cipher aesCipher = Cipher.getInstance(“AES/CBC/PKCS5Padding”); aesCipher.init(Cipher.ENCRYPT_MODE, SecKey, ivParameterSpec); byte[] encryptedBody = aesCipher.doFinal(newBody.getBytes()); ByteArray encodedBody = this.base64Utils.encode(ByteArray.byteArray(encryptedBody)); HttpRequest oldRequest = this.currentRequestResponse.request(); HttpRequest newRequest = oldRequest.withBody(encodedBody); return newRequest; } catch (Exception e) { this.logging.logToError(e); return this.currentRequestResponse.request(); } } else { return this.currentRequestResponse.request(); } } […]
代码跟之前类似,方向相反:取出修改后的内容,加密并Base64编码,替换到原始请求体里。
响应的CustomHttpResponseEditorTab几乎一模一样,就不贴代码了,GitHub仓库里有完整版。唯一的区别是我用了拷贝粘贴来保持示例清晰,实际写代码时最好避免重复。
最后,把前面写的类注册到插件里:
public class CustomHttpRequestResponseEditor implements HttpRequestEditorProvider, HttpResponseEditorProvider { MontoyaApi api; public CustomHttpRequestResponseEditor(MontoyaApi api) { this.api = api; } @Override public ExtensionProvidedHttpRequestEditor provideHttpRequestEditor(EditorCreationContext creationContext) { return new CustomHttpRequestEditorTab(api, creationContext); } @Override public ExtensionProvidedHttpResponseEditor provideHttpResponseEditor(EditorCreationContext creationContext) { return new CustomHttpResponseEditorTab(api, creationContext); } }
public class HttpRequestResponseEditorExample implements BurpExtension { […] @Override public void initialize(MontoyaApi api) { […] CustomHttpRequestResponseEditor customHttpRequestResponseEditor = new CustomHttpRequestResponseEditor(api); api.userInterface().registerHttpRequestEditorProvider(customHttpRequestResponseEditor); api.userInterface().registerHttpResponseEditorProvider(customHttpResponseEditorProvider); } }
搞定!编译运行插件,在请求和响应里都会多一个”Decrypted”选项卡,点开就能看到解密后的内容:
你可以在里面修改解密后的内容,然后点击”Send”或者切回Raw选项卡,插件会自动帮你重新加密:
这样,手动测试时既能保留原始请求/响应记录,又能像没加密一样工作。再加上HttpListener插件处理Scanner和Intruder,完美。
下一章咱们用ContextMenuItem插件来处理同样的场景,换个玩法。
参考资料
[1] https://hnsecurity.it/blog/extending-burp-suite-for-fun-and-profit-the-montoya-way-part-4/
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:幻泉之洲 《扩展Burp Suite:用Montoya API给你的工具加个”解密”标签(第四部分)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论