fscan/Plugins/DCInfo.go

858 lines
19 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.

package Plugins
import (
"fmt"
"github.com/go-ldap/ldap/v3/gssapi"
"github.com/shadow1ng/fscan/Common"
"log"
"os/exec"
"strconv"
"strings"
"syscall"
"unsafe"
"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 IsInDomain() bool {
// 获取计算机域成员身份信息
var joinStatus uint32
var buffer uint32
ret, _, _ := syscall.NewLazyDLL("netapi32.dll").NewProc("NetGetJoinInformation").Call(
0,
uintptr(unsafe.Pointer(&joinStatus)),
uintptr(unsafe.Pointer(&buffer)),
)
if ret == 0 {
// 清理资源
syscall.NewLazyDLL("netapi32.dll").NewProc("NetApiBufferFree").Call(uintptr(buffer))
// 检查是否为域成员
return joinStatus == 3 // 3 = NetSetupDomainName 表示是域成员
}
return false
}
func DCInfoScan(info *Common.HostInfo) (err error) {
if !IsInDomain() {
return fmt.Errorf("当前系统不在域环境中")
}
// 创建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, ",")
}