Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/js/libs/kerberos/sendtokdc.go
2070 views
1
package kerberos
2
3
// the following code is adapted from the original library
4
// https://github.com/jcmturner/gokrb5/blob/855dbc707a37a21467aef6c0245fcf3328dc39ed/v8/client/network.go
5
// it is copied here because the library does not export "SendToKDC()"
6
7
import (
8
"context"
9
"encoding/binary"
10
"encoding/hex"
11
"fmt"
12
"io"
13
"net"
14
"strings"
15
"time"
16
17
"github.com/jcmturner/gokrb5/v8/messages"
18
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
19
)
20
21
// sendtokdc.go deals with actual sending and receiving responses from KDC
22
// SendToKDC sends a message to the KDC and returns the response.
23
// It first tries to send the message over TCP, and if that fails, it falls back to UDP.(and vice versa)
24
// @example
25
// ```javascript
26
// const kerberos = require('nuclei/kerberos');
27
// const client = new kerberos.Client('acme.com');
28
// const response = kerberos.SendToKDC(client, 'message');
29
// ```
30
func SendToKDC(kclient *Client, msg string) (string, error) {
31
if kclient == nil || kclient.nj == nil || kclient.Krb5Config == nil || kclient.Realm == "" {
32
return "", fmt.Errorf("kerberos client is not initialized")
33
}
34
if kclient.config.timeout == 0 {
35
kclient.config.timeout = 5 // default timeout 5 seconds
36
}
37
var response []byte
38
var err error
39
40
response, err = sendToKDCTcp(kclient, msg)
41
if err == nil {
42
// if it related to tcp
43
bin, err := CheckKrbError(response)
44
if err == nil {
45
return string(bin), nil
46
}
47
// if it is krb error no need to do udp
48
if e, ok := err.(messages.KRBError); ok {
49
return string(response), e
50
}
51
}
52
53
// fallback to udp
54
response, err = sendToKDCUdp(kclient, msg)
55
if err == nil {
56
// if it related to udp
57
bin, err := CheckKrbError(response)
58
if err == nil {
59
return string(bin), nil
60
}
61
}
62
return string(response), err
63
}
64
65
// sendToKDCTcp sends a message to the KDC via TCP.
66
func sendToKDCTcp(kclient *Client, msg string) ([]byte, error) {
67
_, kdcs, err := kclient.Krb5Config.GetKDCs(kclient.Realm, true)
68
kclient.nj.HandleError(err, "error getting KDCs")
69
kclient.nj.Require(len(kdcs) > 0, "no KDCs found")
70
71
executionId := kclient.nj.ExecutionId()
72
dialers := protocolstate.GetDialersWithId(executionId)
73
if dialers == nil {
74
return nil, fmt.Errorf("dialers not initialized for %s", executionId)
75
}
76
77
var errs []string
78
for i := 1; i <= len(kdcs); i++ {
79
host, port, err := net.SplitHostPort(kdcs[i])
80
if err == nil && kclient.config.ip != "" {
81
// use that ip address instead of realm/domain for resolving
82
host = kclient.config.ip
83
}
84
tcpConn, err := dialers.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, port))
85
if err != nil {
86
errs = append(errs, fmt.Sprintf("error establishing connection to %s: %v", kdcs[i], err))
87
continue
88
}
89
defer func() {
90
_ = tcpConn.Close()
91
}()
92
_ = tcpConn.SetDeadline(time.Now().Add(time.Duration(kclient.config.timeout) * time.Second)) //read and write deadline
93
rb, err := sendTCP(tcpConn.(*net.TCPConn), []byte(msg))
94
if err != nil {
95
errs = append(errs, fmt.Sprintf("error sending to %s: %v", kdcs[i], err))
96
continue
97
}
98
return rb, nil
99
}
100
if len(errs) > 0 {
101
return nil, fmt.Errorf("error sending to a KDC: %s", strings.Join(errs, "; "))
102
}
103
return nil, nil
104
}
105
106
// sendToKDCUdp sends a message to the KDC via UDP.
107
func sendToKDCUdp(kclient *Client, msg string) ([]byte, error) {
108
_, kdcs, err := kclient.Krb5Config.GetKDCs(kclient.Realm, true)
109
kclient.nj.HandleError(err, "error getting KDCs")
110
kclient.nj.Require(len(kdcs) > 0, "no KDCs found")
111
112
executionId := kclient.nj.ExecutionId()
113
dialers := protocolstate.GetDialersWithId(executionId)
114
if dialers == nil {
115
return nil, fmt.Errorf("dialers not initialized for %s", executionId)
116
}
117
var errs []string
118
for i := 1; i <= len(kdcs); i++ {
119
host, port, err := net.SplitHostPort(kdcs[i])
120
if err == nil && kclient.config.ip != "" {
121
// use that ip address instead of realm/domain for resolving
122
host = kclient.config.ip
123
}
124
udpConn, err := dialers.Fastdialer.Dial(context.TODO(), "udp", net.JoinHostPort(host, port))
125
if err != nil {
126
errs = append(errs, fmt.Sprintf("error establishing connection to %s: %v", kdcs[i], err))
127
continue
128
}
129
defer func() {
130
_ = udpConn.Close()
131
}()
132
_ = udpConn.SetDeadline(time.Now().Add(time.Duration(kclient.config.timeout) * time.Second)) //read and write deadline
133
rb, err := sendUDP(udpConn.(*net.UDPConn), []byte(msg))
134
if err != nil {
135
errs = append(errs, fmt.Sprintf("error sending to %s: %v", kdcs[i], err))
136
continue
137
}
138
return rb, nil
139
}
140
if len(errs) > 0 {
141
// fallback to tcp
142
return nil, fmt.Errorf("error sending to a KDC: %s", strings.Join(errs, "; "))
143
}
144
return nil, nil
145
}
146
147
// sendUDP sends bytes to connection over UDP.
148
func sendUDP(conn *net.UDPConn, b []byte) ([]byte, error) {
149
var r []byte
150
defer func() {
151
_ = conn.Close()
152
}()
153
_, err := conn.Write(b)
154
if err != nil {
155
return r, fmt.Errorf("error sending to (%s): %v", conn.RemoteAddr().String(), err)
156
}
157
udpbuf := make([]byte, 4096)
158
n, _, err := conn.ReadFrom(udpbuf)
159
r = udpbuf[:n]
160
if err != nil {
161
return r, fmt.Errorf("sending over UDP failed to %s: %v", conn.RemoteAddr().String(), err)
162
}
163
if len(r) < 1 {
164
return r, fmt.Errorf("no response data from %s", conn.RemoteAddr().String())
165
}
166
return r, nil
167
}
168
169
// sendTCP sends bytes to connection over TCP.
170
func sendTCP(conn *net.TCPConn, b []byte) ([]byte, error) {
171
defer func() {
172
_ = conn.Close()
173
}()
174
var r []byte
175
// RFC 4120 7.2.2 specifies the first 4 bytes indicate the length of the message in big endian order.
176
hb := make([]byte, 4)
177
binary.BigEndian.PutUint32(hb, uint32(len(b)))
178
b = append(hb, b...)
179
180
_, err := conn.Write(b)
181
if err != nil {
182
return r, fmt.Errorf("error sending to KDC (%s): %v", conn.RemoteAddr().String(), err)
183
}
184
185
sh := make([]byte, 4)
186
_, err = conn.Read(sh)
187
if err != nil {
188
return r, fmt.Errorf("error reading response size header: %v", err)
189
}
190
s := binary.BigEndian.Uint32(sh)
191
192
rb := make([]byte, s)
193
_, err = io.ReadFull(conn, rb)
194
if err != nil {
195
return r, fmt.Errorf("error reading response: %v", err)
196
}
197
if len(rb) < 1 {
198
return r, fmt.Errorf("no response data from KDC %s", conn.RemoteAddr().String())
199
}
200
return rb, nil
201
}
202
203
// CheckKrbError checks if the response bytes from the KDC are a KRBError.
204
func CheckKrbError(b []byte) ([]byte, error) {
205
var KRBErr messages.KRBError
206
if err := KRBErr.Unmarshal(b); err == nil {
207
return b, KRBErr
208
}
209
return b, nil
210
}
211
212
// TGStoHashcat converts a TGS to a hashcat format.
213
func TGStoHashcat(tgs messages.Ticket, username string) (string, error) {
214
return fmt.Sprintf("$krb5tgs$%d$*%s$%s$%s*$%s$%s",
215
tgs.EncPart.EType,
216
username,
217
tgs.Realm,
218
strings.Join(tgs.SName.NameString[:], "/"),
219
hex.EncodeToString(tgs.EncPart.Cipher[:16]),
220
hex.EncodeToString(tgs.EncPart.Cipher[16:]),
221
), nil
222
}
223
224
// ASRepToHashcat converts an AS-REP message to a hashcat format
225
func ASRepToHashcat(asrep messages.ASRep) (string, error) {
226
return fmt.Sprintf("$krb5asrep$%d$%s@%s:%s$%s",
227
asrep.EncPart.EType,
228
asrep.CName.PrincipalNameString(),
229
asrep.CRealm,
230
hex.EncodeToString(asrep.EncPart.Cipher[:16]),
231
hex.EncodeToString(asrep.EncPart.Cipher[16:])), nil
232
}
233
234