用 Go 写的轻量级 OpenLdap 弱密码检测工具
发布时间:2021-11-03 03:17:45  所属栏目:语言  来源:互联网 
            导读:Go连接LDAP服务通过go操作的ldap,这里使用到的是go-ldap[1]包,该包基本上实现了ldap v3的基本功能. 比如连接ldap服务、新增、删除、修改用户信息等,支持条件检索的ldap库中存储的数据信息。2 下载go get github.com/go-ldap/ldap/v3go get github.com/wxn
                
                
                
            | Go连接LDAP服务
	通过go操作的ldap,这里使用到的是go-ldap[1]包,该包基本上实现了ldap v3的基本功能. 比如连接ldap服务、新增、删除、修改用户信息等,支持条件检索的ldap库中存储的数据信息。
	 
	2 下载
	go get github.com/go-ldap/ldap/v3 
	go get github.com/wxnacy/wgo/arrays 
	使用go-ldap包,可以在gopkg.in/ldap.v3@v3.1.0#section-readme[2]查看说明文档
	 
	3 准备LDAP环境
	这里通过docker-compose运行一个临时的ldap实验环境,
	 
	version: "3" 
	services: 
	  ldap: 
	    image: osixia/openldap:latest 
	    container_name: openldap 
	    hostname: openldap 
	    restart: always 
	    environment: 
	      - "LDAP_ORGANISATION=devopsman" 
	      - "LDAP_DOMAIN=devopsman.cn" 
	      - "LDAP_BASE_DN=dc=devopsman,dc=cn" 
	      - "LDAP_ADMIN_PASSWORD=admin123" 
	    ports: 
	      - 389:389 
	      - 636:636 
	可以按需修改对应的环境变量信息.可以在hub.docker.com[3]找到指定版本的镜像信息. 现在创建一下openldap并且检查一下服务的是否正常:
	 
	 
	 
	4 GO-LDAP案例实践
	创建用户
	在pkg.go.dev文档中查看,有一个Add方法可以完成创建用户的操作,但是需要一个AddRequest参数,而NewAddRequest方法可以返回AddRequest,于是按照此思路梳理一下。
	 
	首先要建立与openldap之间的连接,验证账号是否正常,同时此账号要有创建的权限。
	 
	// LoginBind  connection ldap server and binding ldap server 
	func LoginBind(ldapUser, ldapPassword string) (*ldap.Conn, error) { 
	 l, err := ldap.DialURL(ldapURL) 
	 if err != nil { 
	  return nil, err 
	 } 
	 _, err = l.SimpleBind(&ldap.SimpleBindRequest{ 
	  Username: fmt.Sprintf("cn=%s,dc=devopsman,dc=cn", ldapUser), 
	  Password: ldapPassword, 
	 }) 
	 
	 if err != nil { 
	  fmt.Println("ldap password is error: ", ldap.LDAPResultInvalidCredentials) 
	  return nil, err 
	 } 
	 fmt.Println(ldapUser,"登录成功") 
	 return l, nil 
	} 
	其次,创建用户,需要准备用户的姓名、密码、sn、uid、gid等信息,可以创建一个struct结构
	 
	type User struct { 
	 username    string 
	 password    string 
	 telephone   string 
	 emailSuffix string 
	 snUsername  string 
	 uid         string 
	 gid         string 
	} 
	通过go-ldap包提供的NewAddRequest方法,可以返回新增请求
	 
	func (user *User) addUser(conn *ldap.Conn) error { 
	 ldaprow := ldap.NewAddRequest(fmt.Sprintf("cn=%s,dc=devopsman,dc=cn", user.username), nil) 
	 ldaprow.Attribute("userPassword", []string{user.password}) 
	 ldaprow.Attribute("homeDirectory", []string{fmt.Sprintf("/home/%s", user.username)}) 
	 ldaprow.Attribute("cn", []string{user.username}) 
	 ldaprow.Attribute("uid", []string{user.username}) 
	 ldaprow.Attribute("objectClass", []string{"shadowAccount", "posixAccount", "account"}) 
	 ldaprow.Attribute("uidNumber", []string{"2201"}) 
	 ldaprow.Attribute("gidNumber", []string{"2201"}) 
	 ldaprow.Attribute("loginShell", []string{"/bin/bash"}) 
	 
	 if err := conn.Add(ldaprow); err != nil { 
	  return err 
	 } 
	 return nil 
	} 
	最后,我们就可以通过实例化User这个对象,完成用户的创建了:
	 
	func main() { 
	 con, err := LoginBind("admin", "admin123") 
	 fmt.Println(con.IsClosing()) 
	 if err != nil { 
	  fmt.Println("V") 
	  fmt.Println(err) 
	 } 
	 var user User 
	 user.username="marionxue" 
	 user.password="admin123" 
	 user.snUsername="Marionxue" 
	 user.uid="1000" 
	 user.gid="1000" 
	 user.emailSuffix="@qq.com" 
	 
	 if err=user.addUser(con);err!=nil{ 
	  fmt.Println(err) 
	 } 
	 fmt.Println(user.username,"创建完成!") 
	} 
	最后运行就可以创建用户
	 
	... 
	/private/var/folders/jl/9zk5nj316rlg_0svp07w6btc0000gn/T/GoLand/___go_build_github_com_marionxue_go30_tools_go_openldap 
	admin登录成功 
	marionxue 创建完成! 
	遍历用户
	遍历用户依旧需要与openLDAP建立连接,因此我们复用LoginBind函数,创建一个获取账号的函数GetEmployees
	 
	func GetEmployees(con *ldap.Conn) ([]string, error) { 
	 var employees []string 
	 sql := ldap.NewSearchRequest("dc=devopsman,dc=cn", 
	  ldap.ScopeWholeSubtree, 
	  ldap.NeverDerefAliases, 
	  0, 
	  0, 
	  false, 
	  "(objectClass=*)", 
	  []string{"dn", "cn", "objectClass"}, 
	  nil) 
	 
	 cur, err := con.Search(sql) 
	 if err != nil { 
	  return nil, err 
	 } 
	 
	 if len(cur.Entries) > 0 { 
	  for _, item := range cur.Entries { 
	   cn := item.GetAttributeValues("cn") 
	   for _, iCn := range cn { 
	    employees = append(employees, strings.Split(iCn, "[")[0]) 
	   } 
	  } 
	  return employees, nil 
	 } 
	 return nil, nil 
	} 
	我们通过NewSearchRequest检索BaseDB为dc=devopsman,dc=cn下的账号信息,最后将用户名cn打印出来
	 
	func main() { 
	 con, err := LoginBind("admin", "admin123") 
	 if err != nil { 
	  fmt.Println("V") 
	  fmt.Println(err) 
	 } 
	 employees, err := GetEmployees(con) 
	 if err != nil { 
	  fmt.Println(err) 
	 } 
	 for _, employe := range employees { 
	  fmt.Println(employe) 
	 
	 } 
	} 
	结果就是我们前面创建的一个用户
	 
	marionxue 
	删除账号
	同样的思路,然后创建一个删除方法delUser
	 
	// delUser 删除用户 
	func (user *User) delUser(conn *ldap.Conn) error{ 
	 ldaprow := ldap.NewDelRequest(fmt.Sprintf("cn=%s,dc=devopsman,dc=cn",user.username),nil) 
	 
	 if err:= conn.Del(ldaprow);err!=nil{ 
	  return err 
	 } 
	 return nil 
	} 
	然后在main函数中调用
	 
	func main() { 
	 con, err := LoginBind("admin", "admin123") 
	 if err != nil { 
	  fmt.Println("V") 
	  fmt.Println(err) 
	 } 
	 employees, err := GetEmployees(con) 
	 if err != nil { 
	  fmt.Println(err) 
	 } 
	 var user User 
	 user.username="marionxue" 
	 
	 if err:=user.delUser(con);err!=nil{ 
	  fmt.Println("用户删除失败") 
	 } 
	 fmt.Println(user.username,"用户删除成功!") 
	} 
	运行结果:
	 
	admin登录成功 
	marionxue 用户删除成功! 
	弱密码检查
	默认情况下,在ldap中创建用户,并没有密码复杂度的约束,因此对已存在ldap服务中使用弱密码的账号有什么好办法能获取出来吗?ldap的账号一旦创建,就看不到密码了,如果用弱密码字典模拟登录的话,是否可行呢?
	 
	创建一个检查密码的函数CheckPassword,通过逐行读取弱密码词典的数据进行的模拟登录,从而找到ldap中使用弱密码的账号:
	 
	func CheckPassword(employe string) { 
	 // 遍历的弱密码字典 
	 f, err := os.Open("~/dict.txt") 
	 if err != nil { 
	  fmt.Println("reading dict.txt error: ", err) 
	 } 
	 defer f.Close() 
	 scanner := bufio.NewScanner(f) 
	 for scanner.Scan() { 
	  weakpassword := scanner.Text() 
	  _, err := LoginBind(employe, weakpassword) 
	  if err == nil { 
	   fmt.Println(employe + " 使用的密码为: " + weakpassword) 
	  } 
	 } 
	 if err := scanner.Err(); err != nil { 
	  fmt.Println(err) 
	 } 
	 fmt.Println(employe + " check have aleardy finished. and the password is stronger well.") 
	 
	} 
	结合前面说的遍历账号,拿到所有的账号的信息,然后模拟登录,如果命中了弱密码字典中的密码,就打印出来
	 
	func main() { 
	 con, err := LoginBind("admin", "admin123") 
	 if err != nil { 
	  fmt.Println("V") 
	  fmt.Println(err) 
	 } 
	 employees, err := GetEmployees(con) 
	 if err != nil { 
	  fmt.Println(err) 
	 } 
	 Whitelist := []string{"zhangsan","lisi"} 
	 for _, employe := range employees { 
	  fmt.Println("Starting check: ", employe) 
	  index := arrays.ContainsString(Whitelist, employe) 
	  if index == -1 { 
	   CheckPassword(employe) 
	  } else { 
	   fmt.Println(employe + " in whitelist. skiping...") 
	  } 
	  fmt.Println(employe) 
	 } 
	} 
	但是这样实际就是在攻击自己的服务,这里就会产生两个问题:
	 
	用户越多,弱密码字典里面的密码越多,检查的次数也就越多,耗时也就越长
	 
	每次模拟登录,实际上就会创建一个连接,虽然连接认证失败,但是也无疑加重服务器连接创建和销毁的资源损耗,你有调优思路没?   (编辑:锡盟站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! | 
站长推荐
            
        
