fscan/plugins/services/ldap.go
ZacharyZcR e082e2bb59 refactor: 重组插件目录结构,提升管理直观性
将所有服务插件移动到plugins/services/目录下,使目录结构更加清晰直观:
• 创建plugins/services/目录统一管理服务扫描插件
• 添加init.go提供类型别名和函数导出
• 更新main.go导入路径
• 所有20个服务插件功能验证正常

新的目录结构更便于插件管理和维护。
2025-08-26 00:02:13 +08:00

472 lines
12 KiB
Go

package services
import (
"context"
"fmt"
"strings"
ldaplib "github.com/go-ldap/ldap/v3"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// LDAPPlugin LDAP轻量级目录访问协议扫描和利用插件 - 包含目录信息收集利用功能
type LDAPPlugin struct {
name string
ports []int
}
// NewLDAPPlugin 创建LDAP插件
func NewLDAPPlugin() *LDAPPlugin {
return &LDAPPlugin{
name: "ldap",
ports: []int{389, 636, 3268, 3269}, // 389: LDAP, 636: LDAPS, 3268/3269: Global Catalog
}
}
// GetName 实现Plugin接口
func (p *LDAPPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *LDAPPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行LDAP扫描 - 匿名绑定检测和弱密码检测
func (p *LDAPPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// 首先测试匿名绑定
if result := p.testAnonymousBind(ctx, info); result != nil && result.Success {
common.LogSuccess(i18n.GetText("ldap_anonymous_success", target))
return result
}
// 生成测试凭据
credentials := GenerateCredentials("ldap")
if len(credentials) == 0 {
// LDAP默认凭据
credentials = []Credential{
{Username: "admin", Password: "admin"},
{Username: "administrator", Password: "administrator"},
{Username: "admin", Password: "password"},
{Username: "admin", Password: "123456"},
{Username: "ldap", Password: "ldap"},
{Username: "manager", Password: "manager"},
{Username: "root", Password: "root"},
{Username: "bind", Password: "bind"},
{Username: "guest", Password: "guest"},
{Username: "test", Password: "test"},
}
}
// 逐个测试凭据
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "ldap",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if p.testCredential(ctx, info, cred) {
// LDAP认证成功
common.LogSuccess(i18n.GetText("ldap_scan_success", target, cred.Username, cred.Password))
return &ScanResult{
Success: true,
Service: "ldap",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "ldap",
Error: fmt.Errorf("未发现弱密码或匿名访问"),
}
}
// Exploit 执行LDAP利用操作 - 实现目录信息收集功能
func (p *LDAPPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogSuccess(fmt.Sprintf("LDAP利用开始: %s", target))
var output strings.Builder
output.WriteString(fmt.Sprintf("=== LDAP利用结果 - %s ===\n", target))
if creds.Username != "" {
output.WriteString(fmt.Sprintf("认证凭据: %s/%s\n", creds.Username, creds.Password))
} else {
output.WriteString("认证方式: 匿名绑定\n")
}
// 建立LDAP连接
conn, err := p.connectLDAP(ctx, info, creds)
if err != nil {
output.WriteString(fmt.Sprintf("\n[连接失败] %v\n", err))
return &ExploitResult{
Success: false,
Output: output.String(),
Error: err,
}
}
defer conn.Close()
output.WriteString("\n[连接状态] ✅ 成功建立LDAP连接\n")
// 获取根DSE信息
if rootDSE := p.getRootDSE(conn); rootDSE != "" {
output.WriteString(fmt.Sprintf("\n[根DSE信息]\n%s\n", rootDSE))
}
// 获取命名上下文
if namingContexts := p.getNamingContexts(conn); len(namingContexts) > 0 {
output.WriteString(fmt.Sprintf("\n[命名上下文] (共%d个)\n", len(namingContexts)))
for _, context := range namingContexts {
output.WriteString(fmt.Sprintf(" %s\n", context))
}
}
// 枚举用户
if users := p.enumerateUsers(conn); len(users) > 0 {
output.WriteString(fmt.Sprintf("\n[用户枚举] (共%d个)\n", len(users)))
for i, user := range users {
if i >= 20 { // 限制显示前20个用户
output.WriteString("... (更多用户)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", user))
}
}
// 枚举组织单位
if ous := p.enumerateOUs(conn); len(ous) > 0 {
output.WriteString(fmt.Sprintf("\n[组织单位] (共%d个)\n", len(ous)))
for i, ou := range ous {
if i >= 15 { // 限制显示前15个OU
output.WriteString("... (更多组织单位)\n")
break
}
output.WriteString(fmt.Sprintf(" %s\n", ou))
}
}
common.LogSuccess(fmt.Sprintf("LDAP利用完成: %s", target))
return &ExploitResult{
Success: true,
Output: output.String(),
}
}
// testAnonymousBind 测试匿名绑定
func (p *LDAPPlugin) testAnonymousBind(ctx context.Context, info *common.HostInfo) *ScanResult {
conn, err := p.connectLDAP(ctx, info, Credential{})
if err != nil {
return nil
}
defer conn.Close()
// 尝试匿名绑定
if err := conn.UnauthenticatedBind(""); err != nil {
return nil
}
return &ScanResult{
Success: true,
Service: "ldap",
Banner: "匿名访问",
}
}
// testCredential 测试单个凭据
func (p *LDAPPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
conn, err := p.connectLDAP(ctx, info, cred)
if err != nil {
return false
}
defer conn.Close()
// 尝试绑定
bindDNs := p.generateBindDNs(cred.Username)
for _, bindDN := range bindDNs {
if err := conn.Bind(bindDN, cred.Password); err == nil {
return true
}
}
return false
}
// connectLDAP 连接到LDAP服务
func (p *LDAPPlugin) connectLDAP(ctx context.Context, info *common.HostInfo, creds Credential) (*ldaplib.Conn, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 根据端口选择连接方式
var conn *ldaplib.Conn
var err error
if info.Ports == "636" { // LDAPS
conn, err = ldaplib.DialTLS("tcp", target, nil)
} else {
conn, err = ldaplib.Dial("tcp", target)
}
if err != nil {
return nil, err
}
return conn, nil
}
// generateBindDNs 生成绑定DN列表
func (p *LDAPPlugin) generateBindDNs(username string) []string {
return []string{
fmt.Sprintf("cn=%s,dc=example,dc=com", username),
fmt.Sprintf("cn=%s,ou=users,dc=example,dc=com", username),
fmt.Sprintf("uid=%s,ou=users,dc=example,dc=com", username),
fmt.Sprintf("uid=%s,dc=example,dc=com", username),
fmt.Sprintf("cn=%s,cn=users,dc=example,dc=com", username),
fmt.Sprintf("sAMAccountName=%s", username), // Active Directory
username, // 直接使用用户名作为DN
}
}
// getRootDSE 获取根DSE信息
func (p *LDAPPlugin) getRootDSE(conn *ldaplib.Conn) string {
searchRequest := ldaplib.NewSearchRequest(
"", // 根DSE
ldaplib.ScopeBaseObject,
ldaplib.NeverDerefAliases,
0, 0, false,
"(objectClass=*)",
[]string{"*", "+"}, // 请求所有属性
nil,
)
sr, err := conn.Search(searchRequest)
if err != nil || len(sr.Entries) == 0 {
return "无法获取根DSE信息"
}
var info strings.Builder
entry := sr.Entries[0]
// 显示重要属性
importantAttrs := []string{
"vendorName", "vendorVersion", "serverName",
"supportedLDAPVersion", "namingContexts",
"defaultNamingContext", "schemaNamingContext",
"configurationNamingContext",
}
for _, attr := range importantAttrs {
if values := entry.GetAttributeValues(attr); len(values) > 0 {
info.WriteString(fmt.Sprintf("%s: %s\n", attr, strings.Join(values, ", ")))
}
}
return info.String()
}
// getNamingContexts 获取命名上下文
func (p *LDAPPlugin) getNamingContexts(conn *ldaplib.Conn) []string {
searchRequest := ldaplib.NewSearchRequest(
"", // 根DSE
ldaplib.ScopeBaseObject,
ldaplib.NeverDerefAliases,
0, 0, false,
"(objectClass=*)",
[]string{"namingContexts"},
nil,
)
sr, err := conn.Search(searchRequest)
if err != nil || len(sr.Entries) == 0 {
return nil
}
return sr.Entries[0].GetAttributeValues("namingContexts")
}
// enumerateUsers 枚举用户
func (p *LDAPPlugin) enumerateUsers(conn *ldaplib.Conn) []string {
// 尝试常见的用户搜索基DN
searchBases := []string{
"dc=example,dc=com",
"ou=users,dc=example,dc=com",
"cn=users,dc=example,dc=com",
"",
}
var users []string
for _, baseDN := range searchBases {
searchRequest := ldaplib.NewSearchRequest(
baseDN,
ldaplib.ScopeWholeSubtree,
ldaplib.NeverDerefAliases,
50, 0, false, // 限制返回50个结果
"(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))",
[]string{"cn", "uid", "sAMAccountName", "mail", "displayName"},
nil,
)
sr, err := conn.Search(searchRequest)
if err != nil {
continue
}
for _, entry := range sr.Entries {
var userInfo strings.Builder
userInfo.WriteString(fmt.Sprintf("DN: %s", entry.DN))
if cn := entry.GetAttributeValue("cn"); cn != "" {
userInfo.WriteString(fmt.Sprintf(", CN: %s", cn))
}
if uid := entry.GetAttributeValue("uid"); uid != "" {
userInfo.WriteString(fmt.Sprintf(", UID: %s", uid))
}
if sam := entry.GetAttributeValue("sAMAccountName"); sam != "" {
userInfo.WriteString(fmt.Sprintf(", SAM: %s", sam))
}
if mail := entry.GetAttributeValue("mail"); mail != "" {
userInfo.WriteString(fmt.Sprintf(", Mail: %s", mail))
}
users = append(users, userInfo.String())
}
if len(users) > 0 {
break // 找到用户就停止搜索其他基DN
}
}
return users
}
// enumerateOUs 枚举组织单位
func (p *LDAPPlugin) enumerateOUs(conn *ldaplib.Conn) []string {
searchBases := []string{
"dc=example,dc=com",
"",
}
var ous []string
for _, baseDN := range searchBases {
searchRequest := ldaplib.NewSearchRequest(
baseDN,
ldaplib.ScopeWholeSubtree,
ldaplib.NeverDerefAliases,
30, 0, false, // 限制返回30个结果
"(objectClass=organizationalUnit)",
[]string{"ou", "description"},
nil,
)
sr, err := conn.Search(searchRequest)
if err != nil {
continue
}
for _, entry := range sr.Entries {
var ouInfo strings.Builder
ouInfo.WriteString(fmt.Sprintf("DN: %s", entry.DN))
if ou := entry.GetAttributeValue("ou"); ou != "" {
ouInfo.WriteString(fmt.Sprintf(", OU: %s", ou))
}
if desc := entry.GetAttributeValue("description"); desc != "" {
ouInfo.WriteString(fmt.Sprintf(", Desc: %s", desc))
}
ous = append(ous, ouInfo.String())
}
if len(ous) > 0 {
break // 找到OU就停止搜索其他基DN
}
}
return ous
}
// identifyService 服务识别 - 检测LDAP服务
func (p *LDAPPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
conn, err := p.connectLDAP(ctx, info, Credential{})
if err != nil {
return &ScanResult{
Success: false,
Service: "ldap",
Error: err,
}
}
defer conn.Close()
// 尝试获取根DSE信息来确认这是LDAP服务
searchRequest := ldaplib.NewSearchRequest(
"", // 根DSE
ldaplib.ScopeBaseObject,
ldaplib.NeverDerefAliases,
0, 0, false,
"(objectClass=*)",
[]string{"vendorName", "vendorVersion"},
nil,
)
sr, err := conn.Search(searchRequest)
var banner string
if err != nil {
banner = "LDAP目录服务"
} else if len(sr.Entries) > 0 {
entry := sr.Entries[0]
vendor := entry.GetAttributeValue("vendorName")
version := entry.GetAttributeValue("vendorVersion")
if vendor != "" && version != "" {
banner = fmt.Sprintf("LDAP目录服务 (%s %s)", vendor, version)
} else if vendor != "" {
banner = fmt.Sprintf("LDAP目录服务 (%s)", vendor)
} else {
banner = "LDAP目录服务"
}
} else {
banner = "LDAP目录服务"
}
common.LogSuccess(i18n.GetText("ldap_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "ldap",
Banner: banner,
}
}
// init 自动注册插件
func init() {
RegisterPlugin("ldap", func() Plugin {
return NewLDAPPlugin()
})
}