From 117de1fd3c2703c0c85705e47dc7e93716e97c79 Mon Sep 17 00:00:00 2001 From: RPRX <63339210+RPRX@users.noreply.github.com> Date: Fri, 7 Feb 2025 08:15:40 +0000 Subject: [PATCH] MITM freedom RAW TLS: Report website with unexpected Negotiated Protocol / invalid Domain Fronting certificate https://github.com/XTLS/Xray-core/issues/4348#issuecomment-2639965524 Needs `"alpn": ["fromMitm"]` / `"verifyPeerCertInNames": ["fromMitm", ...]`. --- proxy/dokodemo/dokodemo.go | 2 +- transport/internet/tcp/dialer.go | 42 +++++++++++++++++++------------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/proxy/dokodemo/dokodemo.go b/proxy/dokodemo/dokodemo.go index c16135f5..4fb431f4 100644 --- a/proxy/dokodemo/dokodemo.go +++ b/proxy/dokodemo/dokodemo.go @@ -89,7 +89,7 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st destinationOverridden = true ctx = session.ContextWithMitmServerName(ctx, serverName) } - if tlsConn.NegotiatedProtocol() == "http/1.1" { + if tlsConn.NegotiatedProtocol() != "h2" { ctx = session.ContextWithMitmAlpn11(ctx, true) } } diff --git a/transport/internet/tcp/dialer.go b/transport/internet/tcp/dialer.go index 8d9bbf78..d9fb4aab 100644 --- a/transport/internet/tcp/dialer.go +++ b/transport/internet/tcp/dialer.go @@ -33,7 +33,9 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me if IsFromMitm(tlsConfig.ServerName) { tlsConfig.ServerName = mitmServerName } - if r, ok := tlsConfig.Rand.(*tls.RandCarrier); ok && len(r.VerifyPeerCertInNames) > 0 && IsFromMitm(r.VerifyPeerCertInNames[0]) { + r, ok := tlsConfig.Rand.(*tls.RandCarrier) + isFromMitmVerify := ok && len(r.VerifyPeerCertInNames) > 0 && IsFromMitm(r.VerifyPeerCertInNames[0]) + if isFromMitmVerify { r.VerifyPeerCertInNames = r.VerifyPeerCertInNames[1:] after := mitmServerName for { @@ -46,29 +48,35 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me } } } + isFromMitmAlpn := len(tlsConfig.NextProtos) == 1 && IsFromMitm(tlsConfig.NextProtos[0]) + if isFromMitmAlpn { + if mitmAlpn11 { + tlsConfig.NextProtos[0] = "http/1.1" + } else { + tlsConfig.NextProtos = []string{"h2", "http/1.1"} + } + } if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil { conn = tls.UClient(conn, tlsConfig, fingerprint) - if len(tlsConfig.NextProtos) == 1 && (tlsConfig.NextProtos[0] == "http/1.1" || (IsFromMitm(tlsConfig.NextProtos[0]) && mitmAlpn11)) { - if err := conn.(*tls.UConn).WebsocketHandshakeContext(ctx); err != nil { - return nil, err - } + if len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == "http/1.1" { // allow manually specify + err = conn.(*tls.UConn).WebsocketHandshakeContext(ctx) } else { - if err := conn.(*tls.UConn).HandshakeContext(ctx); err != nil { - return nil, err - } + err = conn.(*tls.UConn).HandshakeContext(ctx) } } else { - if len(tlsConfig.NextProtos) == 1 && IsFromMitm(tlsConfig.NextProtos[0]) { - if mitmAlpn11 { - tlsConfig.NextProtos[0] = "http/1.1" - } else { - tlsConfig.NextProtos = nil - } - } conn = tls.Client(conn, tlsConfig) - if err := conn.(*tls.Conn).HandshakeContext(ctx); err != nil { - return nil, err + err = conn.(*tls.Conn).HandshakeContext(ctx) + } + if err != nil { + if isFromMitmVerify { + return nil, errors.New("MITM freedom RAW TLS: failed to verify Domain Fronting certificate from " + mitmServerName).Base(err).AtWarning() } + return nil, err + } + negotiatedProtocol := conn.(tls.Interface).NegotiatedProtocol() + if isFromMitmAlpn && !mitmAlpn11 && negotiatedProtocol != "h2" { + conn.Close() + return nil, errors.New("MITM freedom RAW TLS: unexpected Negotiated Protocol (" + negotiatedProtocol + ") with " + mitmServerName).AtWarning() } } else if config := reality.ConfigFromStreamSettings(streamSettings); config != nil { if conn, err = reality.UClient(conn, config, ctx, dest); err != nil {