From efccf922280a5872e21872750bd58a10cb77deb0 Mon Sep 17 00:00:00 2001 From: psycep Date: Wed, 22 Apr 2026 09:54:09 -0500 Subject: [PATCH] relay, tds: route outbound through transport - pkg/relay/http_client.go, winrm_client.go: swap the http.Transport DialContext from &net.Dialer{Timeout: 10s}.DialContext to a closure that calls transport.DialContext. NTLM relay now respects -proxy. - pkg/relay/socks.go: add a comment on the HTTP DNS passthrough path explaining why it intentionally uses net.DialTimeout rather than transport. That path is the outbound leg of our own SOCKS5 server, so routing it through -proxy would double-tunnel the operator's proxy through the operator's proxy. - pkg/tds/sqlr.go: SQL Server Browser discovery (UDP 1434) now uses transport.DialUDP, which surfaces ErrUDPUnderProxy when a proxy is configured rather than silently bypassing it. --- pkg/relay/http_client.go | 15 ++++++++++----- pkg/relay/socks.go | 3 +++ pkg/relay/winrm_client.go | 15 ++++++++++----- pkg/tds/sqlr.go | 24 +++++++----------------- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/pkg/relay/http_client.go b/pkg/relay/http_client.go index 057a5df..f505a5e 100644 --- a/pkg/relay/http_client.go +++ b/pkg/relay/http_client.go @@ -15,6 +15,7 @@ package relay import ( + "context" "crypto/tls" "encoding/base64" "fmt" @@ -24,8 +25,10 @@ import ( "net/http" "regexp" "strings" + "time" "gopacket/internal/build" + "gopacket/pkg/transport" ) // HTTPRelayClient implements ProtocolClient for relaying NTLM auth to HTTP/HTTPS targets. @@ -73,20 +76,22 @@ func (c *HTTPRelayClient) SetPath(path string) { func (c *HTTPRelayClient) InitConnection() error { // Use a custom transport that keeps connections alive for the NTLM handshake. // The entire 3-leg NTLM exchange must happen on the same TCP connection. - transport := &http.Transport{ + tr := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, DisableKeepAlives: false, // Force single connection reuse for NTLM auth MaxIdleConnsPerHost: 1, - DialContext: (&net.Dialer{ - Timeout: 10 * 1e9, // 10 seconds - }).DialContext, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + return transport.DialContext(ctx, network, addr) + }, } c.httpClient = &http.Client{ - Transport: transport, + Transport: tr, // Don't follow redirects — we need to see the raw 401/302 responses CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse diff --git a/pkg/relay/socks.go b/pkg/relay/socks.go index 38d7ee4..4c425f9 100644 --- a/pkg/relay/socks.go +++ b/pkg/relay/socks.go @@ -447,6 +447,9 @@ func (s *SOCKSServer) handleConnection(conn net.Conn) { func (s *SOCKSServer) handleDNSPassthrough(clientConn net.Conn, host string, port int) { target := fmt.Sprintf("%s:%d", host, port) + // Intentionally uses net.DialTimeout, not transport.DialTimeout: this is + // the outbound leg of our own SOCKS5 server, so routing it through the + // operator's -proxy would double-tunnel. remotConn, err := net.DialTimeout("tcp", target, 10*time.Second) if err != nil { if build.Debug { diff --git a/pkg/relay/winrm_client.go b/pkg/relay/winrm_client.go index 88a0afe..59ad560 100644 --- a/pkg/relay/winrm_client.go +++ b/pkg/relay/winrm_client.go @@ -15,6 +15,7 @@ package relay import ( + "context" "crypto/tls" "encoding/base64" "fmt" @@ -24,8 +25,10 @@ import ( "net/http" "regexp" "strings" + "time" "gopacket/internal/build" + "gopacket/pkg/transport" ) // WinRMRelayClient implements ProtocolClient for relaying NTLM auth to WinRM targets. @@ -50,20 +53,22 @@ func NewWinRMRelayClient(addr string, useTLS bool) *WinRMRelayClient { // InitConnection creates the HTTP client with connection reuse for NTLM handshake. // Implements ProtocolClient. func (c *WinRMRelayClient) InitConnection() error { - transport := &http.Transport{ + tr := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, DisableKeepAlives: false, // Force single connection reuse for NTLM auth MaxIdleConnsPerHost: 1, - DialContext: (&net.Dialer{ - Timeout: 10 * 1e9, // 10 seconds - }).DialContext, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + return transport.DialContext(ctx, network, addr) + }, } c.httpClient = &http.Client{ - Transport: transport, + Transport: tr, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, diff --git a/pkg/tds/sqlr.go b/pkg/tds/sqlr.go index fb46edd..c1f1baa 100644 --- a/pkg/tds/sqlr.go +++ b/pkg/tds/sqlr.go @@ -16,9 +16,10 @@ package tds import ( "fmt" - "net" "strings" "time" + + "gopacket/pkg/transport" ) // SQLRInstance represents a discovered SQL Server instance @@ -31,15 +32,10 @@ type SQLRInstance struct { NamedPipe string } -// GetInstances queries the SQL Server Browser service for instances +// GetInstances queries the SQL Server Browser service for instances. +// Fails under -proxy because SQL Browser is UDP and cannot be tunneled. func GetInstances(server string, timeout time.Duration) ([]SQLRInstance, error) { - // Create UDP connection - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", server, SQLRPort)) - if err != nil { - return nil, err - } - - conn, err := net.DialUDP("udp", nil, addr) + conn, err := transport.DialUDP(fmt.Sprintf("%s:%d", server, SQLRPort)) if err != nil { return nil, err } @@ -66,15 +62,9 @@ func GetInstances(server string, timeout time.Duration) ([]SQLRInstance, error) return parseInstanceResponse(buf[:n]) } -// GetInstancePort queries for a specific instance's port +// GetInstancePort queries for a specific instance's port. Fails under -proxy (UDP). func GetInstancePort(server, instance string, timeout time.Duration) (int, error) { - // Create UDP connection - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", server, SQLRPort)) - if err != nil { - return 0, err - } - - conn, err := net.DialUDP("udp", nil, addr) + conn, err := transport.DialUDP(fmt.Sprintf("%s:%d", server, SQLRPort)) if err != nil { return 0, err }