From fdc43a7d664ec6ebd5b81a8c144f736fce44a00e Mon Sep 17 00:00:00 2001 From: Macmod Date: Wed, 20 Nov 2024 01:47:24 -0300 Subject: [PATCH] Bugfixes & Improved connection config form. --- README.md | 4 +- godap.go | 21 +-- pkg/ldaputils/actions.go | 7 +- tui/main.go | 273 +++++++++++++++++++++++++++++++++------ 4 files changed, 242 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 6cc3f4a..a669288 100644 --- a/README.md +++ b/README.md @@ -74,12 +74,12 @@ $ KRB5CCNAME=ticket.ccache godap -k -d -t ldap/ -d --crt --key -I +$ godap --crt --key -I ``` PKCS#12: ```bash -$ godap -d --pfx -I +$ godap --pfx -I ``` Note. This method will either pass the certificate directly when connecting with LDAPS (`-S`), or upgrade the unencrypted LDAP connection implicitly with StartTLS, therefore you must provide `-I` if you want to use it and your server certificate is not trusted by your client. diff --git a/godap.go b/godap.go index 0345563..c920261 100644 --- a/godap.go +++ b/godap.go @@ -2,9 +2,6 @@ package main import ( "fmt" - "log" - "os" - "strings" "github.com/Macmod/godap/v2/tui" "github.com/spf13/cobra" @@ -26,22 +23,6 @@ func main() { } } - if tui.LdapPasswordFile != "" { - pw, err := os.ReadFile(tui.LdapPasswordFile) - if err != nil { - log.Fatal(err) - } - tui.LdapPassword = strings.TrimSpace(string(pw)) - } - - if tui.NtlmHashFile != "" { - hash, err := os.ReadFile(tui.NtlmHashFile) - if err != nil { - log.Fatal(err) - } - tui.NtlmHash = strings.TrimSpace(string(hash)) - } - tui.SetupApp() }, } @@ -51,7 +32,7 @@ func main() { rootCmd.Flags().StringVarP(&tui.LdapPassword, "password", "p", "", "LDAP password") rootCmd.Flags().StringVarP(&tui.LdapPasswordFile, "passfile", "", "", "Path to a file containing the LDAP password") rootCmd.Flags().StringVarP(&tui.DomainName, "domain", "d", "", "Domain for NTLM / Kerberos authentication") - rootCmd.Flags().StringVarP(&tui.NtlmHash, "hashes", "H", "", "NTLM hash") + rootCmd.Flags().StringVarP(&tui.NtlmHash, "hash", "H", "", "NTLM hash") rootCmd.Flags().BoolVarP(&tui.Kerberos, "kerberos", "k", false, "Use Kerberos ticket for authentication (CCACHE specified via KRB5CCNAME environment variable)") rootCmd.Flags().StringVarP(&tui.TargetSpn, "spn", "t", "", "Target SPN to use for Kerberos bind (usually ldap/dchostname)") rootCmd.Flags().StringVarP(&tui.NtlmHashFile, "hashfile", "", "", "Path to a file containing the NTLM hash") diff --git a/pkg/ldaputils/actions.go b/pkg/ldaputils/actions.go index ad3ff81..aedd180 100644 --- a/pkg/ldaputils/actions.go +++ b/pkg/ldaputils/actions.go @@ -64,9 +64,10 @@ func NewLDAPConn(ldapServer string, ldapPort int, ldaps bool, tlsConfig *tls.Con } return &LDAPConn{ - Conn: conn, - PagingSize: pagingSize, - RootDN: rootDN, + Conn: conn, + PagingSize: pagingSize, + RootDN: rootDN, + DefaultRootDN: rootDN, }, nil } diff --git a/tui/main.go b/tui/main.go index df67f3f..2f0fa35 100644 --- a/tui/main.go +++ b/tui/main.go @@ -18,7 +18,7 @@ import ( "software.sslmate.com/src/go-pkcs12" ) -var GodapVer = "Godap v2.8.1" +var GodapVer = "Godap v2.9.0" var ( LdapServer string @@ -36,6 +36,7 @@ var ( CertFile string KeyFile string PfxFile string + CCachePath string Kerberos bool Emojis bool @@ -53,6 +54,7 @@ var ( SearchFilter string RootDN string ShowHeader bool + AuthType int page int ) @@ -177,47 +179,199 @@ func reconnectLdap() { }) } +func getCurrentAuthType() int { + if PfxFile != "" { + return 6 // Certificate (PKCS#12) + } + + if CertFile != "" && KeyFile != "" { + return 5 // Certificate (PEM) + } + + if Kerberos { + return 4 // Kerberos + } + + if NtlmHashFile != "" { + return 3 // NTLM (file) + } + + if NtlmHash != "" { + return 2 // NTLM + } + + if LdapPasswordFile != "" { + return 1 // Password (file) + } + + return 0 // Password (default) +} + func openConfigForm() { - credsForm := NewXForm() - credsForm. + currentFocus := app.GetFocus() + + // Main config form with connection settings + configForm := NewXForm() + configForm. AddInputField("Server", LdapServer, 20, nil, nil). AddInputField("Port", strconv.Itoa(LdapPort), 20, nil, nil). - AddInputField("Username", LdapUsername, 20, nil, nil). - AddPasswordField("Password", LdapPassword, 20, '*', nil). AddCheckbox("LDAPS", Ldaps, nil). AddCheckbox("IgnoreCert", Insecure, nil). AddInputField("SOCKSProxy", SocksServer, 20, nil, nil). + AddInputField("Domain", DomainName, 20, nil, nil). + AddDropDown("Auth Type", []string{ + "Password", + "Password (file)", + "NTLM", + "NTLM (file)", + "Kerberos", + "Certificate (PEM)", + "Certificate (PKCS#12)", + }, 0, nil) + + // Credentials forms for each auth type + passwordForm := NewXForm() + passwordForm. + AddInputField("Username", LdapUsername, 20, nil, nil). + AddPasswordField("Password", LdapPassword, 20, '*', nil) + + passwordFileForm := NewXForm() + passwordFileForm. + AddInputField("Username", LdapUsername, 20, nil, nil). + AddInputField("Password File", LdapPasswordFile, 20, nil, nil) + + ntlmForm := NewXForm() + ntlmForm. + AddInputField("Username", LdapUsername, 20, nil, nil). + AddPasswordField("NTLM Hash", NtlmHash, 20, '*', nil) + + ntlmFileForm := NewXForm() + ntlmFileForm. + AddInputField("Username", LdapUsername, 20, nil, nil). + AddInputField("Hash File", NtlmHashFile, 20, nil, nil) + + kerberosForm := NewXForm() + kerberosForm. + AddInputField("CCACHE Path", CCachePath, 20, nil, nil). + AddInputField("Target SPN", TargetSpn, 20, nil, nil). + AddInputField("KDC Address", KdcHost, 20, nil, nil) + + pfxForm := NewXForm() + pfxForm. + AddInputField("PFX Path", PfxFile, 20, nil, nil) + + pemForm := NewXForm() + pemForm. + AddInputField("Certificate Path", CertFile, 20, nil, nil). + AddInputField("Key Path", KeyFile, 20, nil, nil) + + // Create pages to switch between auth forms + authPages := tview.NewPages() + authPages. + AddPage("password", passwordForm, true, true). + AddPage("passwordfile", passwordFileForm, true, false). + AddPage("ntlm", ntlmForm, true, false). + AddPage("ntlmfile", ntlmFileForm, true, false). + AddPage("kerberos", kerberosForm, true, false). + AddPage("pem", pemForm, true, false). + AddPage("pfx", pfxForm, true, false) + + // Handle auth type selection + configForm.GetFormItemByLabel("Auth Type").(*tview.DropDown). + SetSelectedFunc(func(text string, index int) { + switch index { + case 0: + authPages.SwitchToPage("password") + case 1: + authPages.SwitchToPage("passwordfile") + case 2: + authPages.SwitchToPage("ntlm") + case 3: + authPages.SwitchToPage("ntlmfile") + case 4: + authPages.SwitchToPage("kerberos") + case 5: + authPages.SwitchToPage("pem") + case 6: + authPages.SwitchToPage("pfx") + } + }) + + configForm.GetFormItemByLabel("Auth Type").(*tview.DropDown). + SetCurrentOption(AuthType) + + configForm. AddButton("Go Back", func() { - app.SetRoot(appPanel, false).SetFocus(treePanel) + app.SetRoot(appPanel, true).SetFocus(currentFocus) }). AddButton("Update", func() { - LdapServer = credsForm.GetFormItemByLabel("Server").(*tview.InputField).GetText() - LdapPort, _ = strconv.Atoi(credsForm.GetFormItemByLabel("Port").(*tview.InputField).GetText()) - LdapUsername = credsForm.GetFormItemByLabel("Username").(*tview.InputField).GetText() - LdapPassword = credsForm.GetFormItemByLabel("Password").(*tview.InputField).GetText() - - Ldaps = credsForm.GetFormItemByLabel("LDAPS").(*tview.Checkbox).IsChecked() - Insecure = credsForm.GetFormItemByLabel("IgnoreCert").(*tview.Checkbox).IsChecked() - - SocksServer = credsForm.GetFormItemByLabel("SOCKSProxy").(*tview.InputField).GetText() + // Update connection settings + LdapServer = configForm.GetFormItemByLabel("Server").(*tview.InputField).GetText() + LdapPort, _ = strconv.Atoi(configForm.GetFormItemByLabel("Port").(*tview.InputField).GetText()) + Ldaps = configForm.GetFormItemByLabel("LDAPS").(*tview.Checkbox).IsChecked() + Insecure = configForm.GetFormItemByLabel("IgnoreCert").(*tview.Checkbox).IsChecked() + SocksServer = configForm.GetFormItemByLabel("SOCKSProxy").(*tview.InputField).GetText() + DomainName = configForm.GetFormItemByLabel("Domain").(*tview.InputField).GetText() + + // Update auth settings based on selected type + authTypeField, _ := configForm.GetFormItemByLabel("Auth Type").(*tview.DropDown).GetCurrentOption() + switch authTypeField { + case 0: // Password + LdapUsername = passwordForm.GetFormItemByLabel("Username").(*tview.InputField).GetText() + LdapPassword = passwordForm.GetFormItemByLabel("Password").(*tview.InputField).GetText() + case 1: // Password file + LdapUsername = passwordFileForm.GetFormItemByLabel("Username").(*tview.InputField).GetText() + LdapPasswordFile = passwordFileForm.GetFormItemByLabel("Password File").(*tview.InputField).GetText() + case 2: // NTLM + LdapUsername = ntlmForm.GetFormItemByLabel("Username").(*tview.InputField).GetText() + NtlmHash = ntlmForm.GetFormItemByLabel("NTLM Hash").(*tview.InputField).GetText() + case 3: // NTLM file + LdapUsername = ntlmFileForm.GetFormItemByLabel("Username").(*tview.InputField).GetText() + NtlmHashFile = ntlmFileForm.GetFormItemByLabel("Hash File").(*tview.InputField).GetText() + case 4: // Kerberos + CCachePath = kerberosForm.GetFormItemByLabel("CCACHE Path").(*tview.InputField).GetText() + TargetSpn = kerberosForm.GetFormItemByLabel("Target SPN").(*tview.InputField).GetText() + KdcHost = kerberosForm.GetFormItemByLabel("KDC Address").(*tview.InputField).GetText() + case 5: // PEM + CertFile = pemForm.GetFormItemByLabel("Certificate Path").(*tview.InputField).GetText() + KeyFile = pemForm.GetFormItemByLabel("Key Path").(*tview.InputField).GetText() + case 6: // PFX + PfxFile = pfxForm.GetFormItemByLabel("PFX Path").(*tview.InputField).GetText() + } - app.SetRoot(appPanel, false).SetFocus(treePanel) + AuthType = authTypeField + app.SetRoot(appPanel, true).SetFocus(currentFocus) reconnectLdap() }) - credsForm.SetTitle("Connection Config").SetBorder(true) + // Create configPanel container for both forms + configPanel := tview.NewFlex(). + AddItem(configForm, 0, 1, true). + AddItem(authPages, 0, 1, false) + + configPanel.SetBorder(true).SetTitle("Connection Configuration") + //assignFormTheme(credsForm) - credsForm.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + configPanel.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyEscape { - app.SetRoot(appPanel, true).SetFocus(appPanel) + app.SetRoot(appPanel, true).SetFocus(currentFocus) + return nil + } + + if event.Key() == tcell.KeyTab { + if app.GetFocus() == configForm { + app.SetFocus(authPages) + } else { + app.SetFocus(configForm) + } return nil } return event }) - app.SetRoot(credsForm, true).SetFocus(credsForm) + app.SetRoot(configPanel, true).SetFocus(configPanel) } func appPanelKeyHandler(event *tcell.EventKey) *tcell.EventKey { @@ -267,24 +421,48 @@ func setupLDAPConn() error { tlsConfig = insecureTlsConfig } - // If a certificate and key pair is provided, store it - // in the TLS config to be used for the connection - if CertFile != "" && KeyFile != "" { - cert, err := tls.LoadX509KeyPair(CertFile, KeyFile) + var ( + currentLdapUsername string + currentLdapPassword string + currentNtlmHash string + ) + + // Auth stuffs + if AuthType == 1 { + pw, err := os.ReadFile(LdapPasswordFile) if err != nil { - log.Fatalf("Error loading certificate / key: %v", err) + app.Stop() + log.Fatal(err) } + currentLdapPassword = strings.TrimSpace(string(pw)) + } else { + currentLdapPassword = LdapPassword + } - tlsConfig.Certificates = []tls.Certificate{cert} - } else if PfxFile != "" { + if AuthType == 3 { + hash, err := os.ReadFile(NtlmHashFile) + if err != nil { + app.Stop() + log.Fatal(err) + } + currentNtlmHash = strings.TrimSpace(string(hash)) + } else { + currentNtlmHash = NtlmHash + } + + // If a certificate and key pair is provided, store it + // in the TLS config to be used for the connection + if AuthType == 6 { pfxData, err := os.ReadFile(PfxFile) if err != nil { + app.Stop() log.Fatalf("Error reading PFX file: %v", err) } // Empty password for now - can be made configurable in the future privateKey, cert, err := pkcs12.Decode(pfxData, "") if err != nil { + app.Stop() log.Fatalf("Error decoding PFX: %v", err) } @@ -295,6 +473,14 @@ func setupLDAPConn() error { } tlsConfig.Certificates = []tls.Certificate{tlsCert} + } else if AuthType == 5 { + cert, err := tls.LoadX509KeyPair(CertFile, KeyFile) + if err != nil { + app.Stop() + log.Fatalf("Error loading certificate / key: %v", err) + } + + tlsConfig.Certificates = []tls.Certificate{cert} } var proxyConn net.Conn = nil @@ -304,8 +490,8 @@ func setupLDAPConn() error { proxyDial := socks.Dial(SocksServer) proxyConn, err = proxyDial("tcp", fmt.Sprintf("%s:%s", LdapServer, strconv.Itoa(LdapPort))) if err != nil { - updateLog(fmt.Sprint(err), "red") - return err + app.Stop() + log.Fatal(fmt.Sprint(err)) } } @@ -324,25 +510,30 @@ func setupLDAPConn() error { isSecure := Ldaps var bindType string - if tlsConfig.Certificates != nil { + if AuthType == 5 || AuthType == 6 { if !Ldaps { // If the connection was not using LDAPS, upgrade it with StartTLS // and then perform an ExternalBind err = lc.UpgradeToTLS(tlsConfig) if err != nil { + app.Stop() log.Fatal(err) } err = lc.ExternalBind() if err != nil { + app.Stop() log.Fatal(err) } } isSecure = true bindType = "LDAP+ClientCertificate" - } else if Kerberos { - ccachePath := os.Getenv("KRB5CCNAME") + } else if AuthType == 4 { + if _, err := os.Stat(CCachePath); err != nil { + app.Stop() + log.Fatal(err) + } var KdcAddr string if KdcHost != "" { @@ -351,17 +542,18 @@ func setupLDAPConn() error { KdcAddr = LdapServer } - err = lc.KerbBindWithCCache(ccachePath, KdcAddr, DomainName, TargetSpn, "aes") + err = lc.KerbBindWithCCache(CCachePath, KdcAddr, DomainName, TargetSpn, "aes") bindType = "Kerberos" - } else if NtlmHash != "" { - err = lc.NTLMBindWithHash(DomainName, LdapUsername, NtlmHash) + } else if AuthType == 2 || AuthType == 3 { + err = lc.NTLMBindWithHash(DomainName, LdapUsername, currentNtlmHash) bindType = "NTLM" } else { - if !strings.Contains(LdapUsername, "@") && DomainName != "" { - LdapUsername += "@" + DomainName + currentLdapUsername = LdapUsername + if !strings.Contains(currentLdapUsername, "@") && DomainName != "" { + currentLdapUsername += "@" + DomainName } - err = lc.LDAPBind(LdapUsername, LdapPassword) + err = lc.LDAPBind(currentLdapUsername, currentLdapPassword) bindType = "LDAP" } @@ -453,6 +645,11 @@ func SetupApp() { // Time format setup TimeFormat = setupTimeFormat(TimeFormat) + // CCache path setup + CCachePath = os.Getenv("KRB5CCNAME") + + AuthType = getCurrentAuthType() + err := setupLDAPConn() if err != nil { log.Fatal(err)