Files
fscan/plugins/local/systeminfo_dc_windows.go
ZacharyZcR 5ad914a1bb feat: 统一服务缓存 + 指纹驱动插件匹配
将 webServiceCache 扩展为通用 serviceCache,所有指纹识别结果
统一缓存,插件匹配时端口不命中则回退到服务名称匹配。

删除多余的 service_cache.go,复用已有的 ServiceInfo 体系。
补充 nil 防御、Explicit 标记、大量单元/集成/回归测试。
2026-06-14 22:23:47 +08:00

233 lines
7.4 KiB
Go

//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(ldapURL(dcHost, 389))
if err != nil {
if ipv4, resolveErr := resolveIPv4(dcHost); resolveErr == nil {
conn, err = ldap.DialURL(ldapURL(ipv4, 389))
}
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")
}