Initial commit
1 parent 535dccc commit 15401d91de8388aac1cef9aaccca1b3b89ece4f5
root authored on 27 Aug 2021
Showing 8 changed files
View
LdapUsrEnum-linux-386 0 → 100755
Not supported
View
LdapUsrEnum-linux-amd64 0 → 100755
Not supported
View
LdapUsrEnum-windows-386.exe 0 → 100755
Not supported
View
LdapUsrEnum-windows-amd64.exe 0 → 100755
Not supported
View
412
LdapUsrEnum.go 0 → 100755
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 <ip> -w <domain.com> --username=\"<username>\" --password=\"<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
}
View
LdapUsrEnum_Screenshot.png 0 → 100755
View
11
go.mod 0 → 100755
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
)
View
20
go.sum 0 → 100755
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=
Buy Me A Coffee