fscan/plugins/services/smtp.go
ZacharyZcR 6cf5719e8a refactor: 彻底清理插件系统,消除虚假利用功能
- 删除整个legacy插件系统(7794行代码)
- 完成所有插件向单文件架构迁移
- 移除19个插件的虚假Exploit功能,只保留真实利用:
  * Redis: 文件写入、SSH密钥注入、计划任务
  * SSH: 命令执行
  * MS17010: EternalBlue漏洞利用
- 统一插件接口,简化架构复杂度
- 清理临时文件和备份文件

重构效果:
- 代码行数: -7794行
- 插件文件数: 从3文件架构→单文件架构
- 真实利用插件: 从22个→3个
- 架构复杂度: 大幅简化
2025-08-26 11:43:48 +08:00

326 lines
7.7 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 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()
})
}