From dac8a913fecb5b295a7f6d0120a77899c5d5803f Mon Sep 17 00:00:00 2001 From: 2867a0 <2867a0@gmail.com> Date: Mon, 7 Aug 2023 21:02:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=B0=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=9A=E6=94=AF=E6=8C=81=E4=BB=A5=E5=BD=93=E5=89=8D=E7=94=A8?= =?UTF-8?q?=E6=88=B7token=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- cli/cli.go | 2 + cli/cli_win.go | 68 ++++++++++++++++++++++++ cli/global/globals.go | 2 + cli/global/globals_win.go | 108 ++++++++++++++++++++++++++++++++++++++ conn/conn.go | 2 + conn/conn_win.go | 94 +++++++++++++++++++++++++++++++++ go.mod | 5 +- 8 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 cli/cli_win.go create mode 100644 cli/global/globals_win.go create mode 100644 conn/conn_win.go diff --git a/README.md b/README.md index 4380d1f..e0d6a5d 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ golang 实现的 Ldap 小工具 **功能类** - [x] SSL连接 - [x] 导出查询结果 -- [ ] (调研)以当前用户Token查询 +- [x] (调研)以当前用户Token查询 - [x] 支持hash登陆 **搜索模块类** diff --git a/cli/cli.go b/cli/cli.go index d1abcb7..60dc81e 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -1,3 +1,5 @@ +//go:build !windows + package cli import ( diff --git a/cli/cli_win.go b/cli/cli_win.go new file mode 100644 index 0000000..187883f --- /dev/null +++ b/cli/cli_win.go @@ -0,0 +1,68 @@ +//go:build windows + +package cli + +import ( + "github.com/spf13/cobra" + "goLdapTools/cli/global" + "goLdapTools/cli/modify" + "goLdapTools/cli/search" + "goLdapTools/conn" + "goLdapTools/log" + "os" +) + +func init() { + //global argument + rootCmd.PersistentFlags().StringP(global.DomainNameStr, "d", "", "domain name (example: test.lab)") + rootCmd.PersistentFlags().StringP(global.UserStr, "u", "", "username") + rootCmd.PersistentFlags().StringP(global.PassStr, "p", "", "password") + rootCmd.PersistentFlags().StringP(global.HashStr, "", "", "hash") + rootCmd.PersistentFlags().StringP(global.GssApiStr, "t", "", "login with current user token(example: --gssapi dc.test.lab)") + rootCmd.PersistentFlags().StringP(global.BaseDnStr, "b", "", "Specify DN (ou=xx,dc=xx,dc=xx)") + rootCmd.PersistentFlags().BoolP(global.SslStr, "s", false, "Use ssl to connect to ldap. default false") + rootCmd.PersistentFlags().StringP(global.ExportStr, "o", "", "save result to file.") + + //search mode + rootCmd.AddCommand(search.SearchCmd) + + //modify mode + rootCmd.AddCommand(modify.AddCmd) +} + +var rootCmd = &cobra.Command{ + Use: "goLdapTool", + Short: "golang ldap tool", + Long: `An Ldap operation tool written in golang, +with functions including searching and modifying Ldap entry attributes`, + + Run: func(cmd *cobra.Command, args []string) { + config, err := global.ParseGlobalCommand(cmd) + if err != nil { + log.PrintErrorf("Parse global command error: %s", err.Error()) + os.Exit(1) + } + + log.PrintDebugf("Connect config:\n"+ + " server: %s\n"+ + " username: %s\n"+ + " password: %s\n"+ + " dn: %s", config.DomainName, config.UserName, config.Password, config.BaseDN) + + // 不使用搜索模块,仅登陆 + ldapConnector, err := conn.LdapConnect(config) + if err != nil { + log.PrintErrorf("error: %s", err.Error()) + os.Exit(1) + } + + log.PrintSuccessf("Connect %s successes", ldapConnector.Config.DomainName) + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + log.PrintError(err) + os.Exit(1) + } +} diff --git a/cli/global/globals.go b/cli/global/globals.go index e1c4c19..1dab624 100644 --- a/cli/global/globals.go +++ b/cli/global/globals.go @@ -1,3 +1,5 @@ +//go:build !windows + package global import ( diff --git a/cli/global/globals_win.go b/cli/global/globals_win.go new file mode 100644 index 0000000..b66b8f8 --- /dev/null +++ b/cli/global/globals_win.go @@ -0,0 +1,108 @@ +//go:build windows + +package global + +import ( + "errors" + "fmt" + "github.com/spf13/cobra" + "goLdapTools/log" + "strings" +) + +const ( + DomainNameStr = "domain-name" + UserStr = "username" + PassStr = "password" + HashStr = "hash" + GssApiStr = "gssapi" + BaseDnStr = "base-dn" + SslStr = "ssl" + ExportStr = "output" +) + +type GlobalCommand struct { + DomainName string + UserName string + Password string + PassHash string + GssApiLogin string + BaseDN string + SSLConn bool + Export string +} + +func ParseGlobalCommand(cmd *cobra.Command) (config *GlobalCommand, err error) { + domainName, err := cmd.Flags().GetString(DomainNameStr) + if err != nil { + log.PrintDebugf("Failed to parse --domainName-- flag %s", err) + return nil, err + } + if domainName == "" { + return nil, errors.New("domain name is not specified") + } + + u, err := cmd.Flags().GetString(UserStr) + if err != nil { + log.PrintDebugf("Failed to parse --username-- flag %s", err) + return nil, err + } + + password, err := cmd.Flags().GetString(PassStr) + if err != nil { + log.PrintDebugf("Failed to parse --password-- flag %s", err) + return nil, err + } + + passHash, err := cmd.Flags().GetString(HashStr) + if err != nil { + log.PrintDebugf("Failed to parse --hash-- flag %s", err) + return nil, err + } + + gssapi, err := cmd.Flags().GetString(GssApiStr) + if err != nil { + log.PrintDebugf("Failed to parse --gssapi-- flag %s", err) + return nil, err + } + + domainNameArr := strings.Split(domainName, ".") + baseDN, err := cmd.Flags().GetString(BaseDnStr) + if err != nil { + log.PrintDebugf("Failed to parse --base dn-- flag %s", err) + return nil, err + } + if baseDN == "" { + baseDN = fmt.Sprintf("dc=%s", strings.Join(domainNameArr, ",dc=")) + } + + ssl, err := cmd.Flags().GetBool(SslStr) + if err != nil { + log.PrintErrorf("Failed to parse --ssl-- flag %s", err) + return nil, err + } + + export, err := cmd.Flags().GetString(ExportStr) + if err != nil { + log.PrintErrorf("Failed to parse --export-- flag %s", err) + return nil, err + } + + log.SaveResultStr = export + + var userName = u + if !strings.Contains(u, "@") && !strings.Contains(u, "\\") { + userName = fmt.Sprintf("%s@%s", u, domainName) + } + + return &GlobalCommand{ + DomainName: domainName, + UserName: userName, + Password: password, + PassHash: passHash, + GssApiLogin: gssapi, + BaseDN: baseDN, + SSLConn: ssl, + Export: export, + }, nil +} diff --git a/conn/conn.go b/conn/conn.go index 9af4223..7ebdf44 100644 --- a/conn/conn.go +++ b/conn/conn.go @@ -1,3 +1,5 @@ +//go:build !windows + package conn import ( diff --git a/conn/conn_win.go b/conn/conn_win.go new file mode 100644 index 0000000..3bc85ec --- /dev/null +++ b/conn/conn_win.go @@ -0,0 +1,94 @@ +//go:build windows + +package conn + +import ( + "crypto/tls" + "fmt" + "github.com/go-ldap/ldap/v3" + "github.com/go-ldap/ldap/v3/gssapi" + "goLdapTools/cli/global" + "goLdapTools/log" + "strings" +) + +type Connector struct { + Conn *ldap.Conn + Config *global.GlobalCommand +} + +func LdapConnect(globalCommand *global.GlobalCommand) (*Connector, error) { + var conn *ldap.Conn + var sspiConn *gssapi.SSPIClient + var err error + + if globalCommand.GssApiLogin != "" { + sspiConn, err = gssapi.NewSSPIClient() + if err != nil { + return nil, err + } + } + + //非加密连接 + if !globalCommand.SSLConn { + log.PrintInfof("Trying to connecting server Ldap://%s:389", globalCommand.DomainName) + conn, err = ldap.Dial("tcp", fmt.Sprintf("%s:389", globalCommand.DomainName)) + if err != nil { + return nil, err + } + } else { + // SSL连接 + log.PrintInfof("Trying to connecting server Ldaps://%s:636", globalCommand.DomainName) + conn, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:636", globalCommand.DomainName), + &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return nil, err + } + } + + if globalCommand.PassHash == "" && globalCommand.Password != "" { + log.PrintInfof("Trying to binding server with password") + log.PrintInfof("Domain Name: %s", globalCommand.DomainName) + log.PrintInfof("username: %s", globalCommand.UserName) + log.PrintInfof("password: %s", globalCommand.Password) + + err = conn.Bind(globalCommand.UserName, globalCommand.Password) + if err != nil { + return nil, err + } + } else if globalCommand.UserName != "" && globalCommand.GssApiLogin == "" { + req := &ldap.NTLMBindRequest{ + Domain: globalCommand.DomainName, + Username: strings.Split(globalCommand.UserName, "@")[0], + Hash: globalCommand.PassHash, + AllowEmptyPassword: true, + Controls: nil, + } + + log.PrintInfof("Trying to binding server with hash") + log.PrintInfof("username: %s", strings.Split(globalCommand.UserName, "@")[0]) + log.PrintInfof("pass-hash: %s", globalCommand.PassHash) + + _, err = conn.NTLMChallengeBind(req) + if err != nil { + return nil, err + } + } else { + log.PrintInfo("Trying to binging server with current token") + + err = conn.GSSAPIBind(sspiConn, fmt.Sprintf("ldap/%s", globalCommand.GssApiLogin), "") + if err != nil { + return nil, err + } + } + + log.PrintSuccess("Binding success") + + return &Connector{Conn: conn, Config: globalCommand}, nil +} + +func (conn *Connector) DoModify(dn string, control []ldap.Control, attrType string, attrVals []string) error { + modifyReq := ldap.NewModifyRequest(dn, control) + modifyReq.Replace(attrType, attrVals) + return conn.Conn.Modify(modifyReq) +} diff --git a/go.mod b/go.mod index 0e58897..bbf37ae 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,17 @@ module goLdapTools go 1.20 require ( + github.com/go-asn1-ber/asn1-ber v1.5.4 github.com/go-ldap/ldap/v3 v3.4.5 github.com/gookit/color v1.5.3 github.com/gookit/slog v0.5.2 github.com/spf13/cobra v1.7.0 + golang.org/x/text v0.10.0 ) require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect + github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect github.com/gookit/goutil v0.6.10 // indirect github.com/gookit/gsr v0.0.8 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -21,5 +23,4 @@ require ( golang.org/x/crypto v0.7.0 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.9.0 // indirect - golang.org/x/text v0.10.0 // indirect )