文章总结: 本文详细介绍了使用BurpSuiteMontoyaAPI编写HttpHandler插件的方法,重点演示如何自动修改HTTP请求和响应中的SHA256签名验证。通过具体代码示例展示了拦截流量、计算摘要、更新请求头等关键步骤,并对比了新老API在WebSocket支持等方面的改进。文章提供了完整的插件开发流程和实战操作指导。 综合评分: 85 文章分类: 渗透测试,安全工具,WEB安全,安全开发,实战经验
扩展Burp Suite——Montoya API实战(第二篇)
幻泉之洲
2026年5月7日 13:02 北京
在小说阅读器读本章
去阅读
本文介绍怎么用Montoya API写HttpHandler插件,实现自动修改HTTP请求和响应。以自动计算SHA256签名为例,手把手教你写代码。顺便聊聊老版API的坑和新版API的改进。
检查和修改HTTP请求与响应
今天我们来聊聊渗透测试中最常用的一类插件——HttpHandler(旧版API里叫HttpListener)。这玩意儿能拦截Burp Suite里所有工具发出的HTTP请求和回来的响应,让你随便改。虽然其他类型的插件也能针对特定工具做流量修改,但HttpHandler能覆盖所有进出流量。
新版的Montoya API终于支持WebSocket流量的检查和修改了。这个功能我等了很久,可以说是最期待的一个。以前没有这个API的时候,我们写插件处理HTTP请求的加密、签名、编码啥的都挺顺手,但就怕遇到用WebSocket的应用。一旦碰到,Burp Suite只能在标准场景下凑合用,复杂点的就得切换到更麻烦的工具。
举个实际点的例子:有个应用在HTTP Header里放了个SHA256哈希,内容是对请求体做的摘要。更常见的可能是HMAC-SHA,但为了简单我们就用SHA256。如果我们拦截一个请求,改了body里的参数,但没重新算哈希,后端就会拒收。在Proxy和Repeater里,可以手动每次重算SHA256,但太慢了,特别烦人。
如果用了Intruder或Scanner,Burp发出的每个请求都会因为哈希错误被后端干掉。最好的办法就是写个HttpHandler插件,让它自动重新生成签名。
下面我们就动手写一个。先拿Python+Flask搭个简单的后端做演示。
import flask from flask import request from hashlib import sha256
app = flask.Flask(__name__)
@app.route(‘/’, methods=[‘GET’, ‘POST’]) def handle_request(): hash = request.headers.get(‘Hash’) body = request.get_data() calculated_hash = sha256(body).hexdigest() if(hash.strip() == calculated_hash): data = request.form.get(‘data’) return data else: return(“Invalid signature!”)
app.run(host=”127.0.0.1″, port=5000, debug=True)
逻辑很简单:从Header里取出Hash,算body的SHA256,对比。一致就返回data参数的值,否则报错。
下面这个请求的哈希是对的:
POST / HTTP/1.1 Host: localhost Content-Length: 19 Hash: 0bae7db0e4ee21521569abf0b881349c7d1da125a49435f8ea0a733b1ef4be78 Content-Type: application/x-www-form-urlencoded
data=Attack+vector!
HTTP/1.1 200 OK Server: Werkzeug/2.3.6 Python/3.11.3 Date: Mon, 12 Jun 2023 16:05:29 GMT Content-Type: text/html; charset=utf-8 Content-Length: 14 Connection: close
Attack vector!
下面这个哈希是错的:
POST / HTTP/1.1 Host: localhost Content-Length: 21 Hash: 0bae7db0e4ee21521569abf0b881349c7d1da125a49435f8ea0a733b1ef4be78 Content-Type: application/x-www-form-urlencoded
data=Attack+vector+2!
HTTP/1.1 200 OK Server: Werkzeug/2.3.6 Python/3.11.3 Date: Mon, 12 Jun 2023 16:10:17 GMT Content-Type: text/html; charset=utf-8 Content-Length: 18 Connection: close
Invalid signature!
打开IDE,先搭个空插件的架子(参考第一部分[1])。
package org.fd.montoyatutorial;
import burp.api.montoya.BurpExtension; import burp.api.montoya.MontoyaApi; import burp.api.montoya.logging.Logging;
public class HttpHandlerExample implements BurpExtension {
MontoyaApi api; Logging logging;
@Override public void initialize(MontoyaApi api) {
// Save a reference to the MontoyaApi object this.api = api;
// api.logging() returns an object that we can use to print messages to stdout and stderr this.logging = api.logging();
// Set the name of the extension api.extension().setName(“Montoya API tutorial – HttpHandlerExample”);
// Print a message to the stdout this.logging.logToOutput(“*** Montoya API tutorial – HttpHandlerExample loaded ***”);
} }
修改请求和响应的API通过MontoyaApi对象的http方法访问。我们在initialize方法里注册一个HttpHandler,提供处理请求和响应的对象。调用http().registerHttpHandler方法,参数是我们自己实现HttpHandler接口的类。
这个接口有两个方法:handleHttpRequestToBeSent和handleHttpResponseReceived。
我们先定义HttpHandler对象,然后在初始化方法里传递它。IDE很好用,能自动生成接口方法的骨架,省得写一堆样板代码。看下面这个骨架,它拦截所有请求和响应,但什么也不做(或者直接抛异常):
package org.fd.montoyatutorial;
import burp.api.montoya.http.handler.*;
public class CustomHttpHandler implements HttpHandler { @Override public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent requestToBeSent) { return null; }
@Override public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) { return null; } }
现在修改一下,让请求和响应直接通过而不做修改。handleHttpRequestToBeSent需要返回RequestToBeSentAction对象,handleHttpResponseReceived返回ResponseReceivedAction。怎么创建这些对象?查文档。Montoya API里大部分是接口,但接口中通常有静态方法可以直接用。比如RequestToBeSentAction有个continueWith方法,接收一个请求(可选加注解对象)。同样ResponseReceivedAction也有continueWith。
package org.fd.montoyatutorial;
import burp.api.montoya.http.handler.*;
public class CustomHttpHandler implements HttpHandler { @Override public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent requestToBeSent) { return RequestToBeSentAction.continueWith(requestToBeSent); }
@Override public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) { return ResponseReceivedAction.continueWith(responseReceived); } }
现在在插件主类里注册这个handler:
package org.fd.montoyatutorial;
import burp.api.montoya.BurpExtension; import burp.api.montoya.MontoyaApi; import burp.api.montoya.logging.Logging;
public class HttpHandlerExample implements BurpExtension {
MontoyaApi api; Logging logging;
@Override public void initialize(MontoyaApi api) {
…
// Register our HttpHandler api.http().registerHttpHandler(new CustomHttpHandler());
} }
接下来实现真正的逻辑。给HttpListener类加个构造函数,保存MontoyaApi引用,方便后续操作。
public class CustomHttpHandler implements HttpHandler {
MontoyaApi api; Logging logging;
public CustomHttpHandler(MontoyaApi api) { // Save a reference to the MontoyaApi object this.api = api; // api.logging() returns an object that we can use to print messages to stdout and stderr this.logging = api.logging(); } […]
插件要做的事情:
- 检查请求是否包含Hash头,没有就原样返回。
- 提取请求体。
- 用SHA256签名请求体(Montoya API提供了CryptoUtils)。
- 用新摘要替换Hash头的旧值。
- 返回修改后的请求。
提取body和header、更新header,这些操作都在HttpRequestToBeSent接口里。计算SHA256用Burp Suite的utilities API(从MontoyaApi对象获取)。最终handleHttpRequestToBeSent的代码如下:
@Override public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent requestToBeSent) {
// Get a list of HTTP headers of the request List headers = requestToBeSent.headers();
// 1 – Check if the list contains an header named “Hash” (using Java streams, introduced in Java 8) if(headers.stream().map(HttpHeader::name).anyMatch(h -> h.trim().equals(“Hash”))) {
// 2 – Extract the body of the request, using “body” function of the HttpRequestToBeSent object ByteArray body = requestToBeSent.body();
// Get a reference to the CryptoUtils offered by Burp Suite CryptoUtils cryptoUtils = api.utilities().cryptoUtils();
// 3 – Calculate SHA256 hash ByteArray sha256hash = cryptoUtils.generateDigest(body, DigestAlgorithm.SHA_256);
// Convert SHA256 bytes to a HEX string, using a Java way String digest = String.format(“%064x”, new BigInteger(1, sha256hash.getBytes()));
// 4 – Set the hash in the “Hash” HTTP header, using withUpdatedHeader of the HttpRequestToBeSent object HttpRequest modifiedRequest = requestToBeSent.withUpdatedHeader(“Hash”,digest);
// 5 – Return the container object feeded with the modified request return RequestToBeSentAction.continueWith(modifiedRequest);
}
return RequestToBeSentAction.continueWith(requestToBeSent); }
第一步用了Java 8的stream,不熟悉的话可以写成传统循环:
boolean headerFound = false; for(int i=0;i < headers.size();i++) { if(headers.get(i).name().trim().equals(“Hash”)) { headerFound = true; break; } } if(headerFound) { … }
第二步,获取body的ByteArray,不是字符串。为了算哈希,需要用CryptoUtils.generateDigest。这个函数返回ByteArray,是个字节数组。想转成十六进制字符串,得自己写点代码。上面用的String.format(“%064x”, new BigInteger(1, sha256hash.getBytes())),注意sha256hash.getBytes()返回的是byte[]。这种方法对256位(32字节)的哈希有效,但其他长度的哈希就可能会丢前面零。更通用的做法是用javax.xml.bind.DatatypeConverter.printHexBinary,或者自己写字节转16进制循环。
更新Header用withUpdatedHeader方法,非常方便。它返回一个新的HttpRequest对象,保留其他所有内容,只替换指定头。如果头不存在,会新增(取决于具体实现?实际上withUpdatedHeader会更新或添加)。这样我们就得到了修改后的请求。
最后一步,用RequestToBeSentAction.continueWith(modifiedRequest)包装后返回。
把这个插件编译打包,加载到Burp里试试。IDE里选择Build Artifacts -> Build,找到生成的jar,然后到Burp的Extensions -> Installed里点Add添加。选择Java,选中jar文件。加载成功后,用Burp访问之前起好的Flask后端。发送请求时带上Hash头,插件会自动重新计算并更新哈希。Intruder、Scanner、Repeater都能自动处理。
要验证插件是否生效,可以改一下body参数,然后看返回是不是正常。如果哈希正确,后端会返回你传入的data值;如果插件没工作,就会报Invalid signature。
用WebSocket的时候,API用法类似,但用的是WebSocketHandler接口,注册方式不同。不过这篇文章太长了,WebSocket的例子咱们放下一期。
完整的示例代码(包括后端)可以上我的GitHub仓库下载[2]。
收工!
参考资料
Extending Burp Suite for fun and profit – The Montoya way – Part 2
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:幻泉之洲 《扩展Burp Suite——Montoya API实战(第二篇)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论