diff --git a/LdapUsrEnum-linux-386 b/LdapUsrEnum-linux-386 new file mode 100755 index 0000000..19fc46a --- /dev/null +++ b/LdapUsrEnum-linux-386 Binary files differ diff --git a/LdapUsrEnum-linux-386 b/LdapUsrEnum-linux-386 new file mode 100755 index 0000000..19fc46a --- /dev/null +++ b/LdapUsrEnum-linux-386 Binary files differ diff --git a/LdapUsrEnum-linux-amd64 b/LdapUsrEnum-linux-amd64 new file mode 100755 index 0000000..19fc46a --- /dev/null +++ b/LdapUsrEnum-linux-amd64 Binary files differ diff --git a/LdapUsrEnum-linux-386 b/LdapUsrEnum-linux-386 new file mode 100755 index 0000000..19fc46a --- /dev/null +++ b/LdapUsrEnum-linux-386 Binary files differ diff --git a/LdapUsrEnum-linux-amd64 b/LdapUsrEnum-linux-amd64 new file mode 100755 index 0000000..19fc46a --- /dev/null +++ b/LdapUsrEnum-linux-amd64 Binary files differ diff --git a/LdapUsrEnum-windows-386.exe b/LdapUsrEnum-windows-386.exe new file mode 100755 index 0000000..dd8e1ca --- /dev/null +++ b/LdapUsrEnum-windows-386.exe Binary files differ diff --git a/LdapUsrEnum-linux-386 b/LdapUsrEnum-linux-386 new file mode 100755 index 0000000..19fc46a --- /dev/null +++ b/LdapUsrEnum-linux-386 Binary files differ diff --git a/LdapUsrEnum-linux-amd64 b/LdapUsrEnum-linux-amd64 new file mode 100755 index 0000000..19fc46a --- /dev/null +++ b/LdapUsrEnum-linux-amd64 Binary files differ diff --git a/LdapUsrEnum-windows-386.exe b/LdapUsrEnum-windows-386.exe new file mode 100755 index 0000000..dd8e1ca --- /dev/null +++ b/LdapUsrEnum-windows-386.exe Binary files differ diff --git a/LdapUsrEnum-windows-amd64.exe b/LdapUsrEnum-windows-amd64.exe new file mode 100755 index 0000000..dd8e1ca --- /dev/null +++ b/LdapUsrEnum-windows-amd64.exe Binary files differ diff --git a/LdapUsrEnum-linux-386 b/LdapUsrEnum-linux-386 new file mode 100755 index 0000000..19fc46a --- /dev/null +++ b/LdapUsrEnum-linux-386 Binary files differ diff --git a/LdapUsrEnum-linux-amd64 b/LdapUsrEnum-linux-amd64 new file mode 100755 index 0000000..19fc46a --- /dev/null +++ b/LdapUsrEnum-linux-amd64 Binary files differ diff --git a/LdapUsrEnum-windows-386.exe b/LdapUsrEnum-windows-386.exe new file mode 100755 index 0000000..dd8e1ca --- /dev/null +++ b/LdapUsrEnum-windows-386.exe Binary files differ diff --git a/LdapUsrEnum-windows-amd64.exe b/LdapUsrEnum-windows-amd64.exe new file mode 100755 index 0000000..dd8e1ca --- /dev/null +++ b/LdapUsrEnum-windows-amd64.exe Binary files differ diff --git a/LdapUsrEnum.go b/LdapUsrEnum.go new file mode 100755 index 0000000..d3f29ed --- /dev/null +++ b/LdapUsrEnum.go @@ -0,0 +1,411 @@ +package main + +import ( + "fmt" + "os" + "runtime" + "crypto/tls" + "strings" + "strconv" + "math" + "time" + + "github.com/akamensky/argparse" + "github.com/go-ldap/ldap/v3" +) + +func main() { + + parser := argparse.NewParser("print", "AD/LDAP Account Brute-forcer ("+runtime.GOOS+") \n ex: ./progName -d -w --username=\"\" --password=\"\"") + dc := parser.String("d", "dc", &argparse.Options{Required: true, Help: "DC to connect to, use IP or full hostname ex. --dc=\"dc.test.local\""}) + domain := parser.String("w", "domain", &argparse.Options{Required: true, Help: "domain ex. --domain=\"test.local\""}) + username := parser.String("u", "username", &argparse.Options{Required: false, Help: "username to connect with ex. --username=\"testuser\""}) + password := parser.String("p", "password", &argparse.Options{Required: false, Help: "password to connect with ex. --password=\"testpass!\""}) + var dispGroup *bool = parser.Flag("g", "groups", &argparse.Options{Required: false, Help: "display user/group relationships. -g"}) + //target := parser.String("t", "target", &argparse.Options{Required: true, Help: "username to bruteforce -target=\"testuser2\""}) + err := parser.Parse(os.Args) + if err != nil { + fmt.Print(parser.Usage(err)) + os.Exit(1) + } + + if len(*dc) == 0 || len(*domain) == 0{ + fmt.Print(parser.Usage(err)) + fmt.Println("[-] Provide DC & domain minimum") + os.Exit(1) + } + + fmt.Printf("[i] DC/AD: %v\n", *dc) + fmt.Printf("[i] domain: %v\n", *domain) + fmt.Println("[!] trying plaintext auth") + l, err := ldap.DialURL("ldap://" + *dc) + if err != nil { + fmt.Println(err) + fmt.Println("[!] trying TLS auth") + l.Close() + ldapURL := "ldaps://"+ *dc + l, err2 := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true})) + if err2 != nil { + fmt.Println(err2) + fmt.Println("[-] could not connect") + os.Exit(1) + } + defer l.Close() + } + defer l.Close() + + if len(*username) == 0{ + *username = "read-only-admin" + } + dcParts := strings.Split(*domain, ".") + dcFmt := "CN="+*username + var baseDN string + for _, element := range dcParts { + baseDN += ",DC="+element + } + dcFmt += baseDN + baseDN = baseDN[1:] + fmt.Println("[+] using: "+dcFmt) + + if len(*password) == 0{ + fmt.Println("[!] trying to connect with unauth client") + err = l.UnauthenticatedBind(dcFmt) + if err != nil { + fmt.Println("[-] unauth session failed - requires credentials") + os.Exit(1) + } + }else{ + fmt.Println("[!] trying to connect with supplied password") + err = l.Bind(*username+"@"+*domain, *password) // FFS this stumped me for SO LONG! >.< + if err != nil { + fmt.Println(err) + fmt.Println("[-] auth session failed - check credentials") + os.Exit(1) + } + } + + + // SEARCH FOR USERNAME'S "sAMAccountName" (basic test) + // Filters must start and finish with ()! + fmt.Print("[+] query to get account username... ") + var mainUser string = "" + filter := fmt.Sprintf("(CN=%s)", ldap.EscapeFilter(*username)) + searchReq := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName","distinguishedName", "primaryGroupID"}, []ldap.Control{}) + result, err := l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + if len(result.Entries) == 0{ + fmt.Println("[-] user can't query LDAP") + os.Exit(1) + } + for _, entry := range result.Entries { + mainUser = entry.GetAttributeValue("sAMAccountName") + } + fmt.Println( mainUser ) + //result.PrettyPrint(2) + + // SEARCH FOR ALL UERNAME'S + fmt.Print("[+] ALL usernames... ") + filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(SamAccountName=*))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName", "whenCreated", "whenChanged", "lastLogon",}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println( len(result.Entries)) + //result.PrettyPrint(2) + + var foundUsers []string + var foundUCreated []string + var foundUChanged []string + var foundULogon []string + for _, entry := range result.Entries { + //fmt.Printf("%s: %v\n", entry.GetAttributeValues("memberOf"), entry.GetAttributeValue("sAMAccountName")) + foundUsers = append(foundUsers, entry.GetAttributeValue("sAMAccountName") ) + foundUCreated = append(foundUCreated, entry.GetAttributeValue("whenCreated") ) + foundUChanged = append(foundUChanged, entry.GetAttributeValue("whenChanged") ) + foundULogon = append(foundULogon, entry.GetAttributeValue("lastLogon") ) + } + + // SEARCH FOR ALL LOCKED OUT ACCOUNTS + fmt.Print("[+] locked accounts... ") + filter = fmt.Sprintf("(&(sAMAccountType=805306368)(lockoutTime>=1))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println( len(result.Entries)) + + var lockedUsers = map[string]int{} + for i := 0; i < len(foundUsers); i++ { + lockedUsers[foundUsers[i]] = 0 + } + for _, entry := range result.Entries { + //fmt.Printf("%s: %v\n", entry.GetAttributeValues("memberOf"), entry.GetAttributeValue("sAMAccountName")) + lockedUsers[entry.GetAttributeValue("sAMAccountName")] = 1 + } + + // SEARCH FOR ALL DISABLED ACCOUNTS + fmt.Print("[+] disabled accounts... ") + filter = fmt.Sprintf("(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=2))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println(len(result.Entries)) + + var disabledUsers = map[string]int{} + for i := 0; i < len(foundUsers); i++ { + disabledUsers[foundUsers[i]] = 0 + } + for _, entry := range result.Entries { + disabledUsers[entry.GetAttributeValue("sAMAccountName")] = 1 + } + + // SEARCH FOR ALL PASSWORD NEVER EXPIRE + fmt.Print("[+] non-expire passwords... ") + filter = fmt.Sprintf("(&(samAccountType=805306368)(|(UserAccountControl:1.2.840.113556.1.4.803:=65536)(msDS-UserDontExpirePassword=TRUE)))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println( len(result.Entries)) + + var neverExpireUsers = map[string]int{} + for i := 0; i < len(foundUsers); i++ { + neverExpireUsers[foundUsers[i]] = 0 + } + for _, entry := range result.Entries { + neverExpireUsers[entry.GetAttributeValue("sAMAccountName")] = 1 + } + + var groups = map[string][]string{} + + // SEARCH FOR ALL groups NOT "Default users" (rid 513) + fmt.Print("[+] groups... ") + filter = fmt.Sprintf("(&(objectCategory=group)(objectClass=group))") + //filter = fmt.Sprintf("(&(CN=\"Administrator\"))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"objectCategory", "sAMAccountName", "distinguishedName"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + //result.PrettyPrint(2) + + // for each group + fmt.Println( len(result.Entries)) + fmt.Print("[+] Matching users to groups.. this could take a while!\n") + for _, entry := range result.Entries { + + // Search for all users of that group + filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(SamAccountName=*)(memberOf:1.2.840.113556.1.4.1941:=%v))", strings.Trim(entry.GetAttributeValue("distinguishedName"), "\t \n" )) + searchReq2 := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result2, err := l.Search(searchReq2) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + if len(result2.Entries) != 0 { + for _, entry2 := range result2.Entries { + groups[entry.GetAttributeValue("sAMAccountName")] = append(groups[entry.GetAttributeValue("sAMAccountName")], entry2.GetAttributeValue("sAMAccountName")) + } + } + //os.Exit(1) + } + + // All users of Default Group (RID 513) + filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(primaryGroupID=513))") + searchReq2 := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result2, err := l.Search(searchReq2) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + if len(result2.Entries) != 0 { + for _, entry2 := range result2.Entries { + groups["Default Group (RID 513)"] = append(groups["Default Group (RID 513)"], entry2.GetAttributeValue("sAMAccountName")) + } + } + + + wantedUsers := []string{} + for x, _ := range groups { // for each group + addGroup := true + for i := range groups[x] { + if groups[x][i] == mainUser { // if user in group + addGroup = false // dont add these users to list + } + } + if addGroup == true{ + for i := range groups[x] { // add the people to a list of accounts want to crack + _, found := Find(wantedUsers, groups[x][i]) + if !found { + wantedUsers = append(wantedUsers, groups[x][i] ) + + } + } + } + } + + if *dispGroup == true{ // display which users belong to which + for x, _ := range groups { + fmt.Printf("[+] group: %v \n", x) + for i := range groups[x] { + fmt.Println("[+] ", groups[x][i]) + } + } + } + + // SEARCH FOR PASSWORD POLICY + fmt.Println("[+] password policy ") + filter = fmt.Sprintf("(objectClass=domainDNS)") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"minPwdLength","minPwdAge","maxPwdAge","pwdHistoryLength","lockoutThreshold","lockoutDuration","lockOutObservationWindow"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println("--- pwd pol ---") + var foundGPO []string + for _, entry := range result.Entries { + foundGPO = []string{ + entry.GetAttributeValue("minPwdLength"), + entry.GetAttributeValue("minPwdAge"), + convertPwdAge(entry.GetAttributeValue("maxPwdAge")), + entry.GetAttributeValue("pwdHistoryLength"), + entry.GetAttributeValue("lockoutThreshold"), + convertLockout(entry.GetAttributeValue("lockoutDuration")), + convertLockout(entry.GetAttributeValue("lockOutObservationWindow")), + } + } + fmt.Printf("minPwdLength: %v \n",foundGPO[0]) + fmt.Printf("minPwdAge: %v \n",foundGPO[1]) + fmt.Printf("maxPwdAge: %v \n",foundGPO[2]) + fmt.Printf("pwdHistoryLength: %v \n",foundGPO[3]) + fmt.Printf("lockoutThreshold: %v \n",foundGPO[4]) + fmt.Printf("lockoutDuration: %v \n",foundGPO[5]) + fmt.Printf("lockOutObservationWindow: %v \n",foundGPO[6]) + + // display results + fmt.Println("--- results ---") + for i := 0; i < len(foundUsers); i++ { + fmt.Printf("%-15v", foundUsers[i]) + if lockedUsers[foundUsers[i]] == 1{ fmt.Print("Locked ")} + if disabledUsers[foundUsers[i]] == 1{ fmt.Print("Disabled ")} + if neverExpireUsers[foundUsers[i]] == 1{ fmt.Print("PassNevExp ")} + fmt.Print("\n") + lasLog := foundULogon[i] + if lasLog != "0" { + fmt.Printf(" %v ", convertLDAPDate(lasLog) ) + fmt.Printf(" (%v)\n", ldapDiff(lasLog) ) + }else{ + fmt.Println(" Never Logged In") + } + } + fmt.Println("--- to try (ALL) ---") + toTry := "" + for i := 0; i < len(foundUsers); i++ { + if lockedUsers[foundUsers[i]] == 0{ // account not locked + if disabledUsers[foundUsers[i]] == 0{ // account not disabled + toTry += ", "+foundUsers[i] + } + } + } + toTry = toTry[2:] // remove first "," + fmt.Println(toTry) + + fmt.Println("--- to try (Diff Group) ---") + toTry = "" + for i := 0; i < len(wantedUsers); i++ { + if lockedUsers[wantedUsers[i]] == 0{ // account not locked + if disabledUsers[wantedUsers[i]] == 0{ // account not disabled + toTry += ", "+wantedUsers[i] + } + } + } + toTry = toTry[2:] // remove first "," + fmt.Println(toTry) + + +} + +func ldapDiff(timestamp string)string{ + i, _ := strconv.ParseInt(timestamp, 10, 64) + date := ( i / 10000000 ) - 11644473600; + t2 := time.Unix(date, 0) + t1 := time.Now() + + diff := t1.Sub(t2) + t := time.Time{}.Add(diff) + // fmt.Println(diff) // DEBUG + formatted := fmt.Sprintf("%d day(s) %02d:%02d:%02d",RoundTime(diff.Seconds()/86400),t.Hour(), t.Minute(), t.Second()) + return formatted +} +func RoundTime(input float64) int { + var result float64 + if input < 0 { + result = math.Ceil(input - 0.5) + } else { + result = math.Floor(input + 0.5) + } + // only interested in integer, ignore fractional + i, _ := math.Modf(result) + + return int(i) +} +func convertLDAPDate(timestamp string)string{ + //baseTime := time.Date(1601, 1, 1, 0, 0, 0, 0, time.UTC) + i, _ := strconv.ParseInt(timestamp, 10, 64) + date := ( i / 10000000 ) - 11644473600; + t := time.Unix(date, 0) + formatted := fmt.Sprintf(" %d-%02d-%02d %02d:%02d:%02d", + t.Year(), t.Month(), t.Day(), + t.Hour(), t.Minute(), t.Second()) + return formatted +} +func convertPwdAge(pwdage string) string { + f, _ := strconv.ParseFloat((strings.Replace(pwdage, "-", "", -1)), 64) + age := ((f / (60 * 10000000)) / 60) / 24 + flr := math.Floor(age) + s := strconv.Itoa(int(flr)) + + return s +} +func convertLockout(lockout string) string { + i, _ := strconv.Atoi(strings.Replace(lockout, "-", "", -1)) + age := i / (60 * 10000000) + s := strconv.Itoa(age) + + return s +} +func after(value string, a string) string { + // Get substring after a string. + pos := strings.LastIndex(value, a) + if pos == -1 { + return "" + } + adjustedPos := pos + len(a) + if adjustedPos >= len(value) { + return "" + } + return value[adjustedPos:len(value)] +} + +func Find(slice []string, val string) (int, bool) { + for i, item := range slice { + if item == val { + return i, true + } + } + return -1, false +} diff --git a/LdapUsrEnum-linux-386 b/LdapUsrEnum-linux-386 new file mode 100755 index 0000000..19fc46a --- /dev/null +++ b/LdapUsrEnum-linux-386 Binary files differ diff --git a/LdapUsrEnum-linux-amd64 b/LdapUsrEnum-linux-amd64 new file mode 100755 index 0000000..19fc46a --- /dev/null +++ b/LdapUsrEnum-linux-amd64 Binary files differ diff --git a/LdapUsrEnum-windows-386.exe b/LdapUsrEnum-windows-386.exe new file mode 100755 index 0000000..dd8e1ca --- /dev/null +++ b/LdapUsrEnum-windows-386.exe Binary files differ diff --git a/LdapUsrEnum-windows-amd64.exe b/LdapUsrEnum-windows-amd64.exe new file mode 100755 index 0000000..dd8e1ca --- /dev/null +++ b/LdapUsrEnum-windows-amd64.exe Binary files differ diff --git a/LdapUsrEnum.go b/LdapUsrEnum.go new file mode 100755 index 0000000..d3f29ed --- /dev/null +++ b/LdapUsrEnum.go @@ -0,0 +1,411 @@ +package main + +import ( + "fmt" + "os" + "runtime" + "crypto/tls" + "strings" + "strconv" + "math" + "time" + + "github.com/akamensky/argparse" + "github.com/go-ldap/ldap/v3" +) + +func main() { + + parser := argparse.NewParser("print", "AD/LDAP Account Brute-forcer ("+runtime.GOOS+") \n ex: ./progName -d -w --username=\"\" --password=\"\"") + dc := parser.String("d", "dc", &argparse.Options{Required: true, Help: "DC to connect to, use IP or full hostname ex. --dc=\"dc.test.local\""}) + domain := parser.String("w", "domain", &argparse.Options{Required: true, Help: "domain ex. --domain=\"test.local\""}) + username := parser.String("u", "username", &argparse.Options{Required: false, Help: "username to connect with ex. --username=\"testuser\""}) + password := parser.String("p", "password", &argparse.Options{Required: false, Help: "password to connect with ex. --password=\"testpass!\""}) + var dispGroup *bool = parser.Flag("g", "groups", &argparse.Options{Required: false, Help: "display user/group relationships. -g"}) + //target := parser.String("t", "target", &argparse.Options{Required: true, Help: "username to bruteforce -target=\"testuser2\""}) + err := parser.Parse(os.Args) + if err != nil { + fmt.Print(parser.Usage(err)) + os.Exit(1) + } + + if len(*dc) == 0 || len(*domain) == 0{ + fmt.Print(parser.Usage(err)) + fmt.Println("[-] Provide DC & domain minimum") + os.Exit(1) + } + + fmt.Printf("[i] DC/AD: %v\n", *dc) + fmt.Printf("[i] domain: %v\n", *domain) + fmt.Println("[!] trying plaintext auth") + l, err := ldap.DialURL("ldap://" + *dc) + if err != nil { + fmt.Println(err) + fmt.Println("[!] trying TLS auth") + l.Close() + ldapURL := "ldaps://"+ *dc + l, err2 := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true})) + if err2 != nil { + fmt.Println(err2) + fmt.Println("[-] could not connect") + os.Exit(1) + } + defer l.Close() + } + defer l.Close() + + if len(*username) == 0{ + *username = "read-only-admin" + } + dcParts := strings.Split(*domain, ".") + dcFmt := "CN="+*username + var baseDN string + for _, element := range dcParts { + baseDN += ",DC="+element + } + dcFmt += baseDN + baseDN = baseDN[1:] + fmt.Println("[+] using: "+dcFmt) + + if len(*password) == 0{ + fmt.Println("[!] trying to connect with unauth client") + err = l.UnauthenticatedBind(dcFmt) + if err != nil { + fmt.Println("[-] unauth session failed - requires credentials") + os.Exit(1) + } + }else{ + fmt.Println("[!] trying to connect with supplied password") + err = l.Bind(*username+"@"+*domain, *password) // FFS this stumped me for SO LONG! >.< + if err != nil { + fmt.Println(err) + fmt.Println("[-] auth session failed - check credentials") + os.Exit(1) + } + } + + + // SEARCH FOR USERNAME'S "sAMAccountName" (basic test) + // Filters must start and finish with ()! + fmt.Print("[+] query to get account username... ") + var mainUser string = "" + filter := fmt.Sprintf("(CN=%s)", ldap.EscapeFilter(*username)) + searchReq := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName","distinguishedName", "primaryGroupID"}, []ldap.Control{}) + result, err := l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + if len(result.Entries) == 0{ + fmt.Println("[-] user can't query LDAP") + os.Exit(1) + } + for _, entry := range result.Entries { + mainUser = entry.GetAttributeValue("sAMAccountName") + } + fmt.Println( mainUser ) + //result.PrettyPrint(2) + + // SEARCH FOR ALL UERNAME'S + fmt.Print("[+] ALL usernames... ") + filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(SamAccountName=*))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName", "whenCreated", "whenChanged", "lastLogon",}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println( len(result.Entries)) + //result.PrettyPrint(2) + + var foundUsers []string + var foundUCreated []string + var foundUChanged []string + var foundULogon []string + for _, entry := range result.Entries { + //fmt.Printf("%s: %v\n", entry.GetAttributeValues("memberOf"), entry.GetAttributeValue("sAMAccountName")) + foundUsers = append(foundUsers, entry.GetAttributeValue("sAMAccountName") ) + foundUCreated = append(foundUCreated, entry.GetAttributeValue("whenCreated") ) + foundUChanged = append(foundUChanged, entry.GetAttributeValue("whenChanged") ) + foundULogon = append(foundULogon, entry.GetAttributeValue("lastLogon") ) + } + + // SEARCH FOR ALL LOCKED OUT ACCOUNTS + fmt.Print("[+] locked accounts... ") + filter = fmt.Sprintf("(&(sAMAccountType=805306368)(lockoutTime>=1))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println( len(result.Entries)) + + var lockedUsers = map[string]int{} + for i := 0; i < len(foundUsers); i++ { + lockedUsers[foundUsers[i]] = 0 + } + for _, entry := range result.Entries { + //fmt.Printf("%s: %v\n", entry.GetAttributeValues("memberOf"), entry.GetAttributeValue("sAMAccountName")) + lockedUsers[entry.GetAttributeValue("sAMAccountName")] = 1 + } + + // SEARCH FOR ALL DISABLED ACCOUNTS + fmt.Print("[+] disabled accounts... ") + filter = fmt.Sprintf("(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=2))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println(len(result.Entries)) + + var disabledUsers = map[string]int{} + for i := 0; i < len(foundUsers); i++ { + disabledUsers[foundUsers[i]] = 0 + } + for _, entry := range result.Entries { + disabledUsers[entry.GetAttributeValue("sAMAccountName")] = 1 + } + + // SEARCH FOR ALL PASSWORD NEVER EXPIRE + fmt.Print("[+] non-expire passwords... ") + filter = fmt.Sprintf("(&(samAccountType=805306368)(|(UserAccountControl:1.2.840.113556.1.4.803:=65536)(msDS-UserDontExpirePassword=TRUE)))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println( len(result.Entries)) + + var neverExpireUsers = map[string]int{} + for i := 0; i < len(foundUsers); i++ { + neverExpireUsers[foundUsers[i]] = 0 + } + for _, entry := range result.Entries { + neverExpireUsers[entry.GetAttributeValue("sAMAccountName")] = 1 + } + + var groups = map[string][]string{} + + // SEARCH FOR ALL groups NOT "Default users" (rid 513) + fmt.Print("[+] groups... ") + filter = fmt.Sprintf("(&(objectCategory=group)(objectClass=group))") + //filter = fmt.Sprintf("(&(CN=\"Administrator\"))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"objectCategory", "sAMAccountName", "distinguishedName"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + //result.PrettyPrint(2) + + // for each group + fmt.Println( len(result.Entries)) + fmt.Print("[+] Matching users to groups.. this could take a while!\n") + for _, entry := range result.Entries { + + // Search for all users of that group + filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(SamAccountName=*)(memberOf:1.2.840.113556.1.4.1941:=%v))", strings.Trim(entry.GetAttributeValue("distinguishedName"), "\t \n" )) + searchReq2 := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result2, err := l.Search(searchReq2) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + if len(result2.Entries) != 0 { + for _, entry2 := range result2.Entries { + groups[entry.GetAttributeValue("sAMAccountName")] = append(groups[entry.GetAttributeValue("sAMAccountName")], entry2.GetAttributeValue("sAMAccountName")) + } + } + //os.Exit(1) + } + + // All users of Default Group (RID 513) + filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(primaryGroupID=513))") + searchReq2 := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result2, err := l.Search(searchReq2) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + if len(result2.Entries) != 0 { + for _, entry2 := range result2.Entries { + groups["Default Group (RID 513)"] = append(groups["Default Group (RID 513)"], entry2.GetAttributeValue("sAMAccountName")) + } + } + + + wantedUsers := []string{} + for x, _ := range groups { // for each group + addGroup := true + for i := range groups[x] { + if groups[x][i] == mainUser { // if user in group + addGroup = false // dont add these users to list + } + } + if addGroup == true{ + for i := range groups[x] { // add the people to a list of accounts want to crack + _, found := Find(wantedUsers, groups[x][i]) + if !found { + wantedUsers = append(wantedUsers, groups[x][i] ) + + } + } + } + } + + if *dispGroup == true{ // display which users belong to which + for x, _ := range groups { + fmt.Printf("[+] group: %v \n", x) + for i := range groups[x] { + fmt.Println("[+] ", groups[x][i]) + } + } + } + + // SEARCH FOR PASSWORD POLICY + fmt.Println("[+] password policy ") + filter = fmt.Sprintf("(objectClass=domainDNS)") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"minPwdLength","minPwdAge","maxPwdAge","pwdHistoryLength","lockoutThreshold","lockoutDuration","lockOutObservationWindow"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println("--- pwd pol ---") + var foundGPO []string + for _, entry := range result.Entries { + foundGPO = []string{ + entry.GetAttributeValue("minPwdLength"), + entry.GetAttributeValue("minPwdAge"), + convertPwdAge(entry.GetAttributeValue("maxPwdAge")), + entry.GetAttributeValue("pwdHistoryLength"), + entry.GetAttributeValue("lockoutThreshold"), + convertLockout(entry.GetAttributeValue("lockoutDuration")), + convertLockout(entry.GetAttributeValue("lockOutObservationWindow")), + } + } + fmt.Printf("minPwdLength: %v \n",foundGPO[0]) + fmt.Printf("minPwdAge: %v \n",foundGPO[1]) + fmt.Printf("maxPwdAge: %v \n",foundGPO[2]) + fmt.Printf("pwdHistoryLength: %v \n",foundGPO[3]) + fmt.Printf("lockoutThreshold: %v \n",foundGPO[4]) + fmt.Printf("lockoutDuration: %v \n",foundGPO[5]) + fmt.Printf("lockOutObservationWindow: %v \n",foundGPO[6]) + + // display results + fmt.Println("--- results ---") + for i := 0; i < len(foundUsers); i++ { + fmt.Printf("%-15v", foundUsers[i]) + if lockedUsers[foundUsers[i]] == 1{ fmt.Print("Locked ")} + if disabledUsers[foundUsers[i]] == 1{ fmt.Print("Disabled ")} + if neverExpireUsers[foundUsers[i]] == 1{ fmt.Print("PassNevExp ")} + fmt.Print("\n") + lasLog := foundULogon[i] + if lasLog != "0" { + fmt.Printf(" %v ", convertLDAPDate(lasLog) ) + fmt.Printf(" (%v)\n", ldapDiff(lasLog) ) + }else{ + fmt.Println(" Never Logged In") + } + } + fmt.Println("--- to try (ALL) ---") + toTry := "" + for i := 0; i < len(foundUsers); i++ { + if lockedUsers[foundUsers[i]] == 0{ // account not locked + if disabledUsers[foundUsers[i]] == 0{ // account not disabled + toTry += ", "+foundUsers[i] + } + } + } + toTry = toTry[2:] // remove first "," + fmt.Println(toTry) + + fmt.Println("--- to try (Diff Group) ---") + toTry = "" + for i := 0; i < len(wantedUsers); i++ { + if lockedUsers[wantedUsers[i]] == 0{ // account not locked + if disabledUsers[wantedUsers[i]] == 0{ // account not disabled + toTry += ", "+wantedUsers[i] + } + } + } + toTry = toTry[2:] // remove first "," + fmt.Println(toTry) + + +} + +func ldapDiff(timestamp string)string{ + i, _ := strconv.ParseInt(timestamp, 10, 64) + date := ( i / 10000000 ) - 11644473600; + t2 := time.Unix(date, 0) + t1 := time.Now() + + diff := t1.Sub(t2) + t := time.Time{}.Add(diff) + // fmt.Println(diff) // DEBUG + formatted := fmt.Sprintf("%d day(s) %02d:%02d:%02d",RoundTime(diff.Seconds()/86400),t.Hour(), t.Minute(), t.Second()) + return formatted +} +func RoundTime(input float64) int { + var result float64 + if input < 0 { + result = math.Ceil(input - 0.5) + } else { + result = math.Floor(input + 0.5) + } + // only interested in integer, ignore fractional + i, _ := math.Modf(result) + + return int(i) +} +func convertLDAPDate(timestamp string)string{ + //baseTime := time.Date(1601, 1, 1, 0, 0, 0, 0, time.UTC) + i, _ := strconv.ParseInt(timestamp, 10, 64) + date := ( i / 10000000 ) - 11644473600; + t := time.Unix(date, 0) + formatted := fmt.Sprintf(" %d-%02d-%02d %02d:%02d:%02d", + t.Year(), t.Month(), t.Day(), + t.Hour(), t.Minute(), t.Second()) + return formatted +} +func convertPwdAge(pwdage string) string { + f, _ := strconv.ParseFloat((strings.Replace(pwdage, "-", "", -1)), 64) + age := ((f / (60 * 10000000)) / 60) / 24 + flr := math.Floor(age) + s := strconv.Itoa(int(flr)) + + return s +} +func convertLockout(lockout string) string { + i, _ := strconv.Atoi(strings.Replace(lockout, "-", "", -1)) + age := i / (60 * 10000000) + s := strconv.Itoa(age) + + return s +} +func after(value string, a string) string { + // Get substring after a string. + pos := strings.LastIndex(value, a) + if pos == -1 { + return "" + } + adjustedPos := pos + len(a) + if adjustedPos >= len(value) { + return "" + } + return value[adjustedPos:len(value)] +} + +func Find(slice []string, val string) (int, bool) { + for i, item := range slice { + if item == val { + return i, true + } + } + return -1, false +} diff --git a/LdapUsrEnum_Screenshot.png b/LdapUsrEnum_Screenshot.png new file mode 100755 index 0000000..7f11d70 --- /dev/null +++ b/LdapUsrEnum_Screenshot.png Binary files differ diff --git a/LdapUsrEnum-linux-386 b/LdapUsrEnum-linux-386 new file mode 100755 index 0000000..19fc46a --- /dev/null +++ b/LdapUsrEnum-linux-386 Binary files differ diff --git a/LdapUsrEnum-linux-amd64 b/LdapUsrEnum-linux-amd64 new file mode 100755 index 0000000..19fc46a --- /dev/null +++ b/LdapUsrEnum-linux-amd64 Binary files differ diff --git a/LdapUsrEnum-windows-386.exe b/LdapUsrEnum-windows-386.exe new file mode 100755 index 0000000..dd8e1ca --- /dev/null +++ b/LdapUsrEnum-windows-386.exe Binary files differ diff --git a/LdapUsrEnum-windows-amd64.exe b/LdapUsrEnum-windows-amd64.exe new file mode 100755 index 0000000..dd8e1ca --- /dev/null +++ b/LdapUsrEnum-windows-amd64.exe Binary files differ diff --git a/LdapUsrEnum.go b/LdapUsrEnum.go new file mode 100755 index 0000000..d3f29ed --- /dev/null +++ b/LdapUsrEnum.go @@ -0,0 +1,411 @@ +package main + +import ( + "fmt" + "os" + "runtime" + "crypto/tls" + "strings" + "strconv" + "math" + "time" + + "github.com/akamensky/argparse" + "github.com/go-ldap/ldap/v3" +) + +func main() { + + parser := argparse.NewParser("print", "AD/LDAP Account Brute-forcer ("+runtime.GOOS+") \n ex: ./progName -d -w --username=\"\" --password=\"\"") + dc := parser.String("d", "dc", &argparse.Options{Required: true, Help: "DC to connect to, use IP or full hostname ex. --dc=\"dc.test.local\""}) + domain := parser.String("w", "domain", &argparse.Options{Required: true, Help: "domain ex. --domain=\"test.local\""}) + username := parser.String("u", "username", &argparse.Options{Required: false, Help: "username to connect with ex. --username=\"testuser\""}) + password := parser.String("p", "password", &argparse.Options{Required: false, Help: "password to connect with ex. --password=\"testpass!\""}) + var dispGroup *bool = parser.Flag("g", "groups", &argparse.Options{Required: false, Help: "display user/group relationships. -g"}) + //target := parser.String("t", "target", &argparse.Options{Required: true, Help: "username to bruteforce -target=\"testuser2\""}) + err := parser.Parse(os.Args) + if err != nil { + fmt.Print(parser.Usage(err)) + os.Exit(1) + } + + if len(*dc) == 0 || len(*domain) == 0{ + fmt.Print(parser.Usage(err)) + fmt.Println("[-] Provide DC & domain minimum") + os.Exit(1) + } + + fmt.Printf("[i] DC/AD: %v\n", *dc) + fmt.Printf("[i] domain: %v\n", *domain) + fmt.Println("[!] trying plaintext auth") + l, err := ldap.DialURL("ldap://" + *dc) + if err != nil { + fmt.Println(err) + fmt.Println("[!] trying TLS auth") + l.Close() + ldapURL := "ldaps://"+ *dc + l, err2 := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true})) + if err2 != nil { + fmt.Println(err2) + fmt.Println("[-] could not connect") + os.Exit(1) + } + defer l.Close() + } + defer l.Close() + + if len(*username) == 0{ + *username = "read-only-admin" + } + dcParts := strings.Split(*domain, ".") + dcFmt := "CN="+*username + var baseDN string + for _, element := range dcParts { + baseDN += ",DC="+element + } + dcFmt += baseDN + baseDN = baseDN[1:] + fmt.Println("[+] using: "+dcFmt) + + if len(*password) == 0{ + fmt.Println("[!] trying to connect with unauth client") + err = l.UnauthenticatedBind(dcFmt) + if err != nil { + fmt.Println("[-] unauth session failed - requires credentials") + os.Exit(1) + } + }else{ + fmt.Println("[!] trying to connect with supplied password") + err = l.Bind(*username+"@"+*domain, *password) // FFS this stumped me for SO LONG! >.< + if err != nil { + fmt.Println(err) + fmt.Println("[-] auth session failed - check credentials") + os.Exit(1) + } + } + + + // SEARCH FOR USERNAME'S "sAMAccountName" (basic test) + // Filters must start and finish with ()! + fmt.Print("[+] query to get account username... ") + var mainUser string = "" + filter := fmt.Sprintf("(CN=%s)", ldap.EscapeFilter(*username)) + searchReq := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName","distinguishedName", "primaryGroupID"}, []ldap.Control{}) + result, err := l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + if len(result.Entries) == 0{ + fmt.Println("[-] user can't query LDAP") + os.Exit(1) + } + for _, entry := range result.Entries { + mainUser = entry.GetAttributeValue("sAMAccountName") + } + fmt.Println( mainUser ) + //result.PrettyPrint(2) + + // SEARCH FOR ALL UERNAME'S + fmt.Print("[+] ALL usernames... ") + filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(SamAccountName=*))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName", "whenCreated", "whenChanged", "lastLogon",}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println( len(result.Entries)) + //result.PrettyPrint(2) + + var foundUsers []string + var foundUCreated []string + var foundUChanged []string + var foundULogon []string + for _, entry := range result.Entries { + //fmt.Printf("%s: %v\n", entry.GetAttributeValues("memberOf"), entry.GetAttributeValue("sAMAccountName")) + foundUsers = append(foundUsers, entry.GetAttributeValue("sAMAccountName") ) + foundUCreated = append(foundUCreated, entry.GetAttributeValue("whenCreated") ) + foundUChanged = append(foundUChanged, entry.GetAttributeValue("whenChanged") ) + foundULogon = append(foundULogon, entry.GetAttributeValue("lastLogon") ) + } + + // SEARCH FOR ALL LOCKED OUT ACCOUNTS + fmt.Print("[+] locked accounts... ") + filter = fmt.Sprintf("(&(sAMAccountType=805306368)(lockoutTime>=1))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println( len(result.Entries)) + + var lockedUsers = map[string]int{} + for i := 0; i < len(foundUsers); i++ { + lockedUsers[foundUsers[i]] = 0 + } + for _, entry := range result.Entries { + //fmt.Printf("%s: %v\n", entry.GetAttributeValues("memberOf"), entry.GetAttributeValue("sAMAccountName")) + lockedUsers[entry.GetAttributeValue("sAMAccountName")] = 1 + } + + // SEARCH FOR ALL DISABLED ACCOUNTS + fmt.Print("[+] disabled accounts... ") + filter = fmt.Sprintf("(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=2))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println(len(result.Entries)) + + var disabledUsers = map[string]int{} + for i := 0; i < len(foundUsers); i++ { + disabledUsers[foundUsers[i]] = 0 + } + for _, entry := range result.Entries { + disabledUsers[entry.GetAttributeValue("sAMAccountName")] = 1 + } + + // SEARCH FOR ALL PASSWORD NEVER EXPIRE + fmt.Print("[+] non-expire passwords... ") + filter = fmt.Sprintf("(&(samAccountType=805306368)(|(UserAccountControl:1.2.840.113556.1.4.803:=65536)(msDS-UserDontExpirePassword=TRUE)))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println( len(result.Entries)) + + var neverExpireUsers = map[string]int{} + for i := 0; i < len(foundUsers); i++ { + neverExpireUsers[foundUsers[i]] = 0 + } + for _, entry := range result.Entries { + neverExpireUsers[entry.GetAttributeValue("sAMAccountName")] = 1 + } + + var groups = map[string][]string{} + + // SEARCH FOR ALL groups NOT "Default users" (rid 513) + fmt.Print("[+] groups... ") + filter = fmt.Sprintf("(&(objectCategory=group)(objectClass=group))") + //filter = fmt.Sprintf("(&(CN=\"Administrator\"))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"objectCategory", "sAMAccountName", "distinguishedName"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + //result.PrettyPrint(2) + + // for each group + fmt.Println( len(result.Entries)) + fmt.Print("[+] Matching users to groups.. this could take a while!\n") + for _, entry := range result.Entries { + + // Search for all users of that group + filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(SamAccountName=*)(memberOf:1.2.840.113556.1.4.1941:=%v))", strings.Trim(entry.GetAttributeValue("distinguishedName"), "\t \n" )) + searchReq2 := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result2, err := l.Search(searchReq2) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + if len(result2.Entries) != 0 { + for _, entry2 := range result2.Entries { + groups[entry.GetAttributeValue("sAMAccountName")] = append(groups[entry.GetAttributeValue("sAMAccountName")], entry2.GetAttributeValue("sAMAccountName")) + } + } + //os.Exit(1) + } + + // All users of Default Group (RID 513) + filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(primaryGroupID=513))") + searchReq2 := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result2, err := l.Search(searchReq2) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + if len(result2.Entries) != 0 { + for _, entry2 := range result2.Entries { + groups["Default Group (RID 513)"] = append(groups["Default Group (RID 513)"], entry2.GetAttributeValue("sAMAccountName")) + } + } + + + wantedUsers := []string{} + for x, _ := range groups { // for each group + addGroup := true + for i := range groups[x] { + if groups[x][i] == mainUser { // if user in group + addGroup = false // dont add these users to list + } + } + if addGroup == true{ + for i := range groups[x] { // add the people to a list of accounts want to crack + _, found := Find(wantedUsers, groups[x][i]) + if !found { + wantedUsers = append(wantedUsers, groups[x][i] ) + + } + } + } + } + + if *dispGroup == true{ // display which users belong to which + for x, _ := range groups { + fmt.Printf("[+] group: %v \n", x) + for i := range groups[x] { + fmt.Println("[+] ", groups[x][i]) + } + } + } + + // SEARCH FOR PASSWORD POLICY + fmt.Println("[+] password policy ") + filter = fmt.Sprintf("(objectClass=domainDNS)") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"minPwdLength","minPwdAge","maxPwdAge","pwdHistoryLength","lockoutThreshold","lockoutDuration","lockOutObservationWindow"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println("--- pwd pol ---") + var foundGPO []string + for _, entry := range result.Entries { + foundGPO = []string{ + entry.GetAttributeValue("minPwdLength"), + entry.GetAttributeValue("minPwdAge"), + convertPwdAge(entry.GetAttributeValue("maxPwdAge")), + entry.GetAttributeValue("pwdHistoryLength"), + entry.GetAttributeValue("lockoutThreshold"), + convertLockout(entry.GetAttributeValue("lockoutDuration")), + convertLockout(entry.GetAttributeValue("lockOutObservationWindow")), + } + } + fmt.Printf("minPwdLength: %v \n",foundGPO[0]) + fmt.Printf("minPwdAge: %v \n",foundGPO[1]) + fmt.Printf("maxPwdAge: %v \n",foundGPO[2]) + fmt.Printf("pwdHistoryLength: %v \n",foundGPO[3]) + fmt.Printf("lockoutThreshold: %v \n",foundGPO[4]) + fmt.Printf("lockoutDuration: %v \n",foundGPO[5]) + fmt.Printf("lockOutObservationWindow: %v \n",foundGPO[6]) + + // display results + fmt.Println("--- results ---") + for i := 0; i < len(foundUsers); i++ { + fmt.Printf("%-15v", foundUsers[i]) + if lockedUsers[foundUsers[i]] == 1{ fmt.Print("Locked ")} + if disabledUsers[foundUsers[i]] == 1{ fmt.Print("Disabled ")} + if neverExpireUsers[foundUsers[i]] == 1{ fmt.Print("PassNevExp ")} + fmt.Print("\n") + lasLog := foundULogon[i] + if lasLog != "0" { + fmt.Printf(" %v ", convertLDAPDate(lasLog) ) + fmt.Printf(" (%v)\n", ldapDiff(lasLog) ) + }else{ + fmt.Println(" Never Logged In") + } + } + fmt.Println("--- to try (ALL) ---") + toTry := "" + for i := 0; i < len(foundUsers); i++ { + if lockedUsers[foundUsers[i]] == 0{ // account not locked + if disabledUsers[foundUsers[i]] == 0{ // account not disabled + toTry += ", "+foundUsers[i] + } + } + } + toTry = toTry[2:] // remove first "," + fmt.Println(toTry) + + fmt.Println("--- to try (Diff Group) ---") + toTry = "" + for i := 0; i < len(wantedUsers); i++ { + if lockedUsers[wantedUsers[i]] == 0{ // account not locked + if disabledUsers[wantedUsers[i]] == 0{ // account not disabled + toTry += ", "+wantedUsers[i] + } + } + } + toTry = toTry[2:] // remove first "," + fmt.Println(toTry) + + +} + +func ldapDiff(timestamp string)string{ + i, _ := strconv.ParseInt(timestamp, 10, 64) + date := ( i / 10000000 ) - 11644473600; + t2 := time.Unix(date, 0) + t1 := time.Now() + + diff := t1.Sub(t2) + t := time.Time{}.Add(diff) + // fmt.Println(diff) // DEBUG + formatted := fmt.Sprintf("%d day(s) %02d:%02d:%02d",RoundTime(diff.Seconds()/86400),t.Hour(), t.Minute(), t.Second()) + return formatted +} +func RoundTime(input float64) int { + var result float64 + if input < 0 { + result = math.Ceil(input - 0.5) + } else { + result = math.Floor(input + 0.5) + } + // only interested in integer, ignore fractional + i, _ := math.Modf(result) + + return int(i) +} +func convertLDAPDate(timestamp string)string{ + //baseTime := time.Date(1601, 1, 1, 0, 0, 0, 0, time.UTC) + i, _ := strconv.ParseInt(timestamp, 10, 64) + date := ( i / 10000000 ) - 11644473600; + t := time.Unix(date, 0) + formatted := fmt.Sprintf(" %d-%02d-%02d %02d:%02d:%02d", + t.Year(), t.Month(), t.Day(), + t.Hour(), t.Minute(), t.Second()) + return formatted +} +func convertPwdAge(pwdage string) string { + f, _ := strconv.ParseFloat((strings.Replace(pwdage, "-", "", -1)), 64) + age := ((f / (60 * 10000000)) / 60) / 24 + flr := math.Floor(age) + s := strconv.Itoa(int(flr)) + + return s +} +func convertLockout(lockout string) string { + i, _ := strconv.Atoi(strings.Replace(lockout, "-", "", -1)) + age := i / (60 * 10000000) + s := strconv.Itoa(age) + + return s +} +func after(value string, a string) string { + // Get substring after a string. + pos := strings.LastIndex(value, a) + if pos == -1 { + return "" + } + adjustedPos := pos + len(a) + if adjustedPos >= len(value) { + return "" + } + return value[adjustedPos:len(value)] +} + +func Find(slice []string, val string) (int, bool) { + for i, item := range slice { + if item == val { + return i, true + } + } + return -1, false +} diff --git a/LdapUsrEnum_Screenshot.png b/LdapUsrEnum_Screenshot.png new file mode 100755 index 0000000..7f11d70 --- /dev/null +++ b/LdapUsrEnum_Screenshot.png Binary files differ diff --git a/go.mod b/go.mod new file mode 100755 index 0000000..3f09e41 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module main + +go 1.16 + +require ( + github.com/akamensky/argparse v1.3.1 + github.com/go-ldap/ldap/v3 v3.3.0 // indirect + gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect + gopkg.in/ldap.v2 v2.5.1 // indirect +) diff --git a/LdapUsrEnum-linux-386 b/LdapUsrEnum-linux-386 new file mode 100755 index 0000000..19fc46a --- /dev/null +++ b/LdapUsrEnum-linux-386 Binary files differ diff --git a/LdapUsrEnum-linux-amd64 b/LdapUsrEnum-linux-amd64 new file mode 100755 index 0000000..19fc46a --- /dev/null +++ b/LdapUsrEnum-linux-amd64 Binary files differ diff --git a/LdapUsrEnum-windows-386.exe b/LdapUsrEnum-windows-386.exe new file mode 100755 index 0000000..dd8e1ca --- /dev/null +++ b/LdapUsrEnum-windows-386.exe Binary files differ diff --git a/LdapUsrEnum-windows-amd64.exe b/LdapUsrEnum-windows-amd64.exe new file mode 100755 index 0000000..dd8e1ca --- /dev/null +++ b/LdapUsrEnum-windows-amd64.exe Binary files differ diff --git a/LdapUsrEnum.go b/LdapUsrEnum.go new file mode 100755 index 0000000..d3f29ed --- /dev/null +++ b/LdapUsrEnum.go @@ -0,0 +1,411 @@ +package main + +import ( + "fmt" + "os" + "runtime" + "crypto/tls" + "strings" + "strconv" + "math" + "time" + + "github.com/akamensky/argparse" + "github.com/go-ldap/ldap/v3" +) + +func main() { + + parser := argparse.NewParser("print", "AD/LDAP Account Brute-forcer ("+runtime.GOOS+") \n ex: ./progName -d -w --username=\"\" --password=\"\"") + dc := parser.String("d", "dc", &argparse.Options{Required: true, Help: "DC to connect to, use IP or full hostname ex. --dc=\"dc.test.local\""}) + domain := parser.String("w", "domain", &argparse.Options{Required: true, Help: "domain ex. --domain=\"test.local\""}) + username := parser.String("u", "username", &argparse.Options{Required: false, Help: "username to connect with ex. --username=\"testuser\""}) + password := parser.String("p", "password", &argparse.Options{Required: false, Help: "password to connect with ex. --password=\"testpass!\""}) + var dispGroup *bool = parser.Flag("g", "groups", &argparse.Options{Required: false, Help: "display user/group relationships. -g"}) + //target := parser.String("t", "target", &argparse.Options{Required: true, Help: "username to bruteforce -target=\"testuser2\""}) + err := parser.Parse(os.Args) + if err != nil { + fmt.Print(parser.Usage(err)) + os.Exit(1) + } + + if len(*dc) == 0 || len(*domain) == 0{ + fmt.Print(parser.Usage(err)) + fmt.Println("[-] Provide DC & domain minimum") + os.Exit(1) + } + + fmt.Printf("[i] DC/AD: %v\n", *dc) + fmt.Printf("[i] domain: %v\n", *domain) + fmt.Println("[!] trying plaintext auth") + l, err := ldap.DialURL("ldap://" + *dc) + if err != nil { + fmt.Println(err) + fmt.Println("[!] trying TLS auth") + l.Close() + ldapURL := "ldaps://"+ *dc + l, err2 := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true})) + if err2 != nil { + fmt.Println(err2) + fmt.Println("[-] could not connect") + os.Exit(1) + } + defer l.Close() + } + defer l.Close() + + if len(*username) == 0{ + *username = "read-only-admin" + } + dcParts := strings.Split(*domain, ".") + dcFmt := "CN="+*username + var baseDN string + for _, element := range dcParts { + baseDN += ",DC="+element + } + dcFmt += baseDN + baseDN = baseDN[1:] + fmt.Println("[+] using: "+dcFmt) + + if len(*password) == 0{ + fmt.Println("[!] trying to connect with unauth client") + err = l.UnauthenticatedBind(dcFmt) + if err != nil { + fmt.Println("[-] unauth session failed - requires credentials") + os.Exit(1) + } + }else{ + fmt.Println("[!] trying to connect with supplied password") + err = l.Bind(*username+"@"+*domain, *password) // FFS this stumped me for SO LONG! >.< + if err != nil { + fmt.Println(err) + fmt.Println("[-] auth session failed - check credentials") + os.Exit(1) + } + } + + + // SEARCH FOR USERNAME'S "sAMAccountName" (basic test) + // Filters must start and finish with ()! + fmt.Print("[+] query to get account username... ") + var mainUser string = "" + filter := fmt.Sprintf("(CN=%s)", ldap.EscapeFilter(*username)) + searchReq := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName","distinguishedName", "primaryGroupID"}, []ldap.Control{}) + result, err := l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + if len(result.Entries) == 0{ + fmt.Println("[-] user can't query LDAP") + os.Exit(1) + } + for _, entry := range result.Entries { + mainUser = entry.GetAttributeValue("sAMAccountName") + } + fmt.Println( mainUser ) + //result.PrettyPrint(2) + + // SEARCH FOR ALL UERNAME'S + fmt.Print("[+] ALL usernames... ") + filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(SamAccountName=*))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName", "whenCreated", "whenChanged", "lastLogon",}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println( len(result.Entries)) + //result.PrettyPrint(2) + + var foundUsers []string + var foundUCreated []string + var foundUChanged []string + var foundULogon []string + for _, entry := range result.Entries { + //fmt.Printf("%s: %v\n", entry.GetAttributeValues("memberOf"), entry.GetAttributeValue("sAMAccountName")) + foundUsers = append(foundUsers, entry.GetAttributeValue("sAMAccountName") ) + foundUCreated = append(foundUCreated, entry.GetAttributeValue("whenCreated") ) + foundUChanged = append(foundUChanged, entry.GetAttributeValue("whenChanged") ) + foundULogon = append(foundULogon, entry.GetAttributeValue("lastLogon") ) + } + + // SEARCH FOR ALL LOCKED OUT ACCOUNTS + fmt.Print("[+] locked accounts... ") + filter = fmt.Sprintf("(&(sAMAccountType=805306368)(lockoutTime>=1))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println( len(result.Entries)) + + var lockedUsers = map[string]int{} + for i := 0; i < len(foundUsers); i++ { + lockedUsers[foundUsers[i]] = 0 + } + for _, entry := range result.Entries { + //fmt.Printf("%s: %v\n", entry.GetAttributeValues("memberOf"), entry.GetAttributeValue("sAMAccountName")) + lockedUsers[entry.GetAttributeValue("sAMAccountName")] = 1 + } + + // SEARCH FOR ALL DISABLED ACCOUNTS + fmt.Print("[+] disabled accounts... ") + filter = fmt.Sprintf("(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=2))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println(len(result.Entries)) + + var disabledUsers = map[string]int{} + for i := 0; i < len(foundUsers); i++ { + disabledUsers[foundUsers[i]] = 0 + } + for _, entry := range result.Entries { + disabledUsers[entry.GetAttributeValue("sAMAccountName")] = 1 + } + + // SEARCH FOR ALL PASSWORD NEVER EXPIRE + fmt.Print("[+] non-expire passwords... ") + filter = fmt.Sprintf("(&(samAccountType=805306368)(|(UserAccountControl:1.2.840.113556.1.4.803:=65536)(msDS-UserDontExpirePassword=TRUE)))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println( len(result.Entries)) + + var neverExpireUsers = map[string]int{} + for i := 0; i < len(foundUsers); i++ { + neverExpireUsers[foundUsers[i]] = 0 + } + for _, entry := range result.Entries { + neverExpireUsers[entry.GetAttributeValue("sAMAccountName")] = 1 + } + + var groups = map[string][]string{} + + // SEARCH FOR ALL groups NOT "Default users" (rid 513) + fmt.Print("[+] groups... ") + filter = fmt.Sprintf("(&(objectCategory=group)(objectClass=group))") + //filter = fmt.Sprintf("(&(CN=\"Administrator\"))") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"objectCategory", "sAMAccountName", "distinguishedName"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + //result.PrettyPrint(2) + + // for each group + fmt.Println( len(result.Entries)) + fmt.Print("[+] Matching users to groups.. this could take a while!\n") + for _, entry := range result.Entries { + + // Search for all users of that group + filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(SamAccountName=*)(memberOf:1.2.840.113556.1.4.1941:=%v))", strings.Trim(entry.GetAttributeValue("distinguishedName"), "\t \n" )) + searchReq2 := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result2, err := l.Search(searchReq2) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + if len(result2.Entries) != 0 { + for _, entry2 := range result2.Entries { + groups[entry.GetAttributeValue("sAMAccountName")] = append(groups[entry.GetAttributeValue("sAMAccountName")], entry2.GetAttributeValue("sAMAccountName")) + } + } + //os.Exit(1) + } + + // All users of Default Group (RID 513) + filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(primaryGroupID=513))") + searchReq2 := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{}) + result2, err := l.Search(searchReq2) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + if len(result2.Entries) != 0 { + for _, entry2 := range result2.Entries { + groups["Default Group (RID 513)"] = append(groups["Default Group (RID 513)"], entry2.GetAttributeValue("sAMAccountName")) + } + } + + + wantedUsers := []string{} + for x, _ := range groups { // for each group + addGroup := true + for i := range groups[x] { + if groups[x][i] == mainUser { // if user in group + addGroup = false // dont add these users to list + } + } + if addGroup == true{ + for i := range groups[x] { // add the people to a list of accounts want to crack + _, found := Find(wantedUsers, groups[x][i]) + if !found { + wantedUsers = append(wantedUsers, groups[x][i] ) + + } + } + } + } + + if *dispGroup == true{ // display which users belong to which + for x, _ := range groups { + fmt.Printf("[+] group: %v \n", x) + for i := range groups[x] { + fmt.Println("[+] ", groups[x][i]) + } + } + } + + // SEARCH FOR PASSWORD POLICY + fmt.Println("[+] password policy ") + filter = fmt.Sprintf("(objectClass=domainDNS)") + searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"minPwdLength","minPwdAge","maxPwdAge","pwdHistoryLength","lockoutThreshold","lockoutDuration","lockOutObservationWindow"}, []ldap.Control{}) + result, err = l.Search(searchReq) + if err != nil { + fmt.Println("[-] failed to query LDAP: %w", err) + os.Exit(1) + } + fmt.Println("--- pwd pol ---") + var foundGPO []string + for _, entry := range result.Entries { + foundGPO = []string{ + entry.GetAttributeValue("minPwdLength"), + entry.GetAttributeValue("minPwdAge"), + convertPwdAge(entry.GetAttributeValue("maxPwdAge")), + entry.GetAttributeValue("pwdHistoryLength"), + entry.GetAttributeValue("lockoutThreshold"), + convertLockout(entry.GetAttributeValue("lockoutDuration")), + convertLockout(entry.GetAttributeValue("lockOutObservationWindow")), + } + } + fmt.Printf("minPwdLength: %v \n",foundGPO[0]) + fmt.Printf("minPwdAge: %v \n",foundGPO[1]) + fmt.Printf("maxPwdAge: %v \n",foundGPO[2]) + fmt.Printf("pwdHistoryLength: %v \n",foundGPO[3]) + fmt.Printf("lockoutThreshold: %v \n",foundGPO[4]) + fmt.Printf("lockoutDuration: %v \n",foundGPO[5]) + fmt.Printf("lockOutObservationWindow: %v \n",foundGPO[6]) + + // display results + fmt.Println("--- results ---") + for i := 0; i < len(foundUsers); i++ { + fmt.Printf("%-15v", foundUsers[i]) + if lockedUsers[foundUsers[i]] == 1{ fmt.Print("Locked ")} + if disabledUsers[foundUsers[i]] == 1{ fmt.Print("Disabled ")} + if neverExpireUsers[foundUsers[i]] == 1{ fmt.Print("PassNevExp ")} + fmt.Print("\n") + lasLog := foundULogon[i] + if lasLog != "0" { + fmt.Printf(" %v ", convertLDAPDate(lasLog) ) + fmt.Printf(" (%v)\n", ldapDiff(lasLog) ) + }else{ + fmt.Println(" Never Logged In") + } + } + fmt.Println("--- to try (ALL) ---") + toTry := "" + for i := 0; i < len(foundUsers); i++ { + if lockedUsers[foundUsers[i]] == 0{ // account not locked + if disabledUsers[foundUsers[i]] == 0{ // account not disabled + toTry += ", "+foundUsers[i] + } + } + } + toTry = toTry[2:] // remove first "," + fmt.Println(toTry) + + fmt.Println("--- to try (Diff Group) ---") + toTry = "" + for i := 0; i < len(wantedUsers); i++ { + if lockedUsers[wantedUsers[i]] == 0{ // account not locked + if disabledUsers[wantedUsers[i]] == 0{ // account not disabled + toTry += ", "+wantedUsers[i] + } + } + } + toTry = toTry[2:] // remove first "," + fmt.Println(toTry) + + +} + +func ldapDiff(timestamp string)string{ + i, _ := strconv.ParseInt(timestamp, 10, 64) + date := ( i / 10000000 ) - 11644473600; + t2 := time.Unix(date, 0) + t1 := time.Now() + + diff := t1.Sub(t2) + t := time.Time{}.Add(diff) + // fmt.Println(diff) // DEBUG + formatted := fmt.Sprintf("%d day(s) %02d:%02d:%02d",RoundTime(diff.Seconds()/86400),t.Hour(), t.Minute(), t.Second()) + return formatted +} +func RoundTime(input float64) int { + var result float64 + if input < 0 { + result = math.Ceil(input - 0.5) + } else { + result = math.Floor(input + 0.5) + } + // only interested in integer, ignore fractional + i, _ := math.Modf(result) + + return int(i) +} +func convertLDAPDate(timestamp string)string{ + //baseTime := time.Date(1601, 1, 1, 0, 0, 0, 0, time.UTC) + i, _ := strconv.ParseInt(timestamp, 10, 64) + date := ( i / 10000000 ) - 11644473600; + t := time.Unix(date, 0) + formatted := fmt.Sprintf(" %d-%02d-%02d %02d:%02d:%02d", + t.Year(), t.Month(), t.Day(), + t.Hour(), t.Minute(), t.Second()) + return formatted +} +func convertPwdAge(pwdage string) string { + f, _ := strconv.ParseFloat((strings.Replace(pwdage, "-", "", -1)), 64) + age := ((f / (60 * 10000000)) / 60) / 24 + flr := math.Floor(age) + s := strconv.Itoa(int(flr)) + + return s +} +func convertLockout(lockout string) string { + i, _ := strconv.Atoi(strings.Replace(lockout, "-", "", -1)) + age := i / (60 * 10000000) + s := strconv.Itoa(age) + + return s +} +func after(value string, a string) string { + // Get substring after a string. + pos := strings.LastIndex(value, a) + if pos == -1 { + return "" + } + adjustedPos := pos + len(a) + if adjustedPos >= len(value) { + return "" + } + return value[adjustedPos:len(value)] +} + +func Find(slice []string, val string) (int, bool) { + for i, item := range slice { + if item == val { + return i, true + } + } + return -1, false +} diff --git a/LdapUsrEnum_Screenshot.png b/LdapUsrEnum_Screenshot.png new file mode 100755 index 0000000..7f11d70 --- /dev/null +++ b/LdapUsrEnum_Screenshot.png Binary files differ diff --git a/go.mod b/go.mod new file mode 100755 index 0000000..3f09e41 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module main + +go 1.16 + +require ( + github.com/akamensky/argparse v1.3.1 + github.com/go-ldap/ldap/v3 v3.3.0 // indirect + gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect + gopkg.in/ldap.v2 v2.5.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100755 index 0000000..e5f7c9a --- /dev/null +++ b/go.sum @@ -0,0 +1,19 @@ +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/akamensky/argparse v1.3.1 h1:kP6+OyvR0fuBH6UhbE6yh/nskrDEIQgEA1SUXDPjx4g= +github.com/akamensky/argparse v1.3.1/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA= +github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= +github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ= +github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= +gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU= +gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk=