fscan/Plugins/DCInfo.go

858 lines
19 KiB
Go
Raw Normal View History

2024-12-28 05:43:22 +08:00
package Plugins
import (
"fmt"
"github.com/go-ldap/ldap/v3/gssapi"
"github.com/shadow1ng/fscan/Common"
"log"
"os/exec"
"strconv"
"strings"
"syscall"
"unsafe"
2024-12-28 05:43:22 +08:00
"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
}
2024-12-28 05:43:22 +08:00
func DCInfoScan(info *Common.HostInfo) (err error) {
if !IsInDomain() {
return fmt.Errorf("当前系统不在域环境中")
}
2024-12-28 05:43:22 +08:00
// 创建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, ",")
}