文章总结: 本文详细介绍了如何使用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给你的工具加个”解密”标签(第四部分)》