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"
|
2024-12-28 06:19:25 +08:00
|
|
|
|
"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
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-28 06:19:25 +08:00
|
|
|
|
// 检查是否在域环境中
|
|
|
|
|
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) {
|
2024-12-28 06:19:25 +08:00
|
|
|
|
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, ",")
|
|
|
|
|
}
|