Files
mandiant-gopacket/pkg/third_party/gokrb5/kadmin/message.go
T
Jacob Paullus 8eea029431 kerberos, dcerpc: tunnel KDC traffic through pkg/transport
The embedded gokrb5/v8 library hard-coded net.DialTimeout for AS/TGS
exchanges, bypassing -proxy and leaking the operator's source IP to the
KDC (UDP/88 first, TCP/88 fallback). The DCERPC Kerberos auth path used
a separate library (oiweiwei/gokrb5.fork/v9 via go-msrpc) that leaked the
same way.

Vendor jcmturner/gokrb5/v8 in-tree at pkg/third_party/gokrb5 with a
required KDCDialer first argument on every client constructor, so
proxy-bypass becomes a compile error. Wire kerberos.TransportKDCDialer
everywhere a gokrb5 client is built. Stamp udp_preference_limit=1 and
dns_lookup_kdc/realm=false unconditionally so KRB5 is TCP-only and the
OS resolver is never consulted; /etc/krb5.conf and $KRB5_CONFIG are
deliberately not read.

For DCERPC: set krbConfig.KDCDialer on every krb5.Config, pass
dcerpc.WithDialer(transport.ContextDialer{}) on every dcerpc.Dial, and
use the "ncacn_ip_tcp:" StringBinding prefix on the OXID-pivot dial so
go-msrpc's hard-coded pre-dial net.LookupIP is skipped (defers FQDN
resolution to the SOCKS5 proxy).

Verified against a live GOAD lab: 8 Kerberos-touching tools plus 5
NTLM/password/PtH regressions all operate through SOCKS5 with zero
direct packets to the AD subnet. Negative control (no -proxy)
immediately emits direct SYNs to the KDC, confirming both the leak
class and the fix.
2026-05-11 23:23:21 -05:00

115 lines
2.7 KiB
Go

package kadmin
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"github.com/mandiant/gopacket/pkg/third_party/gokrb5/messages"
"github.com/mandiant/gopacket/pkg/third_party/gokrb5/types"
)
const (
verisonHex = "ff80"
)
// Request message for changing password.
type Request struct {
APREQ messages.APReq
KRBPriv messages.KRBPriv
}
// Reply message for a password change.
type Reply struct {
MessageLength int
Version int
APREPLength int
APREP messages.APRep
KRBPriv messages.KRBPriv
KRBError messages.KRBError
IsKRBError bool
ResultCode uint16
Result string
}
// Marshal a Request into a byte slice.
func (m *Request) Marshal() (b []byte, err error) {
b = []byte{255, 128} // protocol version number: contains the hex constant 0xff80 (big-endian integer).
ab, e := m.APREQ.Marshal()
if e != nil {
err = fmt.Errorf("error marshaling AP_REQ: %v", e)
return
}
if len(ab) > math.MaxUint16 {
err = errors.New("length of AP_REQ greater then max Uint16 size")
return
}
al := make([]byte, 2)
binary.BigEndian.PutUint16(al, uint16(len(ab)))
b = append(b, al...)
b = append(b, ab...)
pb, e := m.KRBPriv.Marshal()
if e != nil {
err = fmt.Errorf("error marshaling KRB_Priv: %v", e)
return
}
b = append(b, pb...)
if len(b)+2 > math.MaxUint16 {
err = errors.New("length of message greater then max Uint16 size")
return
}
ml := make([]byte, 2)
binary.BigEndian.PutUint16(ml, uint16(len(b)+2))
b = append(ml, b...)
return
}
// Unmarshal a byte slice into a Reply.
func (m *Reply) Unmarshal(b []byte) error {
m.MessageLength = int(binary.BigEndian.Uint16(b[0:2]))
m.Version = int(binary.BigEndian.Uint16(b[2:4]))
if m.Version != 1 {
return fmt.Errorf("kadmin reply has incorrect protocol version number: %d", m.Version)
}
m.APREPLength = int(binary.BigEndian.Uint16(b[4:6]))
if m.APREPLength != 0 {
err := m.APREP.Unmarshal(b[6 : 6+m.APREPLength])
if err != nil {
return err
}
err = m.KRBPriv.Unmarshal(b[6+m.APREPLength : m.MessageLength])
if err != nil {
return err
}
} else {
m.IsKRBError = true
m.KRBError.Unmarshal(b[6:m.MessageLength])
m.ResultCode, m.Result = parseResponse(m.KRBError.EData)
}
return nil
}
func parseResponse(b []byte) (c uint16, s string) {
c = binary.BigEndian.Uint16(b[0:2])
buf := bytes.NewBuffer(b[2:])
m := make([]byte, len(b)-2)
binary.Read(buf, binary.BigEndian, &m)
s = string(m)
return
}
// Decrypt the encrypted part of the KRBError within the change password Reply.
func (m *Reply) Decrypt(key types.EncryptionKey) error {
if m.IsKRBError {
return m.KRBError
}
err := m.KRBPriv.DecryptEncPart(key)
if err != nil {
return err
}
m.ResultCode, m.Result = parseResponse(m.KRBPriv.DecryptedEncPart.UserData)
return nil
}