为何mitmproxy无法拦截SignalR客户端的WebSocket连接?
为测试项目网络环境,需要拦截SignalR的WebSocket连接,选用mitmproxy作为代理工具。尝试用block_list规则:~websocket:444和自定义Python插件都没能成功拦截——插件能正常拦截普通WebSocket连接,但对SignalR无效。SignalR客户端已配置代理,mitmproxy日志只显示大量CONNECT请求,插件没有预期的日志输出。怀疑是WebSocket的hop-by-hop协议特性导致问题,求解决思路。
自定义Python插件代码
import logging from mitmproxy import http from mitmproxy import ctx class BlockWebSockets: def block_websocket(self, flow: http.HTTPFlow) -> None: logging.info("Blocking WebSocket request to %s" % flow.request.pretty_url) flow.response = http.Response.make( 403, b"WebSocket connections are not allowed", {"Content-Type": "text/html"} ) def request(self, flow: http.HTTPFlow) -> None: if flow.request.headers.get("Upgrade", "").lower() == "websocket" or flow.websocket: self.block_websocket(flow) def websocket_start(self, flow: http.HTTPFlow) -> None: self.block_websocket(flow) def websocket_handshake(self, flow: http.HTTPFlow) -> None: self.block_websocket(flow) def http_connect(self, flow: http.HTTPFlow) -> None: if flow.request.headers.get("Upgrade", "").lower() == "websocket" or flow.websocket: self.block_websocket(flow) else: logging.info(bytes(flow.request.headers)) def websocket_message(self, flow: http.HTTPFlow) -> None: # If somehow WebSocket messages get through, this will kill them logging.info("Blocking WebSocket message to %s" % flow.request.pretty_url) flow.intercept() flow.kill() addons = [ BlockWebSockets() ] if __name__ == "__main__": from mitmproxy.tools.main import mitmdump mitmdump(["-s", __file__])
SignalR客户端代理配置代码
// Proxy is a WebProxy instance defined elsewhere in this class but basically: var Proxy = new WebProxy("127.0.0.1:8080"); var connection = new HubConnectionBuilder() .WithUrl(SignalREndpoint, options => { // If we have a proxy, pass it to Signal R too if (Proxy != null) { options.Proxy = Proxy; //These seemed excessive but like I said most of the evening was spent trying to figure it out. options.HttpMessageHandlerFactory = (handler) => { if (handler is HttpClientHandler clientHandler) { clientHandler.Proxy = Proxy; } return handler; }; // Based on: https://source.dot.net/#Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs,2754 options.WebSocketFactory = async (context, token) => { var ws = new ClientWebSocket(); ws.Options.Proxy = Proxy; await ws.ConnectAsync(context.Uri, token); return ws; }; options.WebSocketConfiguration = wsOptions => { wsOptions.Proxy = Proxy; }; } }) .WithAutomaticReconnect(new InfiniteRetryPolicy(0, 2, 5, 10, 15, 30)) .Build();
解决思路与调整方案
1. 修正CONNECT请求处理逻辑
HTTPS场景下,SignalR的WebSocket连接会先发送CONNECT请求建立代理隧道,这个请求本身不带Upgrade头,之前插件在http_connect方法里判断Upgrade头是无效的,反而会阻断隧道建立,导致后续的WebSocket握手请求无法到达插件。需要移除该方法里的WebSocket判断,让CONNECT请求正常通过:
def http_connect(self, flow: http.HTTPFlow) -> None: # 只记录日志,不做拦截,允许隧道建立 logging.info(f"CONNECT request to {flow.request.host}")
2. 精准匹配SignalR的WebSocket请求
SignalR的WebSocket握手请求路径通常包含特定标识(比如/hub),可以结合路径特征+Upgrade头做双重判断,提升拦截精准度:
def request(self, flow: http.HTTPFlow) -> None: is_websocket = flow.request.headers.get("Upgrade", "").lower() == "websocket" # 根据你的SignalR路由调整路径匹配规则 is_signalr = "/hub" in flow.request.path if is_websocket and is_signalr: self.block_websocket(flow)
3. 强制客户端使用WebSocket传输
SignalR默认会自动降级传输方式(比如WebSocket失败时切换到长轮询),需要在客户端配置里强制指定只使用WebSocket,避免绕过拦截:
options.Transports = HttpTransportType.WebSockets;
4. 简化客户端代理配置
不需要重复设置多层代理,只保留顶层Proxy和WebSocketFactory配置即可:
var connection = new HubConnectionBuilder() .WithUrl(SignalREndpoint, options => { options.Proxy = Proxy; options.Transports = HttpTransportType.WebSockets; options.WebSocketFactory = async (context, token) => { var ws = new ClientWebSocket(); ws.Options.Proxy = Proxy; await ws.ConnectAsync(context.Uri, token); return ws; }; }) .WithAutomaticReconnect(new InfiniteRetryPolicy(0, 2, 5, 10, 15, 30)) .Build();
5. 验证mitmproxy证书信任
确保客户端已信任mitmproxy的CA证书,否则HTTPS请求会失败,SignalR可能直接跳过WebSocket传输,导致拦截逻辑无法触发。
内容的提问来源于stack exchange,提问作者ProbablePrime

