From 150e48ba85f939ab682d089908c2ddccffb1d4fb Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Fri, 15 May 2026 23:16:35 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=90=88=E5=B9=B6=20fileinfo=20?= =?UTF-8?q?=E5=92=8C=20dcinfo=20=E5=88=B0=20systeminfo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fileinfo 的敏感文件扫描合并到 systeminfo - dcinfo 的域控信息收集合并到 systeminfo,通过 build tag 隔离平台差异 - 检测到域环境时自动触发域信息收集,WORKGROUP 环境自动跳过 - 新增 systeminfo_dc_windows.go 和 systeminfo_dc_other.go 处理跨平台编译 --- common/i18n/locales/en.yaml | 42 +- common/i18n/locales/zh.yaml | 42 +- plugins/local/dcinfo.go | 829 ------------------------- plugins/local/fileinfo.go | 182 ------ plugins/local/systeminfo.go | 77 ++- plugins/local/systeminfo_dc_other.go | 5 + plugins/local/systeminfo_dc_windows.go | 232 +++++++ 7 files changed, 371 insertions(+), 1038 deletions(-) delete mode 100644 plugins/local/dcinfo.go delete mode 100644 plugins/local/fileinfo.go create mode 100644 plugins/local/systeminfo_dc_other.go create mode 100644 plugins/local/systeminfo_dc_windows.go diff --git a/common/i18n/locales/en.yaml b/common/i18n/locales/en.yaml index 8cabbf0..fb98261 100644 --- a/common/i18n/locales/en.yaml +++ b/common/i18n/locales/en.yaml @@ -595,18 +595,6 @@ forwardshell_read_failed: winstartup_success: other: "Windows startup folder persistence completed: {{.Arg1}} methods" -# File info -fileinfo_sensitive: - other: "Found sensitive file: {{.Arg1}}" -fileinfo_potential: - other: "Found potentially sensitive file: {{.Arg1}}" - -# DC info -dcinfo_not_joined: - other: "Current computer is not joined to a domain" -dcinfo_success: - other: "Domain controller info collection completed: {{.Arg1}} categories succeeded" - # Windows service winservice_success: other: "Windows service persistence completed: {{.Arg1}} items" @@ -704,6 +692,36 @@ systeminfo_macos_detail: other: "macOS: {{.Arg1}}" systeminfo_sensitive_env: other: "Sensitive Env Var: {{.Arg1}}={{.Arg2}}" +systeminfo_sensitive_file: + other: "Sensitive File: {{.Arg1}}" +systeminfo_dc_detected: + other: "Domain detected: {{.Arg1}}" +systeminfo_dc_connect_failed: + other: "Domain controller connection failed: {{.Arg1}}" +systeminfo_dc_basedn: + other: "Domain Base DN: {{.Arg1}}" +systeminfo_dc_created: + other: "Domain created: {{.Arg1}}" +systeminfo_dc_func_level: + other: "Domain functional level: {{.Arg1}}" +systeminfo_dc_controllers: + other: "Domain controllers: {{.Arg1}}" +systeminfo_dc_controller_detail: + other: " - {{.Arg1}} ({{.Arg2}}) {{.Arg3}}" +systeminfo_dc_admins: + other: "Domain admins: {{.Arg1}}" +systeminfo_dc_admin_detail: + other: " - {{.Arg1}} ({{.Arg2}})" +systeminfo_dc_users: + other: "Domain users: {{.Arg1}}" +systeminfo_dc_computers: + other: "Domain computers: {{.Arg1}}" +systeminfo_dc_computer_detail: + other: " - {{.Arg1}} ({{.Arg2}})" +systeminfo_dc_gpos: + other: "Group policies: {{.Arg1}}" +systeminfo_dc_gpo_detail: + other: " - {{.Arg1}} [{{.Arg2}}]" # Windows scheduled task winschtask_success: diff --git a/common/i18n/locales/zh.yaml b/common/i18n/locales/zh.yaml index d524871..f8e118a 100644 --- a/common/i18n/locales/zh.yaml +++ b/common/i18n/locales/zh.yaml @@ -594,18 +594,6 @@ forwardshell_read_failed: winstartup_success: other: "Windows启动文件夹持久化完成: {{.Arg1}}个方法" -# 文件信息 -fileinfo_sensitive: - other: "发现敏感文件: {{.Arg1}}" -fileinfo_potential: - other: "发现潜在敏感文件: {{.Arg1}}" - -# 域控信息 -dcinfo_not_joined: - other: "当前计算机未加入域环境" -dcinfo_success: - other: "域控制器信息收集完成: {{.Arg1}}个类别成功" - # Windows服务 winservice_success: other: "Windows服务持久化完成: {{.Arg1}}个项目" @@ -703,6 +691,36 @@ systeminfo_macos_detail: other: "macOS: {{.Arg1}}" systeminfo_sensitive_env: other: "敏感环境变量: {{.Arg1}}={{.Arg2}}" +systeminfo_sensitive_file: + other: "敏感文件: {{.Arg1}}" +systeminfo_dc_detected: + other: "检测到域环境: {{.Arg1}}" +systeminfo_dc_connect_failed: + other: "域控连接失败: {{.Arg1}}" +systeminfo_dc_basedn: + other: "域 Base DN: {{.Arg1}}" +systeminfo_dc_created: + other: "域创建时间: {{.Arg1}}" +systeminfo_dc_func_level: + other: "域功能级别: {{.Arg1}}" +systeminfo_dc_controllers: + other: "域控制器: {{.Arg1}}个" +systeminfo_dc_controller_detail: + other: " - {{.Arg1}} ({{.Arg2}}) {{.Arg3}}" +systeminfo_dc_admins: + other: "域管理员: {{.Arg1}}个" +systeminfo_dc_admin_detail: + other: " - {{.Arg1}} ({{.Arg2}})" +systeminfo_dc_users: + other: "域用户数: {{.Arg1}}" +systeminfo_dc_computers: + other: "域计算机数: {{.Arg1}}" +systeminfo_dc_computer_detail: + other: " - {{.Arg1}} ({{.Arg2}})" +systeminfo_dc_gpos: + other: "组策略: {{.Arg1}}个" +systeminfo_dc_gpo_detail: + other: " - {{.Arg1}} [{{.Arg2}}]" # Windows计划任务 winschtask_success: diff --git a/plugins/local/dcinfo.go b/plugins/local/dcinfo.go deleted file mode 100644 index 6161f0d..0000000 --- a/plugins/local/dcinfo.go +++ /dev/null @@ -1,829 +0,0 @@ -//go:build (plugin_dcinfo || !plugin_selective) && windows && !no_local - -package local - -import ( - "context" - "errors" - "fmt" - "net" - "os/exec" - "strings" - - "github.com/go-ldap/ldap/v3" - "github.com/go-ldap/ldap/v3/gssapi" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" - "github.com/shadow1ng/fscan/plugins" -) - -// DCInfoPlugin 域控信息收集插件 -// 设计哲学:直接实现,删除过度设计 -// - 删除复杂的继承体系 -// - 直接实现域信息收集功能 -// - 保持原有功能逻辑 -type DCInfoPlugin struct { - plugins.BasePlugin -} - -// DomainInfo 域信息结构 -type DomainInfo struct { - Domain string - BaseDN string - LDAPConn *ldap.Conn -} - -// NewDCInfoPlugin 创建域控信息收集插件 -func NewDCInfoPlugin() *DCInfoPlugin { - return &DCInfoPlugin{ - BasePlugin: plugins.NewBasePlugin("dcinfo"), - } -} - -// Scan 执行域控信息收集 - 直接实现 -func (p *DCInfoPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result { - _ = session.Config - _ = session.State - var output strings.Builder - - output.WriteString("=== 域控制器信息收集 ===\n") - - // 建立域控连接 - domainConn, err := p.connectToDomain() - if err != nil { - if common.ContainsAny(err.Error(), "未加入域", "WORKGROUP") { - msg := i18n.GetText("dcinfo_not_joined") - output.WriteString(msg + ",无法执行域信息收集\n") - common.LogError(msg) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: errors.New(msg), - } - } - output.WriteString(fmt.Sprintf("域控连接失败: %v\n", err)) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: fmt.Errorf("域控连接失败: %w", err), - } - } - defer func() { - if domainConn.LDAPConn != nil { - _ = domainConn.LDAPConn.Close() - } - }() - - output.WriteString(fmt.Sprintf("成功连接到域: %s\n", domainConn.Domain)) - output.WriteString(fmt.Sprintf("Base DN: %s\n\n", domainConn.BaseDN)) - - var successCount int - - // 收集域基本信息 - if domainInfo, err := p.getDomainInfo(domainConn); err == nil { - output.WriteString("✓ 域基本信息:\n") - p.logDomainInfoToOutput(&output, domainInfo) - successCount++ - } else { - output.WriteString(fmt.Sprintf("✗ 获取域基本信息失败: %v\n", err)) - } - - // 获取域控制器信息 - if domainControllers, err := p.getDomainControllers(domainConn); err == nil { - output.WriteString("✓ 域控制器信息:\n") - p.logDomainControllersToOutput(&output, domainControllers) - successCount++ - } else { - output.WriteString(fmt.Sprintf("✗ 获取域控制器信息失败: %v\n", err)) - } - - // 获取域用户信息 - if users, err := p.getDomainUsersDetailed(domainConn); err == nil { - output.WriteString("✓ 域用户信息:\n") - p.logDomainUsersToOutput(&output, users) - successCount++ - } else { - output.WriteString(fmt.Sprintf("✗ 获取域用户失败: %v\n", err)) - } - - // 获取域管理员信息 - if admins, err := p.getDomainAdminsDetailed(domainConn); err == nil { - output.WriteString("✓ 域管理员信息:\n") - p.logDomainAdminsToOutput(&output, admins) - successCount++ - } else { - output.WriteString(fmt.Sprintf("✗ 获取域管理员失败: %v\n", err)) - } - - // 获取域计算机信息 - if computers, err := p.getComputersDetailed(domainConn); err == nil { - output.WriteString("✓ 域计算机信息:\n") - p.logComputersToOutput(&output, computers) - successCount++ - } else { - output.WriteString(fmt.Sprintf("✗ 获取域计算机失败: %v\n", err)) - } - - // 获取组策略信息 - if gpos, err := p.getGroupPolicies(domainConn); err == nil { - output.WriteString("✓ 组策略信息:\n") - p.logGroupPoliciesToOutput(&output, gpos) - successCount++ - } else { - output.WriteString(fmt.Sprintf("✗ 获取组策略失败: %v\n", err)) - } - - // 获取组织单位信息 - if ous, err := p.getOrganizationalUnits(domainConn); err == nil { - output.WriteString("✓ 组织单位信息:\n") - p.logOrganizationalUnitsToOutput(&output, ous) - successCount++ - } else { - output.WriteString(fmt.Sprintf("✗ 获取组织单位失败: %v\n", err)) - } - - // 输出统计 - output.WriteString(fmt.Sprintf("\n域信息收集完成: 成功(%d) 总计(%d)\n", successCount, 7)) - - if successCount > 0 { - common.LogSuccess(i18n.Tr("dcinfo_success", successCount)) - } - - return &plugins.Result{ - Success: successCount > 0, - Output: output.String(), - Error: nil, - } -} - -// connectToDomain 连接到域控制器 -func (p *DCInfoPlugin) connectToDomain() (*DomainInfo, error) { - // 获取域控制器地址 - dcHost, domain, err := p.getDomainController() - if err != nil { - return nil, fmt.Errorf("获取域控制器失败: %w", err) - } - - // 建立LDAP连接 - ldapConn, baseDN, err := p.connectToLDAP(dcHost, domain) - if err != nil { - return nil, fmt.Errorf("LDAP连接失败: %w", err) - } - - return &DomainInfo{ - Domain: domain, - BaseDN: baseDN, - LDAPConn: ldapConn, - }, nil -} - -// getDomainController 获取域控制器地址 -func (p *DCInfoPlugin) getDomainController() (string, string, error) { - // 尝试使用PowerShell获取域名 - domain, err := p.getDomainNamePowerShell() - if err != nil { - // 尝试使用wmic - domain, err = p.getDomainNameWmic() - if err != nil { - // 尝试使用环境变量 - domain, err = p.getDomainNameFromEnv() - if err != nil { - return "", "", fmt.Errorf("获取域名失败: %w", err) - } - } - } - - if domain == "" || domain == "WORKGROUP" { - return "", "", fmt.Errorf("当前机器未加入域") - } - - // 查询域控制器 - dcHost, err := p.findDomainController(domain) - if err != nil { - // 备选方案:使用域名直接构造 - dcHost = fmt.Sprintf("dc.%s", domain) - } - - return dcHost, domain, nil -} - -// getDomainNamePowerShell 使用PowerShell获取域名 -func (p *DCInfoPlugin) getDomainNamePowerShell() (string, error) { - cmd := exec.Command("powershell", "-Command", "(Get-WmiObject Win32_ComputerSystem).Domain") - output, err := cmd.Output() - if err != nil { - return "", err - } - - domain := strings.TrimSpace(string(output)) - if domain == "" || domain == "WORKGROUP" { - return "", fmt.Errorf("未加入域") - } - - return domain, nil -} - -// getDomainNameWmic 使用wmic获取域名 -func (p *DCInfoPlugin) getDomainNameWmic() (string, error) { - cmd := exec.Command("wmic", "computersystem", "get", "domain", "/value") - output, err := cmd.Output() - if err != nil { - return "", err - } - - lines := strings.Split(string(output), "\n") - for _, line := range lines { - if strings.HasPrefix(line, "Domain=") { - domain := strings.TrimSpace(strings.TrimPrefix(line, "Domain=")) - if domain != "" && domain != "WORKGROUP" { - return domain, nil - } - } - } - - return "", fmt.Errorf("未找到域名") -} - -// getDomainNameFromEnv 从环境变量获取域名 -func (p *DCInfoPlugin) getDomainNameFromEnv() (string, error) { - cmd := exec.Command("cmd", "/c", "echo %USERDOMAIN%") - output, err := cmd.Output() - if err != nil { - return "", err - } - - userDomain := strings.ToLower(strings.TrimSpace(string(output))) - if userDomain != "" && userDomain != "workgroup" && userDomain != "%userdomain%" { - return userDomain, nil - } - - return "", fmt.Errorf("从环境变量获取域名失败") -} - -// findDomainController 查找域控制器 -func (p *DCInfoPlugin) findDomainController(domain string) (string, error) { - // 使用nslookup查询SRV记录 - cmd := exec.Command("nslookup", "-type=SRV", fmt.Sprintf("_ldap._tcp.dc._msdcs.%s", domain)) - output, err := cmd.Output() - if err == nil { - lines := strings.Split(string(output), "\n") - for _, line := range lines { - if common.ContainsAny(line, "svr hostname", "service") { - parts := strings.Split(line, "=") - if len(parts) > 1 { - dcHost := strings.TrimSpace(parts[len(parts)-1]) - dcHost = strings.TrimSuffix(dcHost, ".") - if dcHost != "" { - return dcHost, nil - } - } - } - } - } - - // 尝试直接ping域名 - cmd = exec.Command("ping", "-n", "1", domain) - if err := cmd.Run(); err == nil { - return domain, nil - } - - return "", fmt.Errorf("无法找到域控制器") -} - -// connectToLDAP 连接到LDAP服务器 -func (p *DCInfoPlugin) connectToLDAP(dcHost, domain string) (*ldap.Conn, string, error) { - // 创建SSPI客户端 - ldapClient, err := gssapi.NewSSPIClient() - if err != nil { - return nil, "", fmt.Errorf("创建SSPI客户端失败: %w", err) - } - defer func() { _ = ldapClient.Close() }() - - // 尝试连接 - var conn *ldap.Conn - var lastError error - - // 直接连接 - conn, err = ldap.DialURL(fmt.Sprintf("ldap://%s:389", dcHost)) - if err != nil { - lastError = err - // 尝试使用IPv4地址 - ipv4, resolveErr := p.resolveIPv4(dcHost) - if resolveErr == nil { - conn, err = ldap.DialURL(fmt.Sprintf("ldap://%s:389", ipv4)) - if err != nil { - lastError = err - } - } else { - lastError = resolveErr - } - } - - if conn == nil { - return nil, "", fmt.Errorf("LDAP连接失败: %w", lastError) - } - - // 使用GSSAPI进行绑定 - err = conn.GSSAPIBind(ldapClient, fmt.Sprintf("ldap/%s", dcHost), "") - if err != nil { - _ = conn.Close() - return nil, "", fmt.Errorf("GSSAPI绑定失败: %w", err) - } - - // 获取BaseDN - baseDN, err := p.getBaseDN(conn, domain) - if err != nil { - _ = conn.Close() - return nil, "", err - } - - return conn, baseDN, nil -} - -// getBaseDN 获取BaseDN -func (p *DCInfoPlugin) getBaseDN(conn *ldap.Conn, domain string) (string, error) { - searchRequest := ldap.NewSearchRequest( - "", - ldap.ScopeBaseObject, - ldap.NeverDerefAliases, - 0, 0, false, - "(objectClass=*)", - []string{"defaultNamingContext"}, - nil, - ) - - result, err := conn.Search(searchRequest) - if err != nil { - return "", fmt.Errorf("获取defaultNamingContext失败: %w", err) - } - - if len(result.Entries) == 0 { - // 备选方案:从域名构造BaseDN - parts := strings.Split(domain, ".") - var dn []string - for _, part := range parts { - dn = append(dn, fmt.Sprintf("DC=%s", part)) - } - return strings.Join(dn, ","), nil - } - - baseDN := result.Entries[0].GetAttributeValue("defaultNamingContext") - if baseDN == "" { - return "", fmt.Errorf("获取BaseDN失败") - } - - return baseDN, nil -} - -// resolveIPv4 解析主机名为IPv4地址 -func (p *DCInfoPlugin) resolveIPv4(hostname string) (string, error) { - ips, err := net.LookupIP(hostname) - if err != nil { - return "", err - } - - for _, ip := range ips { - if ip.To4() != nil { - return ip.String(), nil - } - } - - return "", fmt.Errorf("未找到IPv4地址") -} - -// getDomainInfo 获取域基本信息 -func (p *DCInfoPlugin) getDomainInfo(conn *DomainInfo) (map[string]interface{}, error) { - searchRequest := ldap.NewSearchRequest( - conn.BaseDN, - ldap.ScopeBaseObject, - ldap.NeverDerefAliases, - 0, 0, false, - "(objectClass=*)", - []string{"whenCreated", "whenChanged", "objectSid", "msDS-Behavior-Version", "dnsRoot"}, - nil, - ) - - sr, err := conn.LDAPConn.Search(searchRequest) - if err != nil { - return nil, err - } - - domainInfo := make(map[string]interface{}) - domainInfo["domain"] = conn.Domain - domainInfo["base_dn"] = conn.BaseDN - - if len(sr.Entries) > 0 { - entry := sr.Entries[0] - domainInfo["created"] = entry.GetAttributeValue("whenCreated") - domainInfo["modified"] = entry.GetAttributeValue("whenChanged") - domainInfo["object_sid"] = entry.GetAttributeValue("objectSid") - domainInfo["functional_level"] = entry.GetAttributeValue("msDS-Behavior-Version") - domainInfo["dns_root"] = entry.GetAttributeValue("dnsRoot") - } - - return domainInfo, nil -} - -// getDomainControllers 获取域控制器信息 -func (p *DCInfoPlugin) getDomainControllers(conn *DomainInfo) ([]map[string]interface{}, error) { - dcQuery := ldap.NewSearchRequest( - conn.BaseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, 0, false, - "(&(objectClass=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))", - []string{"cn", "dNSHostName", "operatingSystem", "operatingSystemVersion", "operatingSystemServicePack", "whenCreated", "lastLogonTimestamp"}, - nil, - ) - - sr, err := conn.LDAPConn.SearchWithPaging(dcQuery, 10000) - if err != nil { - return nil, err - } - - var dcs []map[string]interface{} - for _, entry := range sr.Entries { - dc := make(map[string]interface{}) - dc["name"] = entry.GetAttributeValue("cn") - dc["dns_name"] = entry.GetAttributeValue("dNSHostName") - dc["os"] = entry.GetAttributeValue("operatingSystem") - dc["os_version"] = entry.GetAttributeValue("operatingSystemVersion") - dc["os_service_pack"] = entry.GetAttributeValue("operatingSystemServicePack") - dc["created"] = entry.GetAttributeValue("whenCreated") - dc["last_logon"] = entry.GetAttributeValue("lastLogonTimestamp") - dcs = append(dcs, dc) - } - - return dcs, nil -} - -// getDomainUsersDetailed 获取域用户信息 -func (p *DCInfoPlugin) getDomainUsersDetailed(conn *DomainInfo) ([]map[string]interface{}, error) { - searchRequest := ldap.NewSearchRequest( - conn.BaseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, 0, false, - "(&(objectCategory=person)(objectClass=user))", - []string{"sAMAccountName", "displayName", "mail", "userAccountControl", "whenCreated", "lastLogonTimestamp", "badPwdCount", "pwdLastSet"}, - nil, - ) - - sr, err := conn.LDAPConn.SearchWithPaging(searchRequest, 0) - if err != nil { - return nil, err - } - - var users []map[string]interface{} - for _, entry := range sr.Entries { - user := make(map[string]interface{}) - user["username"] = entry.GetAttributeValue("sAMAccountName") - user["display_name"] = entry.GetAttributeValue("displayName") - user["email"] = entry.GetAttributeValue("mail") - user["account_control"] = entry.GetAttributeValue("userAccountControl") - user["created"] = entry.GetAttributeValue("whenCreated") - user["last_logon"] = entry.GetAttributeValue("lastLogonTimestamp") - user["bad_pwd_count"] = entry.GetAttributeValue("badPwdCount") - user["pwd_last_set"] = entry.GetAttributeValue("pwdLastSet") - users = append(users, user) - } - - return users, nil -} - -// getDomainAdminsDetailed 获取域管理员信息 -func (p *DCInfoPlugin) getDomainAdminsDetailed(conn *DomainInfo) ([]map[string]interface{}, error) { - // 获取Domain Admins组 - searchRequest := ldap.NewSearchRequest( - conn.BaseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, 0, false, - "(&(objectCategory=group)(cn=Domain Admins))", - []string{"member"}, - nil, - ) - - sr, err := conn.LDAPConn.SearchWithPaging(searchRequest, 10000) - if err != nil { - return nil, err - } - - var admins []map[string]interface{} - if len(sr.Entries) > 0 { - members := sr.Entries[0].GetAttributeValues("member") - for _, memberDN := range members { - adminInfo, err := p.getUserInfoByDN(conn, memberDN) - if err == nil { - admins = append(admins, adminInfo) - } - } - } - - return admins, nil -} - -// getComputersDetailed 获取域计算机信息 -func (p *DCInfoPlugin) getComputersDetailed(conn *DomainInfo) ([]map[string]interface{}, error) { - searchRequest := ldap.NewSearchRequest( - conn.BaseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, 0, false, - "(&(objectClass=computer)(!userAccountControl:1.2.840.113556.1.4.803:=8192))", - []string{"cn", "operatingSystem", "operatingSystemVersion", "dNSHostName", "whenCreated", "lastLogonTimestamp", "userAccountControl"}, - nil, - ) - - sr, err := conn.LDAPConn.SearchWithPaging(searchRequest, 0) - if err != nil { - return nil, err - } - - var computers []map[string]interface{} - for _, entry := range sr.Entries { - computer := make(map[string]interface{}) - computer["name"] = entry.GetAttributeValue("cn") - computer["os"] = entry.GetAttributeValue("operatingSystem") - computer["os_version"] = entry.GetAttributeValue("operatingSystemVersion") - computer["dns_name"] = entry.GetAttributeValue("dNSHostName") - computer["created"] = entry.GetAttributeValue("whenCreated") - computer["last_logon"] = entry.GetAttributeValue("lastLogonTimestamp") - computer["account_control"] = entry.GetAttributeValue("userAccountControl") - computers = append(computers, computer) - } - - return computers, nil -} - -// getUserInfoByDN 根据DN获取用户信息 -func (p *DCInfoPlugin) getUserInfoByDN(conn *DomainInfo, userDN string) (map[string]interface{}, error) { - searchRequest := ldap.NewSearchRequest( - userDN, - ldap.ScopeBaseObject, - ldap.NeverDerefAliases, - 0, 0, false, - "(objectClass=*)", - []string{"sAMAccountName", "displayName", "mail", "whenCreated", "lastLogonTimestamp", "userAccountControl"}, - nil, - ) - - sr, err := conn.LDAPConn.Search(searchRequest) - if err != nil { - return nil, err - } - - if len(sr.Entries) == 0 { - return nil, fmt.Errorf("用户不存在") - } - - entry := sr.Entries[0] - userInfo := make(map[string]interface{}) - userInfo["dn"] = userDN - userInfo["username"] = entry.GetAttributeValue("sAMAccountName") - userInfo["display_name"] = entry.GetAttributeValue("displayName") - userInfo["email"] = entry.GetAttributeValue("mail") - userInfo["created"] = entry.GetAttributeValue("whenCreated") - userInfo["last_logon"] = entry.GetAttributeValue("lastLogonTimestamp") - userInfo["group_type"] = "Domain Admins" - - return userInfo, nil -} - -// getGroupPolicies 获取组策略信息 -func (p *DCInfoPlugin) getGroupPolicies(conn *DomainInfo) ([]map[string]interface{}, error) { - searchRequest := ldap.NewSearchRequest( - conn.BaseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, 0, false, - "(objectClass=groupPolicyContainer)", - []string{"cn", "displayName", "objectClass", "distinguishedName", "whenCreated", "whenChanged", "gPCFileSysPath"}, - nil, - ) - - sr, err := conn.LDAPConn.Search(searchRequest) - if err != nil { - sr, err = conn.LDAPConn.SearchWithPaging(searchRequest, 1000) - if err != nil { - return nil, err - } - } - - var gpos []map[string]interface{} - for _, entry := range sr.Entries { - gpo := make(map[string]interface{}) - gpo["guid"] = entry.GetAttributeValue("cn") - gpo["display_name"] = entry.GetAttributeValue("displayName") - gpo["created"] = entry.GetAttributeValue("whenCreated") - gpo["modified"] = entry.GetAttributeValue("whenChanged") - gpo["file_sys_path"] = entry.GetAttributeValue("gPCFileSysPath") - gpo["dn"] = entry.GetAttributeValue("distinguishedName") - gpos = append(gpos, gpo) - } - - return gpos, nil -} - -// getOrganizationalUnits 获取组织单位信息 -func (p *DCInfoPlugin) getOrganizationalUnits(conn *DomainInfo) ([]map[string]interface{}, error) { - searchRequest := ldap.NewSearchRequest( - conn.BaseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, 0, false, - "(objectClass=*)", - []string{"ou", "cn", "name", "description", "objectClass", "distinguishedName", "whenCreated", "gPLink"}, - nil, - ) - - sr, err := conn.LDAPConn.SearchWithPaging(searchRequest, 100) - if err != nil { - return nil, err - } - - var ous []map[string]interface{} - for _, entry := range sr.Entries { - objectClasses := entry.GetAttributeValues("objectClass") - dn := entry.GetAttributeValue("distinguishedName") - - isOU := false - isContainer := false - for _, class := range objectClasses { - switch class { - case "organizationalUnit": - isOU = true - case "container": - isContainer = true - } - } - - if !isOU && !isContainer { - continue - } - - // 获取名称 - name := entry.GetAttributeValue("ou") - if name == "" { - name = entry.GetAttributeValue("cn") - } - if name == "" { - name = entry.GetAttributeValue("name") - } - - // 跳过系统容器 - if strings.Contains(dn, "CN=LostAndFound") || - strings.Contains(dn, "CN=Configuration") || - strings.Contains(dn, "CN=Schema") || - strings.Contains(dn, "CN=System") || - strings.Contains(dn, "CN=Program Data") || - strings.Contains(dn, "CN=Microsoft") || - (strings.HasPrefix(dn, "CN=") && len(name) == 36 && strings.Count(name, "-") == 4) { - continue - } - - if name != "" { - ou := make(map[string]interface{}) - ou["name"] = name - ou["description"] = entry.GetAttributeValue("description") - ou["created"] = entry.GetAttributeValue("whenCreated") - ou["gp_link"] = entry.GetAttributeValue("gPLink") - ou["dn"] = dn - ou["is_ou"] = isOU - ous = append(ous, ou) - } - } - - return ous, nil -} - -// 输出日志函数 -func (p *DCInfoPlugin) logDomainInfoToOutput(output *strings.Builder, domainInfo map[string]interface{}) { - if domain, ok := domainInfo["domain"]; ok { - _, _ = fmt.Fprintf(output, " 域名: %v\n", domain) - } - if created, ok := domainInfo["created"]; ok && created != "" { - _, _ = fmt.Fprintf(output, " 创建时间: %v\n", created) - } - output.WriteString("\n") -} - -func (p *DCInfoPlugin) logDomainControllersToOutput(output *strings.Builder, dcs []map[string]interface{}) { - _, _ = fmt.Fprintf(output, " 发现 %d 个域控制器\n", len(dcs)) - for _, dc := range dcs { - if name, ok := dc["name"]; ok { - _, _ = fmt.Fprintf(output, " - %v (%v)\n", name, dc["dns_name"]) - if os, ok := dc["os"]; ok && os != "" { - _, _ = fmt.Fprintf(output, " 操作系统: %v\n", os) - } - } - } - output.WriteString("\n") -} - -func (p *DCInfoPlugin) logDomainUsersToOutput(output *strings.Builder, users []map[string]interface{}) { - _, _ = fmt.Fprintf(output, " 发现 %d 个域用户\n", len(users)) - count := 0 - for _, user := range users { - if count >= 10 { // 限制显示数量 - output.WriteString(" ...(更多用户已省略)\n") - break - } - if username, ok := user["username"]; ok && username != "" { - displayInfo := fmt.Sprintf(" - %v", username) - if displayName, ok := user["display_name"]; ok && displayName != "" { - displayInfo += fmt.Sprintf(" (%v)", displayName) - } - if email, ok := user["email"]; ok && email != "" { - displayInfo += fmt.Sprintf(" [%v]", email) - } - output.WriteString(displayInfo + "\n") - count++ - } - } - output.WriteString("\n") -} - -func (p *DCInfoPlugin) logDomainAdminsToOutput(output *strings.Builder, admins []map[string]interface{}) { - _, _ = fmt.Fprintf(output, " 发现 %d 个域管理员\n", len(admins)) - for _, admin := range admins { - if username, ok := admin["username"]; ok && username != "" { - adminInfo := fmt.Sprintf(" - %v", username) - if displayName, ok := admin["display_name"]; ok && displayName != "" { - adminInfo += fmt.Sprintf(" (%v)", displayName) - } - if email, ok := admin["email"]; ok && email != "" { - adminInfo += fmt.Sprintf(" [%v]", email) - } - output.WriteString(adminInfo + "\n") - } - } - output.WriteString("\n") -} - -func (p *DCInfoPlugin) logComputersToOutput(output *strings.Builder, computers []map[string]interface{}) { - _, _ = fmt.Fprintf(output, " 发现 %d 台域计算机\n", len(computers)) - count := 0 - for _, computer := range computers { - if count >= 10 { // 限制显示数量 - output.WriteString(" ...(更多计算机已省略)\n") - break - } - if name, ok := computer["name"]; ok && name != "" { - computerInfo := fmt.Sprintf(" - %v", name) - if os, ok := computer["os"]; ok && os != "" { - computerInfo += fmt.Sprintf(" (%v)", os) - } - if dnsName, ok := computer["dns_name"]; ok && dnsName != "" { - computerInfo += fmt.Sprintf(" [%v]", dnsName) - } - output.WriteString(computerInfo + "\n") - count++ - } - } - output.WriteString("\n") -} - -func (p *DCInfoPlugin) logGroupPoliciesToOutput(output *strings.Builder, gpos []map[string]interface{}) { - _, _ = fmt.Fprintf(output, " 发现 %d 个组策略对象\n", len(gpos)) - for _, gpo := range gpos { - if displayName, ok := gpo["display_name"]; ok && displayName != "" { - gpoInfo := fmt.Sprintf(" - %v", displayName) - if guid, ok := gpo["guid"]; ok { - gpoInfo += fmt.Sprintf(" [%v]", guid) - } - output.WriteString(gpoInfo + "\n") - } - } - output.WriteString("\n") -} - -func (p *DCInfoPlugin) logOrganizationalUnitsToOutput(output *strings.Builder, ous []map[string]interface{}) { - _, _ = fmt.Fprintf(output, " 发现 %d 个组织单位和容器\n", len(ous)) - for _, ou := range ous { - if name, ok := ou["name"]; ok && name != "" { - ouInfo := fmt.Sprintf(" - %v", name) - if isOU, ok := ou["is_ou"]; ok { - if isOUBool, ok := isOU.(bool); ok && isOUBool { - ouInfo += " [OU]" - } else { - ouInfo += " [Container]" - } - } else { - ouInfo += " [Container]" - } - if desc, ok := ou["description"]; ok && desc != "" { - ouInfo += fmt.Sprintf(" 描述: %v", desc) - } - output.WriteString(ouInfo + "\n") - } - } - output.WriteString("\n") -} - -// 注册插件 -func init() { - RegisterLocalPlugin("dcinfo", func() Plugin { - return NewDCInfoPlugin() - }) -} diff --git a/plugins/local/fileinfo.go b/plugins/local/fileinfo.go deleted file mode 100644 index 708c1d5..0000000 --- a/plugins/local/fileinfo.go +++ /dev/null @@ -1,182 +0,0 @@ -//go:build (plugin_fileinfo || !plugin_selective) && !no_local - -package local - -import ( - "context" - "fmt" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/i18n" - "github.com/shadow1ng/fscan/plugins" -) - -// FileInfoPlugin 文件信息收集插件 -// 设计哲学:删除所有不必要的复杂性 -// - 没有继承体系 -// - 没有权限检查(让系统告诉我们) -// - 没有平台检查(运行时错误更清晰) -// - 没有复杂配置(直接硬编码关键路径) -type FileInfoPlugin struct { - plugins.BasePlugin -} - -// NewFileInfoPlugin 创建文件信息插件 -func NewFileInfoPlugin() *FileInfoPlugin { - return &FileInfoPlugin{ - BasePlugin: plugins.NewBasePlugin("fileinfo"), - } -} - -// Scan 执行本地文件扫描 - 直接、简单、有效 -func (p *FileInfoPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result { - var foundFiles []string - - // 扫描关键敏感文件位置 - 删除复杂的配置系统 - sensitiveFiles := p.getSensitiveFiles() - for _, file := range sensitiveFiles { - if p.fileExists(file) { - foundFiles = append(foundFiles, file) - common.LogSuccess(i18n.Tr("fileinfo_sensitive", file)) - } - } - - // 搜索用户目录下的敏感文件 - 简化搜索逻辑 - userFiles := p.searchUserFiles() - foundFiles = append(foundFiles, userFiles...) - - // 构建结果 - output := fmt.Sprintf("文件扫描完成 - 发现 %d 个敏感文件", len(foundFiles)) - if len(foundFiles) > 0 { - output += "\n发现的文件:" - for _, file := range foundFiles { - output += "\n " + file - } - } - - return &plugins.Result{ - Success: len(foundFiles) > 0, - Output: output, - Error: nil, - } -} - -// getSensitiveFiles 获取关键敏感文件列表 - 删除复杂的初始化逻辑 -func (p *FileInfoPlugin) getSensitiveFiles() []string { - var files []string - - switch runtime.GOOS { - case "windows": - files = []string{ - "C:\\boot.ini", - "C:\\Windows\\System32\\config\\SAM", - "C:\\Windows\\repair\\sam", - } - - // 添加用户相关路径 - if homeDir, err := os.UserHomeDir(); err == nil { - files = append(files, []string{ - filepath.Join(homeDir, ".ssh", "id_rsa"), - filepath.Join(homeDir, ".aws", "credentials"), - filepath.Join(homeDir, ".azure", "accessTokens.json"), - }...) - } - - case "linux", "darwin": - files = []string{ - "/etc/passwd", - "/etc/shadow", - "/root/.ssh/id_rsa", - "/root/.ssh/authorized_keys", - "/root/.bash_history", - "/etc/nginx/nginx.conf", - "/etc/apache2/apache2.conf", - } - - // 添加用户相关路径 - if homeDir, err := os.UserHomeDir(); err == nil { - files = append(files, []string{ - filepath.Join(homeDir, ".ssh", "id_rsa"), - filepath.Join(homeDir, ".aws", "credentials"), - filepath.Join(homeDir, ".bash_history"), - }...) - } - } - - return files -} - -// searchUserFiles 搜索用户目录敏感文件 - 简化搜索逻辑 -func (p *FileInfoPlugin) searchUserFiles() []string { - var foundFiles []string - - homeDir, err := os.UserHomeDir() - if err != nil { - return foundFiles - } - - // 关键目录 - 删除复杂的目录配置 - searchDirs := []string{ - filepath.Join(homeDir, "Desktop"), - filepath.Join(homeDir, "Documents"), - filepath.Join(homeDir, ".ssh"), - filepath.Join(homeDir, ".aws"), - } - - // 敏感文件关键词 - 删除复杂的白名单系统 - keywords := []string{"password", "key", "secret", "token", "credential", "passwd"} - - for _, dir := range searchDirs { - if !p.dirExists(dir) { - continue - } - - _ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return nil - } - - // 限制深度和大小 - 简单有效 - if info.IsDir() || info.Size() > 1024*1024 { // 1MB - return nil - } - - // 检查文件名是否包含敏感关键词 - filename := strings.ToLower(filepath.Base(path)) - for _, keyword := range keywords { - if strings.Contains(filename, keyword) { - foundFiles = append(foundFiles, path) - common.LogSuccess(i18n.Tr("fileinfo_potential", path)) - break - } - } - - return nil - }) - } - - return foundFiles -} - -// fileExists 检查文件是否存在 -func (p *FileInfoPlugin) fileExists(path string) bool { - _, err := os.Stat(path) - return err == nil -} - -// dirExists 检查目录是否存在 -func (p *FileInfoPlugin) dirExists(path string) bool { - info, err := os.Stat(path) - return err == nil && info.IsDir() -} - -// 注册插件 -func init() { - RegisterLocalPlugin("fileinfo", func() Plugin { - return NewFileInfoPlugin() - }) -} diff --git a/plugins/local/systeminfo.go b/plugins/local/systeminfo.go index 4baa856..84ecfac 100644 --- a/plugins/local/systeminfo.go +++ b/plugins/local/systeminfo.go @@ -11,6 +11,7 @@ import ( "os" "os/exec" "os/user" + "path/filepath" "runtime" "strings" @@ -58,7 +59,9 @@ func (p *SystemInfoPlugin) Scan(ctx context.Context, info *common.HostInfo, sess p.collectPrivilegeInfo() p.collectPlatformInfo() p.collectAVInfo() + p.collectSensitiveFiles() p.collectSensitiveEnvVars() + p.collectDomainInfo() return &plugins.Result{ Success: true, @@ -237,15 +240,13 @@ func (p *SystemInfoPlugin) collectAVInfo() { return } - // 建立进程名索引,O(1) 查找 processIndex := make(map[string][]string) for _, proc := range processes { name := proc if idx := strings.Index(proc, " (PID: "); idx != -1 { name = proc[:idx] } - key := strings.ToLower(name) - processIndex[key] = append(processIndex[key], proc) + processIndex[strings.ToLower(name)] = append(processIndex[strings.ToLower(name)], proc) } for avName, av := range avProducts { @@ -312,6 +313,76 @@ func (p *SystemInfoPlugin) getUnixProcesses() []string { return processes } +func (p *SystemInfoPlugin) collectSensitiveFiles() { + var sensitiveFiles []string + + switch runtime.GOOS { + case "windows": + sensitiveFiles = []string{ + `C:\Windows\System32\config\SAM`, + `C:\Windows\repair\sam`, + } + case "linux", "darwin": + sensitiveFiles = []string{ + "/etc/shadow", + "/root/.ssh/id_rsa", + "/root/.ssh/authorized_keys", + "/root/.bash_history", + } + } + + homeDir, _ := os.UserHomeDir() + if homeDir != "" { + sensitiveFiles = append(sensitiveFiles, + filepath.Join(homeDir, ".ssh", "id_rsa"), + filepath.Join(homeDir, ".ssh", "id_ed25519"), + filepath.Join(homeDir, ".aws", "credentials"), + filepath.Join(homeDir, ".azure", "accessTokens.json"), + filepath.Join(homeDir, ".kube", "config"), + ) + } + + for _, f := range sensitiveFiles { + if _, err := os.Stat(f); err == nil { + p.logSuccess("systeminfo_sensitive_file", f) + } + } + + if homeDir != "" { + p.searchSensitiveInDirs(homeDir) + } +} + +func (p *SystemInfoPlugin) searchSensitiveInDirs(homeDir string) { + searchDirs := []string{ + filepath.Join(homeDir, "Desktop"), + filepath.Join(homeDir, "Documents"), + filepath.Join(homeDir, ".ssh"), + filepath.Join(homeDir, ".aws"), + } + keywords := []string{"password", "key", "secret", "token", "credential", "passwd"} + + for _, dir := range searchDirs { + info, err := os.Stat(dir) + if err != nil || !info.IsDir() { + continue + } + _ = filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { + if err != nil || fi.IsDir() || fi.Size() > 1024*1024 { + return nil + } + name := strings.ToLower(filepath.Base(path)) + for _, kw := range keywords { + if strings.Contains(name, kw) { + p.logSuccess("systeminfo_sensitive_file", path) + break + } + } + return nil + }) + } +} + func (p *SystemInfoPlugin) collectSensitiveEnvVars() { keywords := []string{ "password", "passwd", "secret", "key", "token", diff --git a/plugins/local/systeminfo_dc_other.go b/plugins/local/systeminfo_dc_other.go new file mode 100644 index 0000000..95f8387 --- /dev/null +++ b/plugins/local/systeminfo_dc_other.go @@ -0,0 +1,5 @@ +//go:build (plugin_systeminfo || !plugin_selective) && !windows && !no_local + +package local + +func (p *SystemInfoPlugin) collectDomainInfo() {} diff --git a/plugins/local/systeminfo_dc_windows.go b/plugins/local/systeminfo_dc_windows.go new file mode 100644 index 0000000..8dcb55b --- /dev/null +++ b/plugins/local/systeminfo_dc_windows.go @@ -0,0 +1,232 @@ +//go:build (plugin_systeminfo || !plugin_selective) && windows && !no_local + +package local + +import ( + "fmt" + "net" + "os/exec" + "strings" + + "github.com/go-ldap/ldap/v3" + "github.com/go-ldap/ldap/v3/gssapi" + "github.com/shadow1ng/fscan/common" +) + +type domainInfo struct { + Domain string + BaseDN string + LDAPConn *ldap.Conn +} + +func (p *SystemInfoPlugin) collectDomainInfo() { + domain := p.detectDomain() + if domain == "" { + return + } + + p.logSuccess("systeminfo_dc_detected", domain) + + conn, err := p.connectToDomain(domain) + if err != nil { + p.log("systeminfo_dc_connect_failed", err.Error()) + return + } + defer func() { + if conn.LDAPConn != nil { + _ = conn.LDAPConn.Close() + } + }() + + p.log("systeminfo_dc_basedn", conn.BaseDN) + + p.queryDomainBasicInfo(conn) + p.queryDomainControllers(conn) + p.queryDomainAdmins(conn) + p.queryDomainUsers(conn) + p.queryDomainComputers(conn) + p.queryGroupPolicies(conn) +} + +func (p *SystemInfoPlugin) detectDomain() string { + // PowerShell + if out, err := exec.Command("powershell", "-Command", "(Get-WmiObject Win32_ComputerSystem).Domain").Output(); err == nil { + domain := strings.TrimSpace(string(out)) + if domain != "" && !strings.EqualFold(domain, "WORKGROUP") { + return domain + } + } + // wmic + if out, err := exec.Command("wmic", "computersystem", "get", "domain", "/value").Output(); err == nil { + for _, line := range strings.Split(string(out), "\n") { + if strings.HasPrefix(line, "Domain=") { + domain := strings.TrimSpace(strings.TrimPrefix(line, "Domain=")) + if domain != "" && !strings.EqualFold(domain, "WORKGROUP") { + return domain + } + } + } + } + return "" +} + +func (p *SystemInfoPlugin) connectToDomain(domain string) (*domainInfo, error) { + dcHost, err := p.findDC(domain) + if err != nil { + return nil, err + } + + client, err := gssapi.NewSSPIClient() + if err != nil { + return nil, fmt.Errorf("SSPI: %w", err) + } + defer func() { _ = client.Close() }() + + conn, err := ldap.DialURL(fmt.Sprintf("ldap://%s:389", dcHost)) + if err != nil { + if ipv4, resolveErr := resolveIPv4(dcHost); resolveErr == nil { + conn, err = ldap.DialURL(fmt.Sprintf("ldap://%s:389", ipv4)) + } + if err != nil { + return nil, fmt.Errorf("LDAP dial: %w", err) + } + } + + if err := conn.GSSAPIBind(client, fmt.Sprintf("ldap/%s", dcHost), ""); err != nil { + _ = conn.Close() + return nil, fmt.Errorf("GSSAPI bind: %w", err) + } + + baseDN, err := p.getBaseDN(conn, domain) + if err != nil { + _ = conn.Close() + return nil, err + } + + return &domainInfo{Domain: domain, BaseDN: baseDN, LDAPConn: conn}, nil +} + +func (p *SystemInfoPlugin) findDC(domain string) (string, error) { + if out, err := exec.Command("nslookup", "-type=SRV", fmt.Sprintf("_ldap._tcp.dc._msdcs.%s", domain)).Output(); err == nil { + for _, line := range strings.Split(string(out), "\n") { + if common.ContainsAny(line, "svr hostname", "service") { + parts := strings.Split(line, "=") + if len(parts) > 1 { + host := strings.TrimSpace(parts[len(parts)-1]) + host = strings.TrimSuffix(host, ".") + if host != "" { + return host, nil + } + } + } + } + } + if err := exec.Command("ping", "-n", "1", domain).Run(); err == nil { + return domain, nil + } + return "", fmt.Errorf("cannot find DC for %s", domain) +} + +func (p *SystemInfoPlugin) getBaseDN(conn *ldap.Conn, domain string) (string, error) { + sr, err := conn.Search(ldap.NewSearchRequest("", ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, "(objectClass=*)", []string{"defaultNamingContext"}, nil)) + if err == nil && len(sr.Entries) > 0 { + if dn := sr.Entries[0].GetAttributeValue("defaultNamingContext"); dn != "" { + return dn, nil + } + } + var parts []string + for _, p := range strings.Split(domain, ".") { + parts = append(parts, fmt.Sprintf("DC=%s", p)) + } + return strings.Join(parts, ","), nil +} + +func (p *SystemInfoPlugin) queryDomainBasicInfo(conn *domainInfo) { + sr, err := conn.LDAPConn.Search(ldap.NewSearchRequest(conn.BaseDN, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, "(objectClass=*)", []string{"whenCreated", "whenChanged", "msDS-Behavior-Version"}, nil)) + if err != nil { + return + } + if len(sr.Entries) > 0 { + e := sr.Entries[0] + if v := e.GetAttributeValue("whenCreated"); v != "" { + p.log("systeminfo_dc_created", v) + } + if v := e.GetAttributeValue("msDS-Behavior-Version"); v != "" { + p.log("systeminfo_dc_func_level", v) + } + } +} + +func (p *SystemInfoPlugin) queryDomainControllers(conn *domainInfo) { + sr, err := conn.LDAPConn.SearchWithPaging(ldap.NewSearchRequest(conn.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, "(&(objectClass=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))", []string{"cn", "dNSHostName", "operatingSystem"}, nil), 1000) + if err != nil { + return + } + p.logSuccess("systeminfo_dc_controllers", len(sr.Entries)) + for _, e := range sr.Entries { + p.log("systeminfo_dc_controller_detail", e.GetAttributeValue("cn"), e.GetAttributeValue("dNSHostName"), e.GetAttributeValue("operatingSystem")) + } +} + +func (p *SystemInfoPlugin) queryDomainAdmins(conn *domainInfo) { + sr, err := conn.LDAPConn.SearchWithPaging(ldap.NewSearchRequest(conn.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, "(&(objectCategory=group)(cn=Domain Admins))", []string{"member"}, nil), 1000) + if err != nil || len(sr.Entries) == 0 { + return + } + members := sr.Entries[0].GetAttributeValues("member") + p.logSuccess("systeminfo_dc_admins", len(members)) + for _, dn := range members { + userSr, err := conn.LDAPConn.Search(ldap.NewSearchRequest(dn, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, "(objectClass=*)", []string{"sAMAccountName", "displayName"}, nil)) + if err == nil && len(userSr.Entries) > 0 { + p.log("systeminfo_dc_admin_detail", userSr.Entries[0].GetAttributeValue("sAMAccountName"), userSr.Entries[0].GetAttributeValue("displayName")) + } + } +} + +func (p *SystemInfoPlugin) queryDomainUsers(conn *domainInfo) { + sr, err := conn.LDAPConn.SearchWithPaging(ldap.NewSearchRequest(conn.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, "(&(objectCategory=person)(objectClass=user))", []string{"sAMAccountName"}, nil), 0) + if err != nil { + return + } + p.log("systeminfo_dc_users", len(sr.Entries)) +} + +func (p *SystemInfoPlugin) queryDomainComputers(conn *domainInfo) { + sr, err := conn.LDAPConn.SearchWithPaging(ldap.NewSearchRequest(conn.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, "(&(objectClass=computer)(!userAccountControl:1.2.840.113556.1.4.803:=8192))", []string{"cn", "operatingSystem"}, nil), 0) + if err != nil { + return + } + p.log("systeminfo_dc_computers", len(sr.Entries)) + for _, e := range sr.Entries { + os := e.GetAttributeValue("operatingSystem") + if os != "" { + p.log("systeminfo_dc_computer_detail", e.GetAttributeValue("cn"), os) + } + } +} + +func (p *SystemInfoPlugin) queryGroupPolicies(conn *domainInfo) { + sr, err := conn.LDAPConn.SearchWithPaging(ldap.NewSearchRequest(conn.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, "(objectClass=groupPolicyContainer)", []string{"displayName", "cn"}, nil), 1000) + if err != nil { + return + } + p.log("systeminfo_dc_gpos", len(sr.Entries)) + for _, e := range sr.Entries { + if name := e.GetAttributeValue("displayName"); name != "" { + p.log("systeminfo_dc_gpo_detail", name, e.GetAttributeValue("cn")) + } + } +} + +func resolveIPv4(hostname string) (string, error) { + ips, err := net.LookupIP(hostname) + if err != nil { + return "", err + } + for _, ip := range ips { + if ip.To4() != nil { + return ip.String(), nil + } + } + return "", fmt.Errorf("no IPv4 found") +}