fscan/plugins/local/dcinfo/plugin.go
ZacharyZcR 4a3f281b6b refactor: 统一Plugins目录大小写为小写
- 将所有Plugins路径重命名为plugins
- 修复Git索引与实际文件系统大小写不一致问题
- 确保跨平台兼容性和路径一致性
2025-08-12 13:08:06 +08:00

1021 lines
31 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//go:build windows
package dcinfo
import (
"context"
"fmt"
"github.com/go-ldap/ldap/v3"
"github.com/go-ldap/ldap/v3/gssapi"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
"net"
"os/exec"
"strings"
)
// DCInfoPlugin 域控信息收集插件 - 使用简化架构
type DCInfoPlugin struct {
*local.BaseLocalPlugin
}
// DomainInfo 域信息结构
type DomainInfo struct {
Domain string
BaseDN string
LDAPConn *ldap.Conn
}
// NewDCInfoPlugin 创建域控信息收集插件 - 简化版本
func NewDCInfoPlugin() *DCInfoPlugin {
metadata := &base.PluginMetadata{
Name: "dcinfo",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows域控制器信息收集插件",
Category: "local",
Tags: []string{"local", "domain", "ldap", "windows"},
Protocols: []string{"local"},
}
plugin := &DCInfoPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
}
// 仅支持Windows平台
plugin.SetPlatformSupport([]string{"windows"})
// 不需要特殊权限,使用当前用户凭据
plugin.SetRequiresPrivileges(false)
return plugin
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *DCInfoPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// connectToDomain 连接到域控制器 - 简化版本
func (p *DCInfoPlugin) connectToDomain() (*DomainInfo, error) {
// 获取域控制器地址
dcHost, domain, err := p.getDomainController()
if err != nil {
return nil, fmt.Errorf("获取域控制器失败: %v", err)
}
// 建立LDAP连接
ldapConn, baseDN, err := p.connectToLDAP(dcHost, domain)
if err != nil {
return nil, fmt.Errorf("LDAP连接失败: %v", err)
}
return &DomainInfo{
Domain: domain,
BaseDN: baseDN,
LDAPConn: ldapConn,
}, nil
}
// getDomainController 获取域控制器地址
func (p *DCInfoPlugin) getDomainController() (string, string, error) {
common.LogDebug("开始查询域控制器地址...")
// 方法1: 尝试使用PowerShell获取域名
domain, err := p.getDomainNamePowerShell()
if err != nil {
// 方法2: 尝试使用wmic如果可用
domain, err = p.getDomainNameWmic()
if err != nil {
// 方法3: 尝试使用环境变量
domain, err = p.getDomainNameFromEnv()
if err != nil {
return "", "", fmt.Errorf("获取域名失败: %v", err)
}
}
}
if domain == "" || domain == "WORKGROUP" {
return "", "", fmt.Errorf("当前机器未加入域")
}
common.LogDebug(fmt.Sprintf("获取到域名: %s", domain))
// 查询域控制器
dcHost, err := p.findDomainController(domain)
if err != nil {
// 备选方案:使用域名直接构造
dcHost = fmt.Sprintf("dc.%s", domain)
common.LogBase(fmt.Sprintf("使用备选域控地址: %s", dcHost))
}
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) {
// 方法1: 使用nslookup查询SRV记录
cmd := exec.Command("nslookup", "-type=SRV", fmt.Sprintf("_ldap._tcp.dc._msdcs.%s", domain))
output, err := cmd.Output()
if err == nil {
// 解析nslookup输出
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.Contains(line, "svr hostname") || strings.Contains(line, "service") {
parts := strings.Split(line, "=")
if len(parts) > 1 {
dcHost := strings.TrimSpace(parts[len(parts)-1])
dcHost = strings.TrimSuffix(dcHost, ".")
if dcHost != "" {
common.LogSuccess(fmt.Sprintf("找到域控制器: %s", dcHost))
return dcHost, nil
}
}
}
}
}
// 方法2: 尝试直接ping域名
cmd = exec.Command("ping", "-n", "1", domain)
if err := cmd.Run(); err == nil {
common.LogSuccess(fmt.Sprintf("域控制器可能是: %s", domain))
return domain, nil
}
return "", fmt.Errorf("无法找到域控制器")
}
// connectToLDAP 连接到LDAP服务器
func (p *DCInfoPlugin) connectToLDAP(dcHost, domain string) (*ldap.Conn, string, error) {
common.LogDebug(fmt.Sprintf("尝试连接到LDAP服务器: %s", dcHost))
// 创建SSPI客户端
ldapClient, err := gssapi.NewSSPIClient()
if err != nil {
return nil, "", fmt.Errorf("创建SSPI客户端失败: %v", err)
}
defer ldapClient.Close()
// 尝试多种连接方式
var conn *ldap.Conn
var lastError error
// 方法1: 直接使用主机名连接
common.LogDebug(fmt.Sprintf("方法1: 直接连接 %s:389", dcHost))
conn, err = ldap.DialURL(fmt.Sprintf("ldap://%s:389", dcHost))
if err != nil {
common.LogDebug(fmt.Sprintf("方法1失败: %v", err))
lastError = err
// 方法2: 尝试使用IPv4地址
common.LogDebug("方法2: 解析IPv4地址")
ipv4, err := p.resolveIPv4(dcHost)
if err == nil {
common.LogDebug(fmt.Sprintf("解析到IPv4地址: %s", ipv4))
conn, err = ldap.DialURL(fmt.Sprintf("ldap://%s:389", ipv4))
if err != nil {
common.LogDebug(fmt.Sprintf("IPv4连接失败: %v", err))
lastError = err
}
} else {
common.LogDebug(fmt.Sprintf("IPv4解析失败: %v", err))
lastError = err
}
}
if conn == nil {
return nil, "", fmt.Errorf("LDAP连接失败: %v", lastError)
}
common.LogSuccess("LDAP连接建立成功")
// 使用GSSAPI进行绑定
common.LogDebug("开始GSSAPI认证...")
err = conn.GSSAPIBind(ldapClient, fmt.Sprintf("ldap/%s", dcHost), "")
if err != nil {
conn.Close()
return nil, "", fmt.Errorf("GSSAPI绑定失败: %v", err)
}
common.LogSuccess("GSSAPI认证成功")
// 获取BaseDN
common.LogDebug("获取BaseDN...")
baseDN, err := p.getBaseDN(conn, domain)
if err != nil {
conn.Close()
return nil, "", err
}
common.LogSuccess(fmt.Sprintf("BaseDN获取成功: %s", baseDN))
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失败: %v", 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
}
// 查找第一个IPv4地址
for _, ip := range ips {
if ip.To4() != nil {
return ip.String(), nil
}
}
return "", fmt.Errorf("未找到IPv4地址")
}
// ScanLocal 执行域控信息扫描 - 简化版本
func (p *DCInfoPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogInfo("开始域控制器信息收集...")
// 建立域控连接
domainConn, err := p.connectToDomain()
if err != nil {
// 提供更友好的错误信息
if strings.Contains(err.Error(), "未加入域") || strings.Contains(err.Error(), "WORKGROUP") {
common.LogError("当前计算机未加入域环境,无法执行域信息收集")
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("当前计算机未加入域环境"),
Extra: map[string]interface{}{"suggestion": "此插件需要在域环境中运行"},
}, nil
}
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("域控连接失败: %v", err),
}, nil
}
defer func() {
if domainConn.LDAPConn != nil {
domainConn.LDAPConn.Close()
}
}()
// 收集域信息
domainData := make(map[string]interface{})
// 获取域基本信息
if domainInfo, err := p.getDomainInfo(domainConn); err == nil {
domainData["domain_info"] = domainInfo
p.logDomainInfo(domainInfo)
}
// 获取域控制器详细信息
if domainControllers, err := p.getDomainControllers(domainConn); err == nil {
domainData["domain_controllers"] = domainControllers
p.logDomainControllers(domainControllers)
}
// 获取域用户(限制数量并显示详细信息)
if users, err := p.getDomainUsersDetailed(domainConn); err == nil {
domainData["domain_users"] = users
p.logDomainUsers(users)
} else {
common.LogError(fmt.Sprintf("获取域用户失败: %v", err))
}
// 获取域管理员详细信息
if admins, err := p.getDomainAdminsDetailed(domainConn); err == nil {
domainData["domain_admins"] = admins
p.logDomainAdmins(admins)
} else {
common.LogError(fmt.Sprintf("获取域管理员失败: %v", err))
}
// 获取计算机详细信息
if computers, err := p.getComputersDetailed(domainConn); err == nil {
domainData["computers"] = computers
p.logComputers(computers)
} else {
common.LogError(fmt.Sprintf("获取域计算机失败: %v", err))
}
// 获取组策略信息
common.LogDebug("开始获取组策略信息...")
if gpos, err := p.getGroupPolicies(domainConn); err == nil {
domainData["group_policies"] = gpos
p.logGroupPolicies(gpos)
common.LogDebug(fmt.Sprintf("成功获取 %d 个GPO", len(gpos)))
} else {
common.LogError(fmt.Sprintf("获取组策略失败: %v", err))
}
// 获取OU信息
common.LogDebug("开始获取组织单位信息...")
if ous, err := p.getOrganizationalUnits(domainConn); err == nil {
domainData["organizational_units"] = ous
p.logOrganizationalUnits(ous)
common.LogDebug(fmt.Sprintf("成功获取 %d 个OU", len(ous)))
} else {
common.LogError(fmt.Sprintf("获取组织单位失败: %v", err))
}
// 获取系统信息
systemInfo := p.GetSystemInfo()
result := &base.ScanResult{
Success: len(domainData) > 0,
Service: "DCInfo",
Banner: fmt.Sprintf("检测完成: 域: %s (BaseDN: %s)", domainConn.Domain, domainConn.BaseDN),
Extra: map[string]interface{}{
"domain_data": domainData,
"system_info": systemInfo,
},
}
common.LogSuccess("域控制器信息收集完成")
return result, nil
}
// 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")
// 分析用户状态
if uac := entry.GetAttributeValue("userAccountControl"); uac != "" {
user["account_status"] = p.parseUserAccountControl(uac)
}
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)
}
}
}
// 获取Enterprise Admins组
enterpriseAdminsRequest := ldap.NewSearchRequest(
conn.BaseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0, 0, false,
"(&(objectCategory=group)(cn=Enterprise Admins))",
[]string{"member"},
nil,
)
if enterpriseSr, err := conn.LDAPConn.Search(enterpriseAdminsRequest); err == nil && len(enterpriseSr.Entries) > 0 {
members := enterpriseSr.Entries[0].GetAttributeValues("member")
for _, memberDN := range members {
adminInfo, err := p.getUserInfoByDN(conn, memberDN)
if err == nil {
adminInfo["group_type"] = "Enterprise Admins"
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) {
common.LogDebug("开始搜索GPO...")
common.LogDebug(fmt.Sprintf("使用BaseDN: %s", conn.BaseDN))
var gpos []map[string]interface{}
// 直接使用objectClass=groupPolicyContainer查询这是最准确的方法
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 {
common.LogDebug(fmt.Sprintf("GPO搜索失败尝试SearchWithPaging: %v", err))
// 如果普通搜索失败,尝试分页搜索
sr, err = conn.LDAPConn.SearchWithPaging(searchRequest, 1000)
if err != nil {
common.LogDebug(fmt.Sprintf("GPO分页搜索也失败: %v", err))
return nil, err
}
}
common.LogDebug(fmt.Sprintf("GPO搜索找到 %d 个对象", len(sr.Entries)))
for _, entry := range sr.Entries {
classes := entry.GetAttributeValues("objectClass")
displayName := entry.GetAttributeValue("displayName")
dn := entry.GetAttributeValue("distinguishedName")
cn := entry.GetAttributeValue("cn")
common.LogDebug(fmt.Sprintf("检查GPO对象: %s, CN: %s, DN: %s", displayName, cn, dn))
common.LogDebug(fmt.Sprintf("对象类: %v", classes))
// 直接添加到结果中因为查询条件已经保证是GPO
gpo := make(map[string]interface{})
gpo["guid"] = cn
gpo["display_name"] = displayName
gpo["created"] = entry.GetAttributeValue("whenCreated")
gpo["modified"] = entry.GetAttributeValue("whenChanged")
gpo["file_sys_path"] = entry.GetAttributeValue("gPCFileSysPath")
gpo["dn"] = dn
gpos = append(gpos, gpo)
common.LogDebug(fmt.Sprintf("添加GPO: %s [%s]", displayName, cn))
}
common.LogDebug(fmt.Sprintf("最终找到 %d 个GPO", len(gpos)))
return gpos, nil
}
// getOrganizationalUnits 获取组织单位信息
func (p *DCInfoPlugin) getOrganizationalUnits(conn *DomainInfo) ([]map[string]interface{}, error) {
common.LogDebug("开始搜索OU和容器...")
common.LogDebug(fmt.Sprintf("使用BaseDN: %s", conn.BaseDN))
var ous []map[string]interface{}
// 方法1: 使用更广泛的查询来查找所有可能的OU/容器对象
allRequest := 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(allRequest, 100) // 限制100个以避免过多输出
if err != nil {
common.LogDebug(fmt.Sprintf("广泛搜索失败: %v", err))
} else {
common.LogDebug(fmt.Sprintf("广泛搜索找到 %d 个对象", len(sr.Entries)))
ouCount := 0
containerCount := 0
for _, entry := range sr.Entries {
objectClasses := entry.GetAttributeValues("objectClass")
dn := entry.GetAttributeValue("distinguishedName")
// 检查对象类型
isOU := false
isContainer := false
for _, class := range objectClasses {
if class == "organizationalUnit" {
isOU = true
ouCount++
} else if class == "container" {
isContainer = true
containerCount++
}
}
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.Contains(dn, "CN=WinsockServices") ||
strings.Contains(dn, "CN=RpcServices") ||
(strings.HasPrefix(dn, "CN=") && len(name) == 36 && strings.Count(name, "-") == 4) { // 跳过GUID格式的容器
continue
}
if name != "" {
common.LogDebug(fmt.Sprintf("找到%s: %s (DN: %s)",
map[bool]string{true: "OU", false: "容器"}[isOU], name, dn))
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)
}
}
common.LogDebug(fmt.Sprintf("统计: %d 个OU对象, %d 个容器对象", ouCount, containerCount))
}
common.LogDebug(fmt.Sprintf("最终找到 %d 个OU/容器", len(ous)))
return ous, nil
}
// parseUserAccountControl 解析用户账户控制
func (p *DCInfoPlugin) parseUserAccountControl(uac string) map[string]bool {
status := make(map[string]bool)
// 简化的UAC解析实际使用时可能需要更完整的实现
status["account_disabled"] = false
status["password_not_required"] = false
status["account_locked"] = false
status["password_never_expires"] = false
return status
}
// logDomainInfo 记录域基本信息
func (p *DCInfoPlugin) logDomainInfo(domainInfo map[string]interface{}) {
if domain, ok := domainInfo["domain"]; ok {
common.LogSuccess(fmt.Sprintf("域名: %v", domain))
}
if baseDN, ok := domainInfo["base_dn"]; ok {
common.LogSuccess(fmt.Sprintf("Base DN: %v", baseDN))
}
if created, ok := domainInfo["created"]; ok && created != "" {
common.LogBase(fmt.Sprintf("域创建时间: %v", created))
}
}
// logDomainControllers 记录域控制器信息
func (p *DCInfoPlugin) logDomainControllers(dcs []map[string]interface{}) {
if len(dcs) > 0 {
common.LogSuccess(fmt.Sprintf("发现 %d 个域控制器", len(dcs)))
for _, dc := range dcs {
if name, ok := dc["name"]; ok {
common.LogBase(fmt.Sprintf(" - %v (%v)", name, dc["dns_name"]))
if os, ok := dc["os"]; ok && os != "" {
common.LogBase(fmt.Sprintf(" 操作系统: %v", os))
}
}
}
}
}
// logDomainUsers 记录域用户信息
func (p *DCInfoPlugin) logDomainUsers(users []map[string]interface{}) {
if len(users) > 0 {
common.LogSuccess(fmt.Sprintf("发现 %d 个域用户", len(users)))
for _, user := range users {
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)
}
if created, ok := user["created"]; ok && created != "" {
displayInfo += fmt.Sprintf(" 创建时间: %v", created)
}
common.LogBase(displayInfo)
}
}
}
}
// logDomainAdmins 记录域管理员信息
func (p *DCInfoPlugin) logDomainAdmins(admins []map[string]interface{}) {
if len(admins) > 0 {
common.LogSuccess(fmt.Sprintf("发现 %d 个域管理员", 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)
}
if groupType, ok := admin["group_type"]; ok {
adminInfo += fmt.Sprintf(" 组: %v", groupType)
}
if created, ok := admin["created"]; ok && created != "" {
adminInfo += fmt.Sprintf(" 创建时间: %v", created)
}
if lastLogon, ok := admin["last_logon"]; ok && lastLogon != "" {
adminInfo += fmt.Sprintf(" 最后登录: %v", lastLogon)
}
common.LogBase(adminInfo)
}
}
}
}
// logComputers 记录计算机信息
func (p *DCInfoPlugin) logComputers(computers []map[string]interface{}) {
if len(computers) > 0 {
common.LogSuccess(fmt.Sprintf("发现 %d 台域计算机", len(computers)))
for _, computer := range computers {
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 osVersion, ok := computer["os_version"]; ok && osVersion != "" {
computerInfo += fmt.Sprintf(" %v", osVersion)
}
if dnsName, ok := computer["dns_name"]; ok && dnsName != "" {
computerInfo += fmt.Sprintf(" [%v]", dnsName)
}
if created, ok := computer["created"]; ok && created != "" {
computerInfo += fmt.Sprintf(" 创建时间: %v", created)
}
common.LogBase(computerInfo)
}
}
}
}
// logGroupPolicies 记录组策略信息
func (p *DCInfoPlugin) logGroupPolicies(gpos []map[string]interface{}) {
if len(gpos) > 0 {
common.LogSuccess(fmt.Sprintf("发现 %d 个组策略对象", 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)
}
if created, ok := gpo["created"]; ok && created != "" {
gpoInfo += fmt.Sprintf(" 创建时间: %v", created)
}
if modified, ok := gpo["modified"]; ok && modified != "" {
gpoInfo += fmt.Sprintf(" 修改时间: %v", modified)
}
common.LogBase(gpoInfo)
}
}
}
}
// logOrganizationalUnits 记录组织单位信息
func (p *DCInfoPlugin) logOrganizationalUnits(ous []map[string]interface{}) {
if len(ous) > 0 {
common.LogSuccess(fmt.Sprintf("发现 %d 个组织单位和容器", 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 && isOU.(bool) {
ouInfo += " [OU]"
} else if isContainer, ok := ou["is_container"]; ok && isContainer.(bool) {
ouInfo += " [Container]"
}
if desc, ok := ou["description"]; ok && desc != "" {
ouInfo += fmt.Sprintf(" 描述: %v", desc)
}
if created, ok := ou["created"]; ok && created != "" {
ouInfo += fmt.Sprintf(" 创建: %v", created)
}
if gpLink, ok := ou["gp_link"]; ok && gpLink != "" && gpLink != "" {
ouInfo += " [有GPO链接]"
}
common.LogBase(ouInfo)
}
}
}
}
// GetLocalData 获取域本地数据
func (p *DCInfoPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
data["plugin_type"] = "dcinfo"
data["requires_domain"] = true
return data, nil
}
// ExtractData 提取域数据
func (p *DCInfoPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: "域控信息收集完成",
Data: data,
}, nil
}
// RegisterDCInfoPlugin 注册域控信息收集插件
func RegisterDCInfoPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "dcinfo",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows域控制器信息收集插件",
Category: "local",
Tags: []string{"local", "domain", "ldap", "windows"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewDCInfoPlugin()
},
)
base.GlobalPluginRegistry.Register("dcinfo", factory)
}
// GetInfo 获取插件信息
func (p *DCInfoPlugin) GetInfo() string {
var info strings.Builder
info.WriteString("Windows域控制器信息收集插件\n")
info.WriteString(fmt.Sprintf("支持平台: %s\n", strings.Join(p.GetPlatformSupport(), ", ")))
info.WriteString("功能: 收集域用户、计算机、组策略、OU等信息\n")
info.WriteString("要求: 必须在域环境中运行\n")
return info.String()
}
// 插件注册函数
func init() {
RegisterDCInfoPlugin()
}