Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/js/libs/kerberos/kerberosx.go
2070 views
1
package kerberos
2
3
import (
4
"strings"
5
6
"github.com/Mzack9999/goja"
7
kclient "github.com/jcmturner/gokrb5/v8/client"
8
kconfig "github.com/jcmturner/gokrb5/v8/config"
9
"github.com/jcmturner/gokrb5/v8/iana/errorcode"
10
"github.com/jcmturner/gokrb5/v8/messages"
11
"github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
12
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
13
ConversionUtil "github.com/projectdiscovery/utils/conversion"
14
)
15
16
type (
17
// EnumerateUserResponse is the response from EnumerateUser
18
EnumerateUserResponse struct {
19
Valid bool `json:"valid"`
20
ASREPHash string `json:"asrep_hash"`
21
Error string `json:"error"`
22
}
23
)
24
25
type (
26
// TGS is the response from GetServiceTicket
27
TGS struct {
28
Ticket messages.Ticket `json:"ticket"`
29
Hash string `json:"hash"`
30
ErrMsg string `json:"error"`
31
}
32
)
33
34
type (
35
// Config is extra configuration for the kerberos client
36
Config struct {
37
ip string
38
timeout int // in seconds
39
}
40
)
41
42
// SetIPAddress sets the IP address for the kerberos client
43
// @example
44
// ```javascript
45
// const kerberos = require('nuclei/kerberos');
46
// const cfg = new kerberos.Config();
47
// cfg.SetIPAddress('10.10.10.1');
48
// ```
49
func (c *Config) SetIPAddress(ip string) *Config {
50
c.ip = ip
51
return c
52
}
53
54
// SetTimeout sets the RW timeout for the kerberos client
55
// @example
56
// ```javascript
57
// const kerberos = require('nuclei/kerberos');
58
// const cfg = new kerberos.Config();
59
// cfg.SetTimeout(5);
60
// ```
61
func (c *Config) SetTimeout(timeout int) *Config {
62
c.timeout = timeout
63
return c
64
}
65
66
// Example Values for jargons
67
// Realm: ACME.COM (Authentical zone / security area)
68
// Domain: acme.com (Public website / domain)
69
// DomainController: dc.acme.com (Domain Controller / Active Directory Server)
70
// KDC: kdc.acme.com (Key Distribution Center / Authentication Server)
71
72
type (
73
// Known Issues:
74
// Hardcoded timeout in gokrb5 library
75
// TGT / Session Handling not exposed
76
// Client is kerberos client
77
// @example
78
// ```javascript
79
// const kerberos = require('nuclei/kerberos');
80
// // if controller is empty a dns lookup for default kdc server will be performed
81
// const client = new kerberos.Client('acme.com', 'kdc.acme.com');
82
// ```
83
Client struct {
84
nj *utils.NucleiJS // helper functions/bindings
85
Krb5Config *kconfig.Config
86
Realm string
87
config Config
88
}
89
)
90
91
// Constructor for Kerberos Client
92
// Constructor: constructor(public domain: string, public controller?: string)
93
// When controller is empty or not given krb5 will perform a DNS lookup for the default KDC server
94
// and retrieve its address from the DNS server
95
func NewKerberosClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {
96
// setup nucleijs utils
97
c := &Client{nj: utils.NewNucleiJS(runtime)}
98
c.nj.ObjectSig = "Client(domain, {controller})" // will be included in error messages
99
100
// get arguments (type assertion is efficient than reflection)
101
// when accepting type as input like net.Conn we can use utils.GetArg
102
domain, _ := c.nj.GetArg(call.Arguments, 0).(string)
103
controller, _ := c.nj.GetArg(call.Arguments, 1).(string)
104
105
// validate arguments
106
c.nj.Require(domain != "", "domain cannot be empty")
107
108
cfg := kconfig.New()
109
110
if controller != "" {
111
// validate controller hostport
112
executionId := c.nj.ExecutionId()
113
if !protocolstate.IsHostAllowed(executionId, controller) {
114
c.nj.Throw("domain controller address blacklisted by network policy")
115
}
116
117
tmp := strings.Split(controller, ":")
118
if len(tmp) == 1 {
119
tmp = append(tmp, "88")
120
}
121
realm := strings.ToUpper(domain)
122
cfg.LibDefaults.DefaultRealm = realm // set default realm
123
cfg.Realms = []kconfig.Realm{
124
{
125
Realm: realm,
126
KDC: []string{tmp[0] + ":" + tmp[1]},
127
AdminServer: []string{tmp[0] + ":" + tmp[1]},
128
KPasswdServer: []string{tmp[0] + ":464"}, // default password server port
129
},
130
}
131
cfg.DomainRealm = make(kconfig.DomainRealm)
132
} else {
133
// if controller is empty use DNS lookup
134
cfg.LibDefaults.DNSLookupKDC = true
135
cfg.LibDefaults.DefaultRealm = strings.ToUpper(domain)
136
cfg.DomainRealm = make(kconfig.DomainRealm)
137
}
138
c.Krb5Config = cfg
139
c.Realm = strings.ToUpper(domain)
140
141
// Link Constructor to Client and return
142
return utils.LinkConstructor(call, runtime, c)
143
}
144
145
// NewKerberosClientFromString creates a new kerberos client from a string
146
// by parsing krb5.conf
147
// @example
148
// ```javascript
149
// const kerberos = require('nuclei/kerberos');
150
// const client = kerberos.NewKerberosClientFromString(`
151
// [libdefaults]
152
// default_realm = ACME.COM
153
// dns_lookup_kdc = true
154
// `);
155
// ```
156
func NewKerberosClientFromString(cfg string) (*Client, error) {
157
config, err := kconfig.NewFromString(cfg)
158
if err != nil {
159
return nil, err
160
}
161
return &Client{Krb5Config: config}, nil
162
}
163
164
// SetConfig sets additional config for the kerberos client
165
// Note: as of now ip and timeout overrides are only supported
166
// in EnumerateUser due to fastdialer but can be extended to other methods currently
167
// @example
168
// ```javascript
169
// const kerberos = require('nuclei/kerberos');
170
// const client = new kerberos.Client('acme.com', 'kdc.acme.com');
171
// const cfg = new kerberos.Config();
172
// cfg.SetIPAddress('192.168.100.22');
173
// cfg.SetTimeout(5);
174
// client.SetConfig(cfg);
175
// ```
176
func (c *Client) SetConfig(cfg *Config) {
177
if cfg == nil {
178
c.nj.Throw("config cannot be nil")
179
}
180
c.config = *cfg
181
}
182
183
// EnumerateUser and attempt to get AS-REP hash by disabling PA-FX-FAST
184
// @example
185
// ```javascript
186
// const kerberos = require('nuclei/kerberos');
187
// const client = new kerberos.Client('acme.com', 'kdc.acme.com');
188
// const resp = client.EnumerateUser('pdtm');
189
// log(resp);
190
// ```
191
func (c *Client) EnumerateUser(username string) (EnumerateUserResponse, error) {
192
c.nj.Require(c.Krb5Config != nil, "Kerberos client not initialized")
193
password := "password"
194
// client does not actually attempt connection it manages state here
195
client := kclient.NewWithPassword(username, c.Realm, password, c.Krb5Config, kclient.DisablePAFXFAST(true))
196
defer client.Destroy()
197
198
// generate ASReq hash
199
req, err := messages.NewASReqForTGT(client.Credentials.Domain(), client.Config, client.Credentials.CName())
200
c.nj.HandleError(err, "failed to generate TGT request")
201
202
// marshal request
203
b, err := req.Marshal()
204
c.nj.HandleError(err, "failed to marshal TGT request")
205
206
data, err := SendToKDC(c, string(b))
207
rb := ConversionUtil.Bytes(data)
208
209
if err == nil {
210
var ASRep messages.ASRep
211
resp := EnumerateUserResponse{Valid: true}
212
err = ASRep.Unmarshal(rb)
213
if err != nil {
214
resp.Error = err.Error()
215
return resp, nil
216
}
217
hashcatString, _ := ASRepToHashcat(ASRep)
218
resp.ASREPHash = hashcatString
219
return resp, nil
220
}
221
222
resp := EnumerateUserResponse{}
223
e, ok := err.(messages.KRBError)
224
if !ok {
225
return resp, err
226
}
227
if e.ErrorCode == errorcode.KDC_ERR_PREAUTH_REQUIRED {
228
resp.Valid = true
229
resp.Error = errorcode.Lookup(e.ErrorCode)
230
return resp, nil
231
}
232
resp.Error = errorcode.Lookup(e.ErrorCode)
233
return resp, nil
234
}
235
236
// GetServiceTicket returns a TGS for a given user, password and SPN
237
// @example
238
// ```javascript
239
// const kerberos = require('nuclei/kerberos');
240
// const client = new kerberos.Client('acme.com', 'kdc.acme.com');
241
// const resp = client.GetServiceTicket('pdtm', 'password', 'HOST/CLIENT1');
242
// log(resp);
243
// ```
244
func (c *Client) GetServiceTicket(User, Pass, SPN string) (TGS, error) {
245
c.nj.Require(c.Krb5Config != nil, "Kerberos client not initialized")
246
c.nj.Require(User != "", "User cannot be empty")
247
c.nj.Require(Pass != "", "Pass cannot be empty")
248
c.nj.Require(SPN != "", "SPN cannot be empty")
249
250
executionId := c.nj.ExecutionId()
251
252
if len(c.Krb5Config.Realms) > 0 {
253
// this means dc address was given
254
for _, r := range c.Krb5Config.Realms {
255
for _, kdc := range r.KDC {
256
if !protocolstate.IsHostAllowed(executionId, kdc) {
257
c.nj.Throw("KDC address %v blacklisted by network policy", kdc)
258
}
259
}
260
for _, kpasswd := range r.KPasswdServer {
261
if !protocolstate.IsHostAllowed(executionId, kpasswd) {
262
c.nj.Throw("Kpasswd address %v blacklisted by network policy", kpasswd)
263
}
264
}
265
}
266
} else {
267
// here net.Dialer is used instead of fastdialer hence get possible addresses
268
// and check if they are allowed by network policy
269
_, kdcs, _ := c.Krb5Config.GetKDCs(c.Realm, true)
270
for _, v := range kdcs {
271
if !protocolstate.IsHostAllowed(executionId, v) {
272
c.nj.Throw("KDC address %v blacklisted by network policy", v)
273
}
274
}
275
}
276
277
// client does not actually attempt connection it manages state here
278
client := kclient.NewWithPassword(User, c.Realm, Pass, c.Krb5Config, kclient.DisablePAFXFAST(true))
279
defer client.Destroy()
280
281
resp := TGS{}
282
283
ticket, _, err := client.GetServiceTicket(SPN)
284
resp.Ticket = ticket
285
if err != nil {
286
if code, ok := err.(messages.KRBError); ok {
287
resp.ErrMsg = errorcode.Lookup(code.ErrorCode)
288
return resp, err
289
}
290
return resp, err
291
}
292
// convert AS-REP to hashcat format
293
hashcat, err := TGStoHashcat(ticket, c.Realm)
294
if err != nil {
295
if code, ok := err.(messages.KRBError); ok {
296
resp.ErrMsg = errorcode.Lookup(code.ErrorCode)
297
return resp, err
298
}
299
return resp, err
300
}
301
resp.Ticket = ticket
302
resp.Hash = hashcat
303
return resp, nil
304
}
305
306
// // GetASREP returns AS-REP for a given user and password
307
// // it contains Client's TGT , Principal and Session Key
308
// // Signature: GetASREP(User, Pass)
309
// // @param User: string
310
// // @param Pass: string
311
// func (c *Client) GetASREP(User, Pass string) messages.ASRep {
312
// c.nj.Require(c.Krb5Config != nil, "Kerberos client not initialized")
313
// c.nj.Require(User != "", "User cannot be empty")
314
// c.nj.Require(Pass != "", "Pass cannot be empty")
315
316
// if len(c.Krb5Config.Realms) > 0 {
317
// // this means dc address was given
318
// for _, r := range c.Krb5Config.Realms {
319
// for _, kdc := range r.KDC {
320
// if !protocolstate.IsHostAllowed(kdc) {
321
// c.nj.Throw("KDC address blacklisted by network policy")
322
// }
323
// }
324
// for _, kpasswd := range r.KPasswdServer {
325
// if !protocolstate.IsHostAllowed(kpasswd) {
326
// c.nj.Throw("Kpasswd address blacklisted by network policy")
327
// }
328
// }
329
// }
330
// } else {
331
// // here net.Dialer is used instead of fastdialer hence get possible addresses
332
// // and check if they are allowed by network policy
333
// _, kdcs, _ := c.Krb5Config.GetKDCs(c.Realm, true)
334
// for _, v := range kdcs {
335
// if !protocolstate.IsHostAllowed(v) {
336
// c.nj.Throw("KDC address blacklisted by network policy")
337
// }
338
// }
339
// }
340
341
// // login to get TGT
342
// cl := kclient.NewWithPassword(User, c.Realm, Pass, c.Krb5Config, kclient.DisablePAFXFAST(true))
343
// defer cl.Destroy()
344
345
// // generate ASReq
346
// ASReq, err := messages.NewASReqForTGT(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName())
347
// c.nj.HandleError(err, "failed to generate TGT request")
348
349
// // exchange AS-REQ for AS-REP
350
// resp, err := cl.ASExchange(c.Realm, ASReq, 0)
351
// c.nj.HandleError(err, "failed to exchange AS-REQ")
352
353
// // try to decrypt encrypted parts of the response and TGT
354
// key, err := resp.DecryptEncPart(cl.Credentials)
355
// if err == nil {
356
// _ = resp.Ticket.Decrypt(key)
357
// }
358
// return resp
359
// }
360
361