package services import ( "context" "crypto/tls" "fmt" "net" "net/smtp" "strings" "time" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common/i18n" ) // SMTPPlugin SMTP邮件服务扫描和利用插件 - 包含用户枚举利用功能 type SMTPPlugin struct { name string ports []int } // NewSMTPPlugin 创建SMTP插件 func NewSMTPPlugin() *SMTPPlugin { return &SMTPPlugin{ name: "smtp", ports: []int{25, 465, 587, 2525}, // SMTP端口 } } // GetName 实现Plugin接口 func (p *SMTPPlugin) GetName() string { return p.name } // GetPorts 实现Plugin接口 func (p *SMTPPlugin) GetPorts() []int { return p.ports } // Scan 执行SMTP扫描 - 弱密码检测和开放中继检测 func (p *SMTPPlugin) 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.testOpenRelay(ctx, info); result != nil && result.Success { common.LogSuccess(i18n.GetText("smtp_open_relay_success", target)) return result } // 生成测试凭据 credentials := GenerateCredentials("smtp") if len(credentials) == 0 { // SMTP默认凭据 credentials = []Credential{ {Username: "admin", Password: "admin"}, {Username: "admin", Password: "password"}, {Username: "admin", Password: "123456"}, {Username: "test", Password: "test"}, {Username: "user", Password: "user"}, {Username: "mail", Password: "mail"}, {Username: "postmaster", Password: "postmaster"}, } } // 逐个测试凭据 for _, cred := range credentials { // 检查Context是否被取消 select { case <-ctx.Done(): return &ScanResult{ Success: false, Service: "smtp", Error: ctx.Err(), } default: } // 测试凭据 if p.testCredential(ctx, info, cred) { // SMTP认证成功 common.LogSuccess(i18n.GetText("smtp_scan_success", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, Service: "smtp", Username: cred.Username, Password: cred.Password, } } } // 所有凭据都失败 return &ScanResult{ Success: false, Service: "smtp", Error: fmt.Errorf("未发现弱密码或开放中继"), } } // testOpenRelay 测试开放中继 func (p *SMTPPlugin) testOpenRelay(ctx context.Context, info *common.HostInfo) *ScanResult { if p.checkOpenRelay(ctx, info) != "" { return &ScanResult{ Success: true, Service: "smtp", Banner: "开放中继", } } return nil } // testCredential 测试单个凭据 func (p *SMTPPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) // 根据端口选择连接方式 var client *smtp.Client var err error if info.Ports == "465" { // SMTPS conn, err := tls.Dial("tcp", target, &tls.Config{InsecureSkipVerify: true}) if err != nil { return false } defer conn.Close() client, err = smtp.NewClient(conn, info.Host) if err != nil { return false } } else { client, err = smtp.Dial(target) if err != nil { return false } } defer client.Quit() // 尝试STARTTLS if ok, _ := client.Extension("STARTTLS"); ok { if err := client.StartTLS(&tls.Config{InsecureSkipVerify: true}); err != nil { // STARTTLS失败,继续明文认证 } } // 尝试认证 auth := smtp.PlainAuth("", cred.Username, cred.Password, info.Host) if err := client.Auth(auth); err != nil { return false } return true } // getServerInfo 获取服务器信息 func (p *SMTPPlugin) getServerInfo(ctx context.Context, info *common.HostInfo) string { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) if err != nil { return "" } defer conn.Close() // 读取欢迎消息 conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) buffer := make([]byte, 1024) n, err := conn.Read(buffer) if err != nil { return "" } welcome := strings.TrimSpace(string(buffer[:n])) if strings.HasPrefix(welcome, "220") { return strings.TrimPrefix(welcome, "220 ") } return welcome } // getExtensions 获取SMTP扩展 func (p *SMTPPlugin) getExtensions(ctx context.Context, info *common.HostInfo) []string { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) client, err := smtp.Dial(target) if err != nil { return nil } defer client.Quit() // 发送EHLO获取扩展 if err := client.Hello("fscan.test"); err != nil { return nil } var extensions []string if exts, _ := client.Extension("AUTH"); exts { extensions = append(extensions, "AUTH (认证支持)") } if exts, _ := client.Extension("STARTTLS"); exts { extensions = append(extensions, "STARTTLS (TLS支持)") } if exts, _ := client.Extension("SIZE"); exts { extensions = append(extensions, "SIZE (邮件大小限制)") } if exts, _ := client.Extension("PIPELINING"); exts { extensions = append(extensions, "PIPELINING (管道支持)") } if exts, _ := client.Extension("8BITMIME"); exts { extensions = append(extensions, "8BITMIME (8位MIME)") } return extensions } // enumerateUsers 枚举用户 func (p *SMTPPlugin) enumerateUsers(ctx context.Context, info *common.HostInfo) []string { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) client, err := smtp.Dial(target) if err != nil { return nil } defer client.Quit() if err := client.Hello("fscan.test"); err != nil { return nil } // 常见用户名列表 commonUsers := []string{"admin", "administrator", "root", "user", "test", "mail", "postmaster", "webmaster", "info", "support", "sales", "marketing"} var validUsers []string for _, user := range commonUsers { // 使用VRFY命令验证用户 if err := p.sendRawCommand(client, fmt.Sprintf("VRFY %s", user)); err == nil { validUsers = append(validUsers, user) } // 限制数量 if len(validUsers) >= 10 { break } } return validUsers } // sendRawCommand 发送原始SMTP命令 func (p *SMTPPlugin) sendRawCommand(client *smtp.Client, command string) error { // 这里简化实现,实际需要直接操作连接 // smtp包没有直接的原始命令接口 return fmt.Errorf("VRFY command not supported") } // checkOpenRelay 检查开放中继 func (p *SMTPPlugin) checkOpenRelay(ctx context.Context, info *common.HostInfo) string { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) client, err := smtp.Dial(target) if err != nil { return "" } defer client.Quit() if err := client.Hello("fscan.test"); err != nil { return "" } // 尝试发送外部邮件测试开放中继 if err := client.Mail("test@fscan.test"); err != nil { return "❌ 邮件发送测试失败" } if err := client.Rcpt("external@example.com"); err != nil { return "❌ 不是开放中继" } return "⚠️ 可能存在开放中继风险" } // identifyService 服务识别 - 检测SMTP服务 func (p *SMTPPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) serverInfo := p.getServerInfo(ctx, info) var banner string if serverInfo != "" { banner = fmt.Sprintf("SMTP邮件服务 (%s)", serverInfo) } else { // 尝试简单连接测试 conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) if err != nil { return &ScanResult{ Success: false, Service: "smtp", Error: err, } } defer conn.Close() banner = "SMTP邮件服务" } common.LogSuccess(i18n.GetText("smtp_service_identified", target, banner)) return &ScanResult{ Success: true, Service: "smtp", Banner: banner, } } // init 自动注册插件 func init() { RegisterPlugin("smtp", func() Plugin { return NewSMTPPlugin() }) }