文章总结: 本文详细介绍了使用MontoyaAPI开发BurpSuite扩展处理WebSocket消息的方法,通过Flask-SocketIO示例演示了自动重新生成SHA-256哈希的插件开发流程。文章涵盖WebSocket协议特点、测试环境搭建、双重监听器注册机制以及消息篡改的具体实现,为渗透测试中WebSocket安全检测提供完整技术方案。 综合评分: 85 文章分类: WEB安全,安全工具,渗透测试,红队,安全开发
扩展Burp Suite——Montoya API实战(第三篇):WebSocket消息处理
幻泉之洲
2026年5月8日 14:56 北京
在小说阅读器读本章
去阅读
本文介绍了如何使用Montoya API开发Burp Suite扩展,实现对WebSocket消息的检查和篡改,通过一个实际案例演示了自动重新生成SHA-256哈希的插件开发过程。
这是系列教程的第三篇。之前我们学了怎么搞HTTP处理器——渗透测试里最常见的Burp扩展。现在该看看WebSocket了。
WebSocket:以前是痛点,现在有解了
WebSocket是个有状态的全双工协议,现代浏览器都支持,一般用在需要实时更新的应用里。几年前Burp Suite对WebSocket的支持很有限,我测试时得用OWASP ZAP来补位。后来Burp改进了,现在WebSocket在Proxy和Repeater里整合得不错。
说到扩展,WebSocket支持是Montoya API才引入的——这是大家选择新API的另一个理由,尽管它还不支持Python和Ruby。这些年我碰到过很多用WebSocket的应用,想找个现成的扩展经常找不到(HTTP的倒是很多),这确实是个痛点。现在好了,我们可以像处理HTTP那样检查和篡改WebSocket消息了。
测试环境搭起来
老规矩,先搞个测试场景。我改了一个Flask-SocketIO的示例,用它跑个简单的聊天服务器,通信走WebSocket。
改过的代码放在我的GitHub仓库里。改动包括:
- 把异步模式设为eventlet(走WebSocket的那种),加了必要模块
- 去掉了ping/pong的来回发送,减少消息量
- 给“Echo”功能加了哈希校验:前端JS计算SHA-256,后端验证,不对就返回错误
后端的核心逻辑就这几行:
@socketio.event def my_event(message): session[‘receive_count’] = session.get(‘receive_count’, 0) + 1 if ‘hash’ in message: calculated_hash = sha256(message[‘data’].encode(‘utf-8’)).hexdigest() if(message[‘hash’] == calculated_hash): emit(‘my_response’, {‘data’: message[‘data’], ‘count’: session[‘receive_count’]}) else: emit(‘my_response’, {‘data’: ‘INVALID SIGNATURE’, ‘count’: session[‘receive_count’]}) else: session[‘receive_count’] = session.get(‘receive_count’, 0) + 1 emit(‘my_response’, {‘data’: message[‘data’], ‘count’: session[‘receive_count’]})
跑起来很简单:
- (可选)创建并激活Python虚拟环境
- 安装依赖:
pip install -r requirements.txt - 启动:
python app.py - 访问
http://localhost:5000/
在网页上发条消息试试:
Burp里可以看到浏览器发送了包含“test”字符串及其SHA256哈希的消息,也收到了同样的字符串回复。注意:WebSocket没有请求-响应的对应关系,只有双方互发的消息。
把消息发给Repeater,改了data字段的值但不重算签名,后端就会返回“INVALID SIGNATURE”。
好,场景有了。现在我们来写个Burp扩展,功能类似上一篇的HTTP处理器——自动重新生成所有从浏览器发往后端的WebSocket消息的哈希。这样就能拦截篡改消息,用Repeater时也不用手动算哈希了。
开发WebSocket扩展:两个监听器
从第一部分的Hello World项目起步,这次要用到MontoyaApi接口里的WebSocket相关方法。
像之前一样,我们需要注册一个监听器,等特定事件(这里是WebSocket创建)发生时触发。
WebSocket的处理比HTTP稍微复杂一点,需要注册两个监听器。HTTP场景下我们只有一个监听器,在请求/响应进出Burp时被通知。WebSocket不行,得先设一个监听器,等某个工具创建WebSocket时(通过特殊的HTTP Upgrade请求,详情看维基百科)被调用。当WebSocket创建后,我们的监听器拿到参数,再注册第二个监听器处理这条连接上的消息。
先搞第一个监听器。实现WebSocketCreatedHandler接口:
import burp.api.montoya.MontoyaApi; import burp.api.montoya.logging.Logging; import burp.api.montoya.websocket.WebSocketCreated;
public class CustomWebsocketCreatedHandler implements WebSocketCreatedHandler { MontoyaApi api; Logging logging;
public CustomWebsocketCreatedHandler(MontoyaApi api) { this.api = api; this.logging = api.logging(); }
@Override public void handleWebSocketCreated(WebSocketCreated webSocketCreated) { // 在这里做点啥 } }
传给handleWebSocketCreated的WebSocketCreated对象提供了三个方法:
-
toolSource():哪个工具创建的WebSocket
-
upgradeRequest():对应的Upgrade HTTP请求
-
webSocket():获取实际的WebSocket引用,我们需要它来注册消息监听器
webSocket()返回的对象有个注册监听器的方法,看文档:
现在要创建第二个监听器,实现MessageHandler接口。它有两个方法(第三个可选):处理二进制消息和处理文本消息。
import burp.api.montoya.MontoyaApi; import burp.api.montoya.logging.Logging; import burp.api.montoya.websocket.*;
public class CustomWebsocketHandler implements MessageHandler { MontoyaApi api; Logging logging;
public CustomWebsocketHandler(MontoyaApi api) { this.api = api; this.logging = api.logging(); }
@Override public TextMessageAction handleTextMessage(TextMessage textMessage) { return TextMessageAction.continueWith(textMessage); }
@Override public BinaryMessageAction handleBinaryMessage(BinaryMessage binaryMessage) { return BinaryMessageAction.continueWith(binaryMessage); } }
这两个方法分别返回TextMessageAction和BinaryMessageAction。看文档,有几个静态方法可以创建这些对象:
现在把第二个监听器注册到第一个监听器里:
public class CustomWebsocketCreatedHandler implements WebSocketCreatedHandler { … @Override public void handleWebSocketCreated(WebSocketCreated webSocketCreated) { WebSocket websocket = webSocketCreated.webSocket(); websocket.registerMessageHandler(new CustomWebsocketHandler(api)); } }
然后在主类的initialize方法里注册创建监听器:
public class WebsocketExample implements BurpExtension { … @Override public void initialize(MontoyaApi api) { … api.websockets().registerWebSocketCreatedHandler(new CustomWebsocketCreatedHandler(api)); } }
骨架好了。先写点调试代码,打印经过的消息:
@Override public TextMessageAction handleTextMessage(TextMessage textMessage) { String payload = textMessage.payload(); Direction direction = textMessage.direction(); if(direction == Direction.CLIENT_TO_SERVER) { logging.logToOutput(“T ==========>>”); } else { logging.logToOutput(“T <<==========”); } logging.logToOutput(payload); logging.logToOutput(“”); return TextMessageAction.continueWith(textMessage); }
@Override public BinaryMessageAction handleBinaryMessage(BinaryMessage binaryMessage) { ByteArray payload = binaryMessage.payload(); Direction direction = binaryMessage.direction(); if(direction == Direction.CLIENT_TO_SERVER) { logging.logToOutput(“B ==========>>”); } else { logging.logToOutput(“B <<==========”); } logging.logToOutput(base64Utils.encodeToString(payload)); logging.logToOutput(“”); return BinaryMessageAction.continueWith(binaryMessage); }
编译加载插件(参考第一部分的流程),刷新测试页面,点“Echo”功能,看看输出:
截图里能看到我们的消息(内容“aaaa”)、哈希,还有后端的回复。都是文本消息,所以我们只在handleTextMessage里搞哈希逻辑。二进制消息流程一样。
逻辑跟上一篇类似,不过这次用正则提取数据:
- 只处理包含“my_event”且方向为客户端到服务器的消息
- 用正则提取data和hash字段
- 用Burp的加密工具重新计算SHA-256
- 替换旧哈希
- 打印改动后的消息(后面会解释为啥要打印)
- 把修改后的消息发往后端
@Override public TextMessageAction handleTextMessage(TextMessage textMessage) { String payload = textMessage.payload(); Direction direction = textMessage.direction();
if(payload.contains(“my_event”) && direction == Direction.CLIENT_TO_SERVER) { Pattern p = Pattern.compile(“.*\\”data\\”\\:.*\\”([^\\”]+)\\”.*\\”hash\\”\\:.*\\”([^\\”]+)\\””); Matcher m = p.matcher(payload); if(m.find() && m.groupCount() == 2) { ByteArray sha256hash = cryptoUtils.generateDigest(ByteArray.byteArray(m.group(1)), DigestAlgorithm.SHA_256); String digest = String.format(“%064x”, new BigInteger(1, sha256hash.getBytes())); String newMessage = payload.replaceAll(m.group(2), digest); logging.logToOutput(“* Message with updated hash:”); logging.logToOutput(newMessage); return TextMessageAction.continueWith(newMessage); } else { logging.logToOutput(“Data and hash not found. Returning original message.”); return TextMessageAction.continueWith(textMessage); } } return TextMessageAction.continueWith(textMessage); }
构建插件,刷新页面,发“aaaa”,把消息丢Repeater里,改成“bbbb”但不算哈希,看看后端认不认:
后端返回“bbbb”而不是“INVALID SIGNATURE”,说明插件生效了。
和HTTP一样,插件在消息离开发送前修改了哈希,所以Repeater里看到的还是原始“aaaa”的哈希。HTTP场景下可以用Logger看实际发送的请求,但WebSocket目前没有Logger。所以我们在第5步打印了修改后的消息。在stdout里能看到实际发往后端的消息(包括没篡改的“aaaa”,因为插件重新算了所有哈希):
好,这一集就到这里。现在我们的扩展可以检查和篡改WebSocket消息了。代码和改过的Flask示例都在我的GitHub仓库里。
下一集我们聊聊怎么添加请求/响应消息编辑器标签页,用来处理自定义编码或加密——这跟之前的方法不太一样,是我这些年经常用的一种扩展类型。
下次见!
参考资料
[1] https://hnsecurity.it/blog/extending-burp-suite-for-fun-and-profit-the-montoya-way-part-3/
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:幻泉之洲 《扩展Burp Suite——Montoya API实战(第三篇):WebSocket消息处理》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。




![[技术深潜]潜伏9年的Copy-Fail:内核0-Day通杀Root](/images/random/titlepic/4.jpg)





评论