You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何mitmproxy无法拦截SignalR客户端的WebSocket连接?

拦截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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 08:55:54