From 907b92863e6aa0976f4037873265da7366a4ccd3 Mon Sep 17 00:00:00 2001 From: ZacharyZcR <2903735704@qq.com> Date: Sat, 28 Dec 2024 05:43:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=9F=9F=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E6=89=AB=E6=8F=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Plugins/DCInfo.go | 830 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 830 insertions(+) create mode 100644 Plugins/DCInfo.go diff --git a/Plugins/DCInfo.go b/Plugins/DCInfo.go new file mode 100644 index 0000000..62222b2 --- /dev/null +++ b/Plugins/DCInfo.go @@ -0,0 +1,830 @@ +package Plugins + +import ( + "fmt" + "github.com/go-ldap/ldap/v3/gssapi" + "github.com/shadow1ng/fscan/Common" + "log" + "os/exec" + "strconv" + "strings" + + "github.com/go-ldap/ldap/v3" +) + +type DomainInfo struct { + conn *ldap.Conn + baseDN string +} + +func (d *DomainInfo) Close() { + if d.conn != nil { + d.conn.Close() + } +} + +func (d *DomainInfo) GetCAComputers() ([]string, error) { + // 在Configuration容器中查找CA服务器 + searchRequest := ldap.NewSearchRequest( + "CN=Configuration,"+d.baseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + "(&(objectCategory=pKIEnrollmentService))", // CA服务器的查询条件 + []string{"cn", "dNSHostName"}, + nil, + ) + + sr, err := d.conn.SearchWithPaging(searchRequest, 10000) + if err != nil { + return nil, err + } + + var caComputers []string + for _, entry := range sr.Entries { + cn := entry.GetAttributeValue("cn") + if cn != "" { + caComputers = append(caComputers, cn) + } + } + return caComputers, nil +} + +func (d *DomainInfo) GetExchangeServers() ([]string, error) { + searchRequest := ldap.NewSearchRequest( + d.baseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + "(&(objectCategory=group)(cn=Exchange Servers))", + []string{"member"}, + nil, + ) + + sr, err := d.conn.SearchWithPaging(searchRequest, 10000) + if err != nil { + return nil, err + } + + var exchangeServers []string + for _, entry := range sr.Entries { + for _, member := range entry.GetAttributeValues("member") { + if member != "" { + exchangeServers = append(exchangeServers, member) + } + } + } + + // 移除第一个条目(如果存在) + if len(exchangeServers) > 1 { + exchangeServers = exchangeServers[1:] + } + + return exchangeServers, nil +} + +func (d *DomainInfo) GetMsSqlServers() ([]string, error) { + searchRequest := ldap.NewSearchRequest( + d.baseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + "(&(objectClass=computer)(servicePrincipalName=MSSQLSvc*))", + []string{"name"}, + nil, + ) + + sr, err := d.conn.SearchWithPaging(searchRequest, 10000) + if err != nil { + return nil, err + } + + var sqlServers []string + for _, entry := range sr.Entries { + name := entry.GetAttributeValue("name") + if name != "" { + sqlServers = append(sqlServers, name) + } + } + + return sqlServers, nil +} + +func (d *DomainInfo) GetSpecialComputers() (map[string][]string, error) { + results := make(map[string][]string) + + // 获取SQL Server + sqlServers, err := d.GetMsSqlServers() + if err == nil && len(sqlServers) > 0 { + results["SQL服务器"] = sqlServers + } + + // 获取CA服务器 + caComputers, err := d.GetCAComputers() + if err == nil && len(caComputers) > 0 { + results["CA服务器"] = caComputers + } + + // 获取域控制器 + dcQuery := ldap.NewSearchRequest( + d.baseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + "(&(objectClass=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))", + []string{"cn"}, + nil, + ) + + if sr, err := d.conn.SearchWithPaging(dcQuery, 10000); err == nil { + var dcs []string + for _, entry := range sr.Entries { + name := entry.GetAttributeValue("cn") + if name != "" { + dcs = append(dcs, name) + } + } + if len(dcs) > 0 { + results["域控制器"] = dcs + } + } + + // 获取Exchange服务器 + exchangeServers, err := d.GetExchangeServers() + if err == nil && len(exchangeServers) > 0 { + results["Exchange服务器"] = exchangeServers + } + + return results, nil +} + +// 获取域用户 +func (d *DomainInfo) GetDomainUsers() ([]string, error) { + + searchRequest := ldap.NewSearchRequest( + d.baseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + "(&(objectCategory=person)(objectClass=user))", + []string{"sAMAccountName"}, + nil, + ) + + sr, err := d.conn.SearchWithPaging(searchRequest, 10000) + if err != nil { + return nil, err + } + + var users []string + for _, entry := range sr.Entries { + users = append(users, entry.GetAttributeValue("sAMAccountName")) + } + + return users, nil +} + +// 获取域管理员 +func (d *DomainInfo) GetDomainAdmins() ([]string, error) { + searchRequest := ldap.NewSearchRequest( + d.baseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + "(&(objectCategory=group)(cn=Domain Admins))", + []string{"member", "sAMAccountName"}, + nil, + ) + + sr, err := d.conn.SearchWithPaging(searchRequest, 10000) + if err != nil { + return nil, err + } + + var admins []string + if len(sr.Entries) > 0 { + // 获取组成员 + members := sr.Entries[0].GetAttributeValues("member") + + // 对每个成员DN执行查询以获取其sAMAccountName + for _, memberDN := range members { + memberSearch := ldap.NewSearchRequest( + memberDN, + ldap.ScopeBaseObject, + ldap.NeverDerefAliases, + 0, + 0, + false, + "(objectClass=*)", + []string{"sAMAccountName"}, + nil, + ) + + memberResult, err := d.conn.Search(memberSearch) + if err != nil { + continue // 跳过出错的成员 + } + + if len(memberResult.Entries) > 0 { + samAccountName := memberResult.Entries[0].GetAttributeValue("sAMAccountName") + if samAccountName != "" { + admins = append(admins, samAccountName) + } + } + } + } + + return admins, nil +} + +// 获取组织单位(OU) +func (d *DomainInfo) GetOUs() ([]string, error) { + searchRequest := ldap.NewSearchRequest( + d.baseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + "(objectClass=organizationalUnit)", + []string{"ou"}, + nil, + ) + + sr, err := d.conn.SearchWithPaging(searchRequest, 10000) + if err != nil { + return nil, err + } + + var ous []string + for _, entry := range sr.Entries { + ou := entry.GetAttributeValue("ou") + if ou != "" { + ous = append(ous, ou) + } + } + return ous, nil +} + +func (d *DomainInfo) GetComputers() ([]Computer, error) { + searchRequest := ldap.NewSearchRequest( + d.baseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + "(&(objectClass=computer))", + []string{"cn", "operatingSystem", "dNSHostName"}, + nil, + ) + + sr, err := d.conn.SearchWithPaging(searchRequest, 10000) + if err != nil { + return nil, err + } + + var computers []Computer + for _, entry := range sr.Entries { + computer := Computer{ + Name: entry.GetAttributeValue("cn"), + OperatingSystem: entry.GetAttributeValue("operatingSystem"), + DNSHostName: entry.GetAttributeValue("dNSHostName"), + } + computers = append(computers, computer) + } + return computers, nil +} + +// 定义计算机结构体 +type Computer struct { + Name string + OperatingSystem string + DNSHostName string +} + +// 获取信任域关系 +func (d *DomainInfo) GetTrustDomains() ([]string, error) { + searchRequest := ldap.NewSearchRequest( + d.baseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + "(&(objectClass=trustedDomain))", + []string{"cn", "trustDirection", "trustType"}, + nil, + ) + + sr, err := d.conn.SearchWithPaging(searchRequest, 10000) + if err != nil { + return nil, err + } + + var trustInfo []string + for _, entry := range sr.Entries { + cn := entry.GetAttributeValue("cn") + if cn != "" { + trustInfo = append(trustInfo, cn) + } + } + return trustInfo, nil +} + +// 获取域管理员组成员 +func (d *DomainInfo) GetAdminGroups() (map[string][]string, error) { + adminGroups := map[string]string{ + "Domain Admins": "(&(objectClass=group)(cn=Domain Admins))", + "Enterprise Admins": "(&(objectClass=group)(cn=Enterprise Admins))", + "Administrators": "(&(objectClass=group)(cn=Administrators))", + } + + results := make(map[string][]string) + + for groupName, filter := range adminGroups { + searchRequest := ldap.NewSearchRequest( + d.baseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + filter, + []string{"member"}, + nil, + ) + + sr, err := d.conn.SearchWithPaging(searchRequest, 10000) + if err != nil { + continue + } + + if len(sr.Entries) > 0 { + members := sr.Entries[0].GetAttributeValues("member") + if len(members) > 0 { + results[groupName] = members + } + } + } + return results, nil +} + +// 获取委派信息 +func (d *DomainInfo) GetDelegation() (map[string][]string, error) { + delegationQueries := map[string]string{ + "非约束委派": "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=524288))", + "约束委派": "(msDS-AllowedToDelegateTo=*)", + "基于资源的约束委派": "(msDS-AllowedToActOnBehalfOfOtherIdentity=*)", + } + + results := make(map[string][]string) + + for delegationType, query := range delegationQueries { + searchRequest := ldap.NewSearchRequest( + d.baseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + query, + []string{"cn", "distinguishedName"}, + nil, + ) + + sr, err := d.conn.SearchWithPaging(searchRequest, 10000) + if err != nil { + continue + } + + var entries []string + for _, entry := range sr.Entries { + cn := entry.GetAttributeValue("cn") + if cn != "" { + entries = append(entries, cn) + } + } + + if len(entries) > 0 { + results[delegationType] = entries + } + } + return results, nil +} + +// 获取AS-REP Roasting漏洞用户 +func (d *DomainInfo) GetAsrepRoastUsers() ([]string, error) { + searchRequest := ldap.NewSearchRequest( + d.baseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + "(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))", + []string{"sAMAccountName"}, + nil, + ) + + sr, err := d.conn.SearchWithPaging(searchRequest, 10000) + if err != nil { + return nil, err + } + + var users []string + for _, entry := range sr.Entries { + name := entry.GetAttributeValue("sAMAccountName") + if name != "" { + users = append(users, name) + } + } + return users, nil +} + +// 获取域密码策略 +func (d *DomainInfo) GetPasswordPolicy() (map[string]string, error) { + searchRequest := ldap.NewSearchRequest( + d.baseDN, + ldap.ScopeBaseObject, + ldap.NeverDerefAliases, + 0, + 0, + false, + "(objectClass=*)", + []string{ + "maxPwdAge", + "minPwdAge", + "minPwdLength", + "pwdHistoryLength", + "pwdProperties", + "lockoutThreshold", + "lockoutDuration", + }, + nil, + ) + + sr, err := d.conn.Search(searchRequest) + if err != nil { + return nil, err + } + + if len(sr.Entries) == 0 { + return nil, fmt.Errorf("未找到密码策略信息") + } + + policy := make(map[string]string) + entry := sr.Entries[0] + + // 转换最大密码期限(负值,以100纳秒为单位) + if maxAge := entry.GetAttributeValue("maxPwdAge"); maxAge != "" { + maxAgeInt, _ := strconv.ParseInt(maxAge, 10, 64) + if maxAgeInt != 0 { + days := float64(maxAgeInt) * -1 / float64(864000000000) + policy["最大密码期限"] = fmt.Sprintf("%.0f天", days) + } + } + + if minLength := entry.GetAttributeValue("minPwdLength"); minLength != "" { + policy["最小密码长度"] = minLength + "个字符" + } + + if historyLength := entry.GetAttributeValue("pwdHistoryLength"); historyLength != "" { + policy["密码历史长度"] = historyLength + "个" + } + + if lockoutThreshold := entry.GetAttributeValue("lockoutThreshold"); lockoutThreshold != "" { + policy["账户锁定阈值"] = lockoutThreshold + "次" + } + + return policy, nil +} + +// 获取SPN信息 +func (d *DomainInfo) GetSPNs() (map[string][]string, error) { + searchRequest := ldap.NewSearchRequest( + d.baseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + "(servicePrincipalName=*)", + []string{"distinguishedName", "servicePrincipalName", "cn"}, + nil, + ) + + sr, err := d.conn.SearchWithPaging(searchRequest, 10000) + if err != nil { + return nil, err + } + + spns := make(map[string][]string) + for _, entry := range sr.Entries { + dn := entry.GetAttributeValue("distinguishedName") + _ = entry.GetAttributeValue("cn") + spnList := entry.GetAttributeValues("servicePrincipalName") + if len(spnList) > 0 { + key := fmt.Sprintf("[*] SPN:%s", dn) + spns[key] = spnList + } + } + return spns, nil +} + +// 获取域控制器地址 +func getDomainController() (string, error) { + // 先尝试使用 wmic 获取当前域名 + cmd := exec.Command("wmic", "computersystem", "get", "domain") + output, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("获取域名失败: %v", err) + } + + lines := strings.Split(string(output), "\n") + if len(lines) < 2 { + return "", fmt.Errorf("未找到域名") + } + + domain := strings.TrimSpace(lines[1]) + if domain == "" { + return "", fmt.Errorf("域名为空") + } + + // 使用 nslookup 查询域控制器 + cmd = exec.Command("nslookup", "-type=SRV", fmt.Sprintf("_ldap._tcp.dc._msdcs.%s", domain)) + output, err = cmd.Output() + if err != nil { + return "", fmt.Errorf("查询域控制器失败: %v", err) + } + + // 解析 nslookup 输出 + lines = strings.Split(string(output), "\n") + for _, line := range lines { + // 查找包含域控制器主机名的行 + if strings.Contains(line, "svr hostname") { + parts := strings.Split(line, "=") + if len(parts) > 1 { + dcHost := strings.TrimSpace(parts[1]) + // 移除末尾的点号(如果存在) + dcHost = strings.TrimSuffix(dcHost, ".") + return dcHost, nil + } + } + } + + // 如果上述方法失败,尝试直接使用域名前缀加上 DC 后缀 + domainParts := strings.Split(domain, ".") + if len(domainParts) > 0 { + return fmt.Sprintf("dc.%s", domain), nil + } + + return "", fmt.Errorf("无法获取域控制器地址") +} + +func NewDomainInfo() (*DomainInfo, error) { + // 获取域控制器地址 + dcHost, err := getDomainController() + if err != nil { + return nil, fmt.Errorf("获取域控制器失败: %v", err) + } + + // 创建SSPI客户端 + ldapClient, err := gssapi.NewSSPIClient() + if err != nil { + return nil, fmt.Errorf("创建SSPI客户端失败: %v", err) + } + defer ldapClient.Close() + + // 创建LDAP连接 + conn, err := ldap.DialURL(fmt.Sprintf("ldap://%s:389", dcHost)) + if err != nil { + return nil, fmt.Errorf("LDAP连接失败: %v", err) + } + + // 使用GSSAPI进行绑定 + err = conn.GSSAPIBind(ldapClient, fmt.Sprintf("ldap/%s", dcHost), "") + if err != nil { + conn.Close() + return nil, fmt.Errorf("GSSAPI绑定失败: %v", err) + } + + // 先执行一个根搜索来获取defaultNamingContext + searchRequest := ldap.NewSearchRequest( + "", + ldap.ScopeBaseObject, + ldap.NeverDerefAliases, + 0, 0, false, + "(objectClass=*)", + []string{"defaultNamingContext"}, + nil, + ) + + result, err := conn.Search(searchRequest) + if err != nil { + conn.Close() + return nil, fmt.Errorf("获取defaultNamingContext失败: %v", err) + } + + if len(result.Entries) == 0 { + conn.Close() + return nil, fmt.Errorf("未找到defaultNamingContext") + } + + baseDN := result.Entries[0].GetAttributeValue("defaultNamingContext") + if baseDN == "" { + baseDN = getDomainDN(dcHost) // 使用备选方法 + } + + fmt.Printf("Using BaseDN: %s\n", baseDN) // 添加调试输出 + + return &DomainInfo{ + conn: conn, + baseDN: baseDN, + }, nil +} + +func DCInfoScan(info *Common.HostInfo) (err error) { + // 创建DomainInfo实例,使用当前用户凭据 + di, err := NewDomainInfo() + if err != nil { + log.Fatal(err) + } + defer di.Close() + + // 首先获取特殊计算机列表 + specialComputers, err := di.GetSpecialComputers() + if err != nil { + log.Printf("获取特殊计算机失败: %v", err) + } else { + // 按固定顺序显示结果 + categories := []string{ + "SQL服务器", + "CA服务器", + "域控制器", + "Exchange服务器", + } + + for _, category := range categories { + if computers, ok := specialComputers[category]; ok { + fmt.Printf("[*] %s:\n", category) + for _, computer := range computers { + fmt.Printf("\t%s\n", computer) + } + } + } + fmt.Println() + } + + users, err := di.GetDomainUsers() + if err != nil { + log.Printf("获取域用户失败: %v", err) + return + } + + // 打印用户信息 + fmt.Println("[*] 域用户:") + for _, user := range users { + fmt.Println("\t" + user) + } + + // 获取域管理员 + admins, err := di.GetDomainAdmins() + if err != nil { + log.Printf("获取域管理员失败: %v", err) + return + } + + // 打印域管理员信息 + fmt.Println("[*] 域管理员:") + for _, admin := range admins { + fmt.Println("\t" + admin) + } + + // 获取组织单位 + ous, err := di.GetOUs() + if err != nil { + log.Printf("获取组织单位失败: %v", err) + return + } + + // 打印组织单位信息 + fmt.Println("[*] 组织单位:") + for _, ou := range ous { + fmt.Println("\t" + ou) + } + + // 获取域计算机 + computers, err := di.GetComputers() + if err != nil { + log.Printf("获取域计算机失败: %v", err) + return + } + + // 打印域计算机信息 + fmt.Println("[*] 域计算机:") + for _, computer := range computers { + fmt.Printf("\t%s", computer.Name) + if computer.OperatingSystem != "" { + fmt.Printf(" --> %s", computer.OperatingSystem) + } + fmt.Println() + } + + // 获取并显示信任域关系 + trustDomains, err := di.GetTrustDomains() + if err == nil { + fmt.Println("[*] 信任域关系:") + for _, domain := range trustDomains { + fmt.Printf("\t%s\n", domain) + } + fmt.Println() + } + + // 获取并显示域管理员组信息 + adminGroups, err := di.GetAdminGroups() + if err == nil { + for groupName, members := range adminGroups { + fmt.Printf("[*] %s成员:\n", groupName) + for _, member := range members { + fmt.Printf("\t%s\n", member) + } + fmt.Println() + } + } + + // 获取并显示委派信息 + delegations, err := di.GetDelegation() + if err == nil { + for delegationType, entries := range delegations { + fmt.Printf("[*] %s:\n", delegationType) + for _, entry := range entries { + fmt.Printf("\t%s\n", entry) + } + fmt.Println() + } + } + + // 获取并显示AS-REP Roasting漏洞用户 + asrepUsers, err := di.GetAsrepRoastUsers() + if err == nil { + fmt.Println("[*] AS-REP弱口令账户:") + for _, user := range asrepUsers { + fmt.Printf("\t%s\n", user) + } + fmt.Println() + } + + // 获取并显示域密码策略 + passwordPolicy, err := di.GetPasswordPolicy() + if err == nil { + fmt.Println("[*] 域密码策略:") + for key, value := range passwordPolicy { + fmt.Printf("\t%s: %s\n", key, value) + } + fmt.Println() + } + + // 获取SPN信息 + spns, err := di.GetSPNs() + if err != nil { + log.Printf("获取SPN信息失败: %v", err) + return + } + + // 打印SPN信息 + if len(spns) > 0 { + for dn, spnList := range spns { + fmt.Println(dn) + for _, spn := range spnList { + fmt.Printf("\t%s\n", spn) + } + fmt.Println() + } + } else { + fmt.Println("[*] 未发现SPN信息\n") + } + return nil +} + +// 辅助函数:从服务器地址获取域DN +func getDomainDN(server string) string { + parts := strings.Split(server, ".") + var dn []string + for _, part := range parts { + dn = append(dn, fmt.Sprintf("DC=%s", part)) + } + return strings.Join(dn, ",") +}