Files
mandiant-gopacket/pkg/tds/sqlr.go
T
psycep efccf92228 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.
2026-04-22 09:54:09 -05:00

163 lines
3.8 KiB
Go

// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tds
import (
"fmt"
"strings"
"time"
"gopacket/pkg/transport"
)
// SQLRInstance represents a discovered SQL Server instance
type SQLRInstance struct {
ServerName string
InstanceName string
IsClustered string
Version string
TCP string
NamedPipe string
}
// 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) {
conn, err := transport.DialUDP(fmt.Sprintf("%s:%d", server, SQLRPort))
if err != nil {
return nil, err
}
defer conn.Close()
// Send CLNT_UCAST_EX request
request := []byte{SQLRClntUcastEx}
if _, err := conn.Write(request); err != nil {
return nil, err
}
// Set read deadline
if err := conn.SetReadDeadline(time.Now().Add(timeout)); err != nil {
return nil, err
}
// Read response
buf := make([]byte, 65535)
n, err := conn.Read(buf)
if err != nil {
return nil, err
}
return parseInstanceResponse(buf[:n])
}
// GetInstancePort queries for a specific instance's port. Fails under -proxy (UDP).
func GetInstancePort(server, instance string, timeout time.Duration) (int, error) {
conn, err := transport.DialUDP(fmt.Sprintf("%s:%d", server, SQLRPort))
if err != nil {
return 0, err
}
defer conn.Close()
// Send CLNT_UCAST_INST request
request := append([]byte{SQLRClntUcastInst}, []byte(instance)...)
request = append(request, 0x00) // Null terminator
if _, err := conn.Write(request); err != nil {
return 0, err
}
// Set read deadline
if err := conn.SetReadDeadline(time.Now().Add(timeout)); err != nil {
return 0, err
}
// Read response
buf := make([]byte, 65535)
n, err := conn.Read(buf)
if err != nil {
return 0, err
}
instances, err := parseInstanceResponse(buf[:n])
if err != nil {
return 0, err
}
for _, inst := range instances {
if strings.EqualFold(inst.InstanceName, instance) {
var port int
fmt.Sscanf(inst.TCP, "%d", &port)
return port, nil
}
}
return 0, fmt.Errorf("instance %s not found", instance)
}
// parseInstanceResponse parses the SQL Server Browser response
func parseInstanceResponse(data []byte) ([]SQLRInstance, error) {
if len(data) < 3 {
return nil, fmt.Errorf("response too short")
}
// Check response type
if data[0] != 0x05 {
return nil, fmt.Errorf("unexpected response type: 0x%02x", data[0])
}
// Get length
// length := binary.LittleEndian.Uint16(data[1:3])
payload := data[3:]
// Split by double semicolon
entries := strings.Split(string(payload), ";;")
var instances []SQLRInstance
for _, entry := range entries {
entry = strings.TrimSpace(entry)
if entry == "" {
continue
}
inst := SQLRInstance{}
fields := strings.Split(entry, ";")
for i := 0; i < len(fields)-1; i += 2 {
key := fields[i]
value := fields[i+1]
switch key {
case "ServerName":
inst.ServerName = value
case "InstanceName":
inst.InstanceName = value
case "IsClustered":
inst.IsClustered = value
case "Version":
inst.Version = value
case "tcp":
inst.TCP = value
case "np":
inst.NamedPipe = value
}
}
if inst.ServerName != "" || inst.InstanceName != "" {
instances = append(instances, inst)
}
}
return instances, nil
}