Skip to content

Commit

Permalink
Improved NTDS.DIT import, since multi value reads are now possible fr…
Browse files Browse the repository at this point in the history
…om ESE databases, changed how regular domain and server autodetection works
  • Loading branch information
lkarlslund committed Nov 20, 2024
1 parent 950b497 commit cdcd40c
Show file tree
Hide file tree
Showing 7 changed files with 470 additions and 337 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ require (
github.com/spf13/viper v1.19.0
github.com/timtadh/lexmachine v0.2.3
go.etcd.io/bbolt v1.3.11
www.velocidex.com/golang/go-ese v0.2.1-0.20240207005444-85d57b555f8b
www.velocidex.com/golang/go-ese v0.2.1-0.20240919031656-49fc3c3f8373
)

require github.com/timtadh/data-structures v0.6.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -537,3 +537,5 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
www.velocidex.com/golang/go-ese v0.2.1-0.20240207005444-85d57b555f8b h1:3pFfQuY3k0qViJDlLqmUfGP4YkQIl25Vc/Uq8Pl0qLA=
www.velocidex.com/golang/go-ese v0.2.1-0.20240207005444-85d57b555f8b/go.mod h1:6fC9T6UGLbM7icuA0ugomU5HbFC5XA5I30zlWtZT8YE=
www.velocidex.com/golang/go-ese v0.2.1-0.20240919031656-49fc3c3f8373 h1:XyH8GmoBOXNn1vlZjyZ5XxYHfIzKn8YJhyDdJWiPxGU=
www.velocidex.com/golang/go-ese v0.2.1-0.20240919031656-49fc3c3f8373/go.mod h1:8RT+ep8ek2tEu4jXydNAZt4+bXMbUq4K/3mu1IIs3fM=
114 changes: 28 additions & 86 deletions modules/integrations/activedirectory/collect/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"io/fs"
"io/ioutil"
"net"
"os"
"path/filepath"
"runtime"
Expand All @@ -16,7 +15,6 @@ import (
"github.com/lkarlslund/adalanche/modules/ui"
"github.com/pkg/errors"

"github.com/Showmax/go-fqdn"
"github.com/gofrs/uuid"
"github.com/lkarlslund/adalanche/modules/basedata"
clicollect "github.com/lkarlslund/adalanche/modules/cli/collect"
Expand Down Expand Up @@ -71,6 +69,7 @@ var (

authmode AuthMode
tlsmode TLSmode
options LDAPOptions
)

func init() {
Expand All @@ -93,67 +92,35 @@ func PreRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("unknown TLS mode %v", tlsmode)
}

if *port == -1 {
if tlsmode == TLS {
*port = 636
} else {
*port = 389
}
}

authmode, err = AuthModeString(*AuthmodeString)
if err != nil {
return fmt.Errorf("unknown auth mode %v", authmode)
}

// AUTODETECTION
if *autodetect {
if len(*servers) == 0 {
// We only need to auto-detect the domain if the server is not supplied
if *domain == "" {
ui.Info().Msg("No domain supplied, auto-detecting")
*domain = strings.ToLower(os.Getenv("USERDNSDOMAIN"))
if *domain == "" {
// That didn't work, lets try something else
f, err := fqdn.FqdnHostname()
if err == nil && strings.Contains(f, ".") {
ui.Info().Msg("No USERDNSDOMAIN set - using machines FQDN as basis")
*domain = strings.ToLower(f[strings.Index(f, ".")+1:])
}
}
if *domain == "" {
return errors.New("Domain auto-detection failed")
} else {
ui.Info().Msgf("Auto-detected domain as %v", *domain)
}
}

// Auto-detect server
cname, dnsservers, err := net.LookupSRV("", "", "_ldap._tcp.dc._msdcs."+*domain)
if err == nil && cname != "" && len(dnsservers) != 0 {
for _, dnsserver := range dnsservers {
*servers = append(*servers, strings.TrimRight(dnsserver.Target, "."))
}
ui.Info().Msgf("AD controller(s) detected as: %v", strings.Join(*servers, ", "))
} else {
return errors.New("AD controller auto-detection failed, use '--server' parameter")
}
options = LDAPOptions{
Servers: *servers,
Port: int16(*port),
AuthMode: authmode,
User: *user,
Password: *pass,
Domain: *domain,
AuthDomain: *authdomain,
TLSMode: tlsmode,
IgnoreCert: *ignoreCert,
Debug: *ldapdebug,
Channelbinding: *channelbinding,
}

if runtime.GOOS != "windows" && *user == "" && authmode != KerberosCache {
// Auto-detect user
*user = os.Getenv("USERNAME")
if *user != "" {
ui.Info().Msgf("Auto-detected username as %v", *user)
} else {
return errors.New("Username autodetection failed - please use '--username' parameter")
}
}
if *autodetect {
err := options.Autodetect()
if err != nil {
ui.Warn().Msgf("Problem doing auto-detection: %v", err)
}
}

// END OF AUTODETECTION

if len(*servers) == 0 {
if len(options.Servers) == 0 {
return errors.New("missing AD controller server name - please provide this on commandline")
}

Expand All @@ -162,27 +129,27 @@ func PreRun(cmd *cobra.Command, args []string) error {
return nil
}

if *user == "" {
if *pass != "" {
if options.User == "" {
if options.Password != "" {
return errors.New("You supplied a password, but not a username. Please provide a username or do not supply a password")
}

if runtime.GOOS != "windows" {
return errors.New("You need to supply a username and password for platforms other than Windows")
}
} else {
if *pass == "" {
if options.Password == "" {
fmt.Printf("Please enter password for %v: ", *user)
passwd, err := term.ReadPassword(int(syscall.Stdin))
fmt.Println()
if err == nil {
*pass = string(passwd)
options.Password = string(passwd)
}
}

if *pass == "!" {
if options.Password == "!" {
// A single ! indicates we want to use a blank password, so lets change it to that
*pass = ""
options.Password = ""
}

// if authmode == NTLM {
Expand Down Expand Up @@ -291,38 +258,13 @@ func Execute(cmd *cobra.Command, args []string) error {
ad.Disconnect()
} else {
// Active Directory dump directly from AD controller
options := LDAPOptions{
Domain: *domain,
Port: uint16(*port),
AuthMode: authmode,
User: *user,
Password: *pass,
AuthDomain: *authdomain,
TLSMode: tlsmode,
IgnoreCert: *ignoreCert,
Debug: *ldapdebug,
Channelbinding: *channelbinding,
}
var ad LDAPDumper

// Find usable DC from list of servers
var chosenserver string
for _, server := range *servers {
options.Server = server
ad = CreateDumper(options)
ad = CreateDumper(options)

err := ad.Connect()
if err == nil {
// Successfull connect
chosenserver = server
break
}

ui.Warn().Msgf("Problem connecting to DC %v: %v", server, err)
}
if chosenserver != "" {
ui.Info().Msgf("Successfull connect to DC %v", chosenserver)
} else {
err := ad.Connect()
if err != nil {
return errors.New("All DCs failed login attempts")
}

Expand Down
73 changes: 70 additions & 3 deletions modules/integrations/activedirectory/collect/ldap_common.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
package collect

import "github.com/lkarlslund/adalanche/modules/integrations/activedirectory"
import (
"net"
"os"
"runtime"
"strings"

"github.com/Showmax/go-fqdn"
"github.com/lkarlslund/adalanche/modules/integrations/activedirectory"
"github.com/lkarlslund/adalanche/modules/ui"
"github.com/pkg/errors"
)

//go:generate go run github.com/dmarkham/enumer -type=TLSmode,AuthMode,LDAPScope,LDAPError,LDAPOption -json -output ldap_enums.go

Expand Down Expand Up @@ -137,8 +147,8 @@ const (

type LDAPOptions struct {
Domain string `json:"domain"`
Server string `json:"server"`
Port uint16 `json:"port"`
Servers []string `json:"server"` // tries servers in this order
Port int16 `json:"port"`
User string `json:"user"`
Password string `json:"password"`
AuthDomain string `json:"authdomain"`
Expand All @@ -156,6 +166,63 @@ func NewLDAPOptions() LDAPOptions {
return LDAPOptions{}
}

func (ldo *LDAPOptions) Autodetect() error {
if ldo.Port == -1 {
if tlsmode == TLS {
ldo.Port = 636
} else {
ldo.Port = 389
}
}

if ldo.Domain == "" {
ui.Info().Msg("No domain supplied, auto-detecting")
ldo.Domain = strings.ToLower(os.Getenv("USERDNSDOMAIN"))
if ldo.Domain == "" {
// That didn't work, lets try something else
f, err := fqdn.FqdnHostname()
if err == nil && strings.Contains(f, ".") {
ui.Info().Msg("No USERDNSDOMAIN set - using machines FQDN as basis")
ldo.Domain = strings.ToLower(f[strings.Index(f, ".")+1:])
}
}
}

if len(ldo.Servers) == 0 {
if ldo.Domain == "" {
return errors.New("Server auto-detection failed, we don't know the domain")
}
ui.Info().Msgf("Trying to auto-detect servers on domain '%v'", ldo.Domain)

// Auto-detect server
cname, dnsservers, err := net.LookupSRV("", "", "_ldap._tcp.dc._msdcs."+ldo.Domain)
if err == nil && cname != "" && len(dnsservers) != 0 {
for _, dnsserver := range dnsservers {
ldo.Servers = append(ldo.Servers, strings.TrimRight(dnsserver.Target, "."))
}
ui.Info().Msgf("AD controller(s) detected as: %v", strings.Join(ldo.Servers, ", "))
} else {
return errors.New("AD controller auto-detection failed, use '--server' parameter")
}
}

if runtime.GOOS != "windows" && ldo.User == "" && ldo.AuthMode != KerberosCache {
// Auto-detect user
ldo.User = os.Getenv("USERNAME")
if ldo.User != "" {
ui.Info().Msgf("Auto-detected username as %v", ldo.User)
} else {
return errors.New("Username autodetection failed - please use '--username' parameter")
}
}

if ldo.AuthDomain == "" {
ldo.AuthDomain = ldo.Domain
}

return nil
}

type objectCallbackFunc func(ro *activedirectory.RawObject) error

type DumpOptions struct {
Expand Down
Loading

0 comments on commit cdcd40c

Please sign in to comment.