Path: blob/dev/pkg/js/libs/kerberos/kerberosx.go
2070 views
package kerberos12import (3"strings"45"github.com/Mzack9999/goja"6kclient "github.com/jcmturner/gokrb5/v8/client"7kconfig "github.com/jcmturner/gokrb5/v8/config"8"github.com/jcmturner/gokrb5/v8/iana/errorcode"9"github.com/jcmturner/gokrb5/v8/messages"10"github.com/projectdiscovery/nuclei/v3/pkg/js/utils"11"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"12ConversionUtil "github.com/projectdiscovery/utils/conversion"13)1415type (16// EnumerateUserResponse is the response from EnumerateUser17EnumerateUserResponse struct {18Valid bool `json:"valid"`19ASREPHash string `json:"asrep_hash"`20Error string `json:"error"`21}22)2324type (25// TGS is the response from GetServiceTicket26TGS struct {27Ticket messages.Ticket `json:"ticket"`28Hash string `json:"hash"`29ErrMsg string `json:"error"`30}31)3233type (34// Config is extra configuration for the kerberos client35Config struct {36ip string37timeout int // in seconds38}39)4041// SetIPAddress sets the IP address for the kerberos client42// @example43// ```javascript44// const kerberos = require('nuclei/kerberos');45// const cfg = new kerberos.Config();46// cfg.SetIPAddress('10.10.10.1');47// ```48func (c *Config) SetIPAddress(ip string) *Config {49c.ip = ip50return c51}5253// SetTimeout sets the RW timeout for the kerberos client54// @example55// ```javascript56// const kerberos = require('nuclei/kerberos');57// const cfg = new kerberos.Config();58// cfg.SetTimeout(5);59// ```60func (c *Config) SetTimeout(timeout int) *Config {61c.timeout = timeout62return c63}6465// Example Values for jargons66// Realm: ACME.COM (Authentical zone / security area)67// Domain: acme.com (Public website / domain)68// DomainController: dc.acme.com (Domain Controller / Active Directory Server)69// KDC: kdc.acme.com (Key Distribution Center / Authentication Server)7071type (72// Known Issues:73// Hardcoded timeout in gokrb5 library74// TGT / Session Handling not exposed75// Client is kerberos client76// @example77// ```javascript78// const kerberos = require('nuclei/kerberos');79// // if controller is empty a dns lookup for default kdc server will be performed80// const client = new kerberos.Client('acme.com', 'kdc.acme.com');81// ```82Client struct {83nj *utils.NucleiJS // helper functions/bindings84Krb5Config *kconfig.Config85Realm string86config Config87}88)8990// Constructor for Kerberos Client91// Constructor: constructor(public domain: string, public controller?: string)92// When controller is empty or not given krb5 will perform a DNS lookup for the default KDC server93// and retrieve its address from the DNS server94func NewKerberosClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {95// setup nucleijs utils96c := &Client{nj: utils.NewNucleiJS(runtime)}97c.nj.ObjectSig = "Client(domain, {controller})" // will be included in error messages9899// get arguments (type assertion is efficient than reflection)100// when accepting type as input like net.Conn we can use utils.GetArg101domain, _ := c.nj.GetArg(call.Arguments, 0).(string)102controller, _ := c.nj.GetArg(call.Arguments, 1).(string)103104// validate arguments105c.nj.Require(domain != "", "domain cannot be empty")106107cfg := kconfig.New()108109if controller != "" {110// validate controller hostport111executionId := c.nj.ExecutionId()112if !protocolstate.IsHostAllowed(executionId, controller) {113c.nj.Throw("domain controller address blacklisted by network policy")114}115116tmp := strings.Split(controller, ":")117if len(tmp) == 1 {118tmp = append(tmp, "88")119}120realm := strings.ToUpper(domain)121cfg.LibDefaults.DefaultRealm = realm // set default realm122cfg.Realms = []kconfig.Realm{123{124Realm: realm,125KDC: []string{tmp[0] + ":" + tmp[1]},126AdminServer: []string{tmp[0] + ":" + tmp[1]},127KPasswdServer: []string{tmp[0] + ":464"}, // default password server port128},129}130cfg.DomainRealm = make(kconfig.DomainRealm)131} else {132// if controller is empty use DNS lookup133cfg.LibDefaults.DNSLookupKDC = true134cfg.LibDefaults.DefaultRealm = strings.ToUpper(domain)135cfg.DomainRealm = make(kconfig.DomainRealm)136}137c.Krb5Config = cfg138c.Realm = strings.ToUpper(domain)139140// Link Constructor to Client and return141return utils.LinkConstructor(call, runtime, c)142}143144// NewKerberosClientFromString creates a new kerberos client from a string145// by parsing krb5.conf146// @example147// ```javascript148// const kerberos = require('nuclei/kerberos');149// const client = kerberos.NewKerberosClientFromString(`150// [libdefaults]151// default_realm = ACME.COM152// dns_lookup_kdc = true153// `);154// ```155func NewKerberosClientFromString(cfg string) (*Client, error) {156config, err := kconfig.NewFromString(cfg)157if err != nil {158return nil, err159}160return &Client{Krb5Config: config}, nil161}162163// SetConfig sets additional config for the kerberos client164// Note: as of now ip and timeout overrides are only supported165// in EnumerateUser due to fastdialer but can be extended to other methods currently166// @example167// ```javascript168// const kerberos = require('nuclei/kerberos');169// const client = new kerberos.Client('acme.com', 'kdc.acme.com');170// const cfg = new kerberos.Config();171// cfg.SetIPAddress('192.168.100.22');172// cfg.SetTimeout(5);173// client.SetConfig(cfg);174// ```175func (c *Client) SetConfig(cfg *Config) {176if cfg == nil {177c.nj.Throw("config cannot be nil")178}179c.config = *cfg180}181182// EnumerateUser and attempt to get AS-REP hash by disabling PA-FX-FAST183// @example184// ```javascript185// const kerberos = require('nuclei/kerberos');186// const client = new kerberos.Client('acme.com', 'kdc.acme.com');187// const resp = client.EnumerateUser('pdtm');188// log(resp);189// ```190func (c *Client) EnumerateUser(username string) (EnumerateUserResponse, error) {191c.nj.Require(c.Krb5Config != nil, "Kerberos client not initialized")192password := "password"193// client does not actually attempt connection it manages state here194client := kclient.NewWithPassword(username, c.Realm, password, c.Krb5Config, kclient.DisablePAFXFAST(true))195defer client.Destroy()196197// generate ASReq hash198req, err := messages.NewASReqForTGT(client.Credentials.Domain(), client.Config, client.Credentials.CName())199c.nj.HandleError(err, "failed to generate TGT request")200201// marshal request202b, err := req.Marshal()203c.nj.HandleError(err, "failed to marshal TGT request")204205data, err := SendToKDC(c, string(b))206rb := ConversionUtil.Bytes(data)207208if err == nil {209var ASRep messages.ASRep210resp := EnumerateUserResponse{Valid: true}211err = ASRep.Unmarshal(rb)212if err != nil {213resp.Error = err.Error()214return resp, nil215}216hashcatString, _ := ASRepToHashcat(ASRep)217resp.ASREPHash = hashcatString218return resp, nil219}220221resp := EnumerateUserResponse{}222e, ok := err.(messages.KRBError)223if !ok {224return resp, err225}226if e.ErrorCode == errorcode.KDC_ERR_PREAUTH_REQUIRED {227resp.Valid = true228resp.Error = errorcode.Lookup(e.ErrorCode)229return resp, nil230}231resp.Error = errorcode.Lookup(e.ErrorCode)232return resp, nil233}234235// GetServiceTicket returns a TGS for a given user, password and SPN236// @example237// ```javascript238// const kerberos = require('nuclei/kerberos');239// const client = new kerberos.Client('acme.com', 'kdc.acme.com');240// const resp = client.GetServiceTicket('pdtm', 'password', 'HOST/CLIENT1');241// log(resp);242// ```243func (c *Client) GetServiceTicket(User, Pass, SPN string) (TGS, error) {244c.nj.Require(c.Krb5Config != nil, "Kerberos client not initialized")245c.nj.Require(User != "", "User cannot be empty")246c.nj.Require(Pass != "", "Pass cannot be empty")247c.nj.Require(SPN != "", "SPN cannot be empty")248249executionId := c.nj.ExecutionId()250251if len(c.Krb5Config.Realms) > 0 {252// this means dc address was given253for _, r := range c.Krb5Config.Realms {254for _, kdc := range r.KDC {255if !protocolstate.IsHostAllowed(executionId, kdc) {256c.nj.Throw("KDC address %v blacklisted by network policy", kdc)257}258}259for _, kpasswd := range r.KPasswdServer {260if !protocolstate.IsHostAllowed(executionId, kpasswd) {261c.nj.Throw("Kpasswd address %v blacklisted by network policy", kpasswd)262}263}264}265} else {266// here net.Dialer is used instead of fastdialer hence get possible addresses267// and check if they are allowed by network policy268_, kdcs, _ := c.Krb5Config.GetKDCs(c.Realm, true)269for _, v := range kdcs {270if !protocolstate.IsHostAllowed(executionId, v) {271c.nj.Throw("KDC address %v blacklisted by network policy", v)272}273}274}275276// client does not actually attempt connection it manages state here277client := kclient.NewWithPassword(User, c.Realm, Pass, c.Krb5Config, kclient.DisablePAFXFAST(true))278defer client.Destroy()279280resp := TGS{}281282ticket, _, err := client.GetServiceTicket(SPN)283resp.Ticket = ticket284if err != nil {285if code, ok := err.(messages.KRBError); ok {286resp.ErrMsg = errorcode.Lookup(code.ErrorCode)287return resp, err288}289return resp, err290}291// convert AS-REP to hashcat format292hashcat, err := TGStoHashcat(ticket, c.Realm)293if err != nil {294if code, ok := err.(messages.KRBError); ok {295resp.ErrMsg = errorcode.Lookup(code.ErrorCode)296return resp, err297}298return resp, err299}300resp.Ticket = ticket301resp.Hash = hashcat302return resp, nil303}304305// // GetASREP returns AS-REP for a given user and password306// // it contains Client's TGT , Principal and Session Key307// // Signature: GetASREP(User, Pass)308// // @param User: string309// // @param Pass: string310// func (c *Client) GetASREP(User, Pass string) messages.ASRep {311// c.nj.Require(c.Krb5Config != nil, "Kerberos client not initialized")312// c.nj.Require(User != "", "User cannot be empty")313// c.nj.Require(Pass != "", "Pass cannot be empty")314315// if len(c.Krb5Config.Realms) > 0 {316// // this means dc address was given317// for _, r := range c.Krb5Config.Realms {318// for _, kdc := range r.KDC {319// if !protocolstate.IsHostAllowed(kdc) {320// c.nj.Throw("KDC address blacklisted by network policy")321// }322// }323// for _, kpasswd := range r.KPasswdServer {324// if !protocolstate.IsHostAllowed(kpasswd) {325// c.nj.Throw("Kpasswd address blacklisted by network policy")326// }327// }328// }329// } else {330// // here net.Dialer is used instead of fastdialer hence get possible addresses331// // and check if they are allowed by network policy332// _, kdcs, _ := c.Krb5Config.GetKDCs(c.Realm, true)333// for _, v := range kdcs {334// if !protocolstate.IsHostAllowed(v) {335// c.nj.Throw("KDC address blacklisted by network policy")336// }337// }338// }339340// // login to get TGT341// cl := kclient.NewWithPassword(User, c.Realm, Pass, c.Krb5Config, kclient.DisablePAFXFAST(true))342// defer cl.Destroy()343344// // generate ASReq345// ASReq, err := messages.NewASReqForTGT(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName())346// c.nj.HandleError(err, "failed to generate TGT request")347348// // exchange AS-REQ for AS-REP349// resp, err := cl.ASExchange(c.Realm, ASReq, 0)350// c.nj.HandleError(err, "failed to exchange AS-REQ")351352// // try to decrypt encrypted parts of the response and TGT353// key, err := resp.DecryptEncPart(cl.Credentials)354// if err == nil {355// _ = resp.Ticket.Decrypt(key)356// }357// return resp358// }359360361