● Notice //GitHub (@shehzade) is temporarily suspended pending an automated false-positive review. All tools are hosted on this Code Mirror and available on request (contact); GitHub links will return once reinstated.
PUBLIC MIRRORGo v1.0

Multi-protocol network egress assessment and DLP verification framework featuring automated L4 sweeps, corporate proxy traversal, and covert protocol smuggling across 20+ channels.

$ git clone https://shehzade.io/repos/contraband.git Copy
Source ZIP (1.93 MB)
Code 174 Commits 1
contraband / pkg / client / proxy / auth_kerberos.go
← Back to directory
135 lines · 4.1 KB
Raw
1package proxy
2
3import (
4 "bytes"
5 "encoding/asn1"
6 "encoding/base64"
7 "errors"
8 "fmt"
9 "os"
10 "strings"
11)
12
13// OID for NTLMSSP: 1.3.6.1.4.1.311.2.2.10
14var oidNTLMSSP = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 2, 2, 10}
15
16// OID for SPNEGO: 1.3.6.1.5.5.2
17var oidSPNEGO = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 2}
18
19// NegTokenInit represents SPNEGO negotiation initiation (RFC 4178).
20type negTokenInitASN1 struct {
21 MechTypes []asn1.ObjectIdentifier `asn1:"explicit,tag:0"`
22 ReqFlags asn1.BitString `asn1:"explicit,optional,tag:1"`
23 MechToken []byte `asn1:"explicit,optional,tag:2"`
24 MechListMIC []byte `asn1:"explicit,optional,tag:3"`
25}
26
27// SPNEGOHeader wraps NegTokenInit inside the GSS-API InitialContextToken.
28type spnegoInitialContextToken struct {
29 ThisMech asn1.ObjectIdentifier
30 InnerToken negTokenInitASN1 `asn1:"explicit,tag:0"`
31}
32
33// negTokenRespASN1 represents SPNEGO negotiation response.
34type negTokenRespASN1 struct {
35 NegState asn1.Enumerated `asn1:"explicit,optional,tag:0"`
36 SupportedMech asn1.ObjectIdentifier `asn1:"explicit,optional,tag:1"`
37 ResponseToken []byte `asn1:"explicit,optional,tag:2"`
38 MechListMIC []byte `asn1:"explicit,optional,tag:3"`
39}
40
41// HasKerberosCredentials checks whether standard Kerberos ccache is present in the environment.
42func HasKerberosCredentials() bool {
43 if ccache := os.Getenv("KRB5CCNAME"); ccache != "" {
44 if strings.HasPrefix(ccache, "FILE:") {
45 ccache = strings.TrimPrefix(ccache, "FILE:")
46 }
47 if _, err := os.Stat(ccache); err == nil {
48 return true
49 }
50 }
51 uid := os.Getuid()
52 defaultPath := fmt.Sprintf("/tmp/krb5cc_%d", uid)
53 if _, err := os.Stat(defaultPath); err == nil {
54 return true
55 }
56 return false
57}
58
59// BuildSPNEGONTLMInit wraps an NTLM Type 1 negotiate token into a SPNEGO NegTokenInit.
60func BuildSPNEGONTLMInit(domain, workstation string) (string, error) {
61 type1B64 := BuildNTLMType1(domain, workstation)
62 type1Bytes, err := base64.StdEncoding.DecodeString(type1B64)
63 if err != nil {
64 return "", err
65 }
66
67 initToken := negTokenInitASN1{
68 MechTypes: []asn1.ObjectIdentifier{oidNTLMSSP},
69 MechToken: type1Bytes,
70 }
71
72 wrapper := spnegoInitialContextToken{
73 ThisMech: oidSPNEGO,
74 InnerToken: initToken,
75 }
76
77 derBytes, err := asn1.Marshal(wrapper)
78 if err != nil {
79 // Fallback: manual ASN.1 framing if complex struct marshalling varies
80 var buf bytes.Buffer
81 buf.Write([]byte{0x60}) // Application 0 (GSS-API context token)
82 mechOID, _ := asn1.Marshal(oidSPNEGO)
83 mechTokenDER, _ := asn1.Marshal(type1Bytes)
84 mechTypeDER, _ := asn1.Marshal([]asn1.ObjectIdentifier{oidNTLMSSP})
85
86 var inner bytes.Buffer
87 inner.Write([]byte{0xa0}) // tag 0: mechTypes
88 inner.Write([]byte{byte(len(mechTypeDER))})
89 inner.Write(mechTypeDER)
90
91 inner.Write([]byte{0xa2}) // tag 2: mechToken
92 inner.Write([]byte{byte(len(mechTokenDER))})
93 inner.Write(mechTokenDER)
94
95 innerBytes := inner.Bytes()
96 var negToken bytes.Buffer
97 negToken.Write([]byte{0xa0}) // NegTokenInit tag 0
98 negToken.Write([]byte{byte(len(innerBytes))})
99 negToken.Write(innerBytes)
100
101 fullPayload := append(mechOID, negToken.Bytes()...)
102 buf.Write([]byte{byte(len(fullPayload))})
103 buf.Write(fullPayload)
104 derBytes = buf.Bytes()
105 }
106
107 return "Negotiate " + base64.StdEncoding.EncodeToString(derBytes), nil
108}
109
110// ExtractSPNEGOMechToken unpacks a response token from a SPNEGO NegTokenResp.
111func ExtractSPNEGOMechToken(header string) ([]byte, error) {
112 header = strings.TrimSpace(header)
113 if strings.HasPrefix(strings.ToLower(header), "negotiate ") {
114 header = strings.TrimSpace(header[10:])
115 }
116
117 data, err := base64.StdEncoding.DecodeString(header)
118 if err != nil {
119 return nil, fmt.Errorf("failed to decode SPNEGO base64: %w", err)
120 }
121
122 // If raw NTLMSSP payload was directly returned
123 if bytes.Contains(data, ntlmSignature) {
124 idx := bytes.Index(data, ntlmSignature)
125 return data[idx:], nil
126 }
127
128 var resp negTokenRespASN1
129 _, err = asn1.Unmarshal(data, &resp)
130 if err == nil && len(resp.ResponseToken) > 0 {
131 return resp.ResponseToken, nil
132 }
133
134 return nil, errors.New("failed to extract mechToken from SPNEGO response")
135}