From 532daf16ed6d5031a74f37a1896f3fcd3e213b70 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 9 Aug 2025 13:16:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20POP3=E9=82=AE=E4=BB=B6=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E6=8F=92=E4=BB=B6=E8=BF=81=E7=A7=BB=E5=88=B0=E6=96=B0?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现POP3协议连接器,支持普通和TLS连接(端口110/995) - 支持弱密码检测和邮件服务识别功能 - 简化实现,不提供利用功能,专注于安全扫描 - 集成国际化消息系统 - 测试通过:服务识别和弱密码检测功能 --- Plugins/services/pop3/connector.go | 269 +++++++++++++++++++++++++++++ Plugins/services/pop3/exploiter.go | 36 ++++ Plugins/services/pop3/plugin.go | 210 ++++++++++++++++++++++ 3 files changed, 515 insertions(+) create mode 100644 Plugins/services/pop3/connector.go create mode 100644 Plugins/services/pop3/exploiter.go create mode 100644 Plugins/services/pop3/plugin.go diff --git a/Plugins/services/pop3/connector.go b/Plugins/services/pop3/connector.go new file mode 100644 index 0000000..913edbe --- /dev/null +++ b/Plugins/services/pop3/connector.go @@ -0,0 +1,269 @@ +package pop3 + +import ( + "bufio" + "context" + "crypto/tls" + "fmt" + "net" + "strings" + "time" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" + "github.com/shadow1ng/fscan/plugins/base" +) + +// POP3Connector POP3连接器实现 +type POP3Connector struct { + host string + port int +} + +// POP3Connection POP3连接结构 +type POP3Connection struct { + conn net.Conn + reader *bufio.Reader + isTLS bool + banner string + username string + password string +} + +// NewPOP3Connector 创建POP3连接器 +func NewPOP3Connector() *POP3Connector { + return &POP3Connector{} +} + +// Connect 建立POP3连接 +func (c *POP3Connector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) { + timeout := time.Duration(common.Timeout) * time.Second + address := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + // 结果通道 + type connResult struct { + conn *POP3Connection + isTLS bool + banner string + err error + } + resultChan := make(chan connResult, 1) + + // 在协程中尝试连接 + go func() { + // 首先尝试普通连接 + conn, err := common.WrapperTcpWithTimeout("tcp", address, timeout) + if err == nil { + banner, authErr := c.readBanner(conn, timeout) + if authErr == nil { + pop3Conn := &POP3Connection{ + conn: conn, + reader: bufio.NewReader(conn), + isTLS: false, + banner: banner, + } + select { + case <-ctx.Done(): + conn.Close() + case resultChan <- connResult{pop3Conn, false, banner, nil}: + } + return + } + conn.Close() + } + + // 如果普通连接失败,尝试TLS连接(端口995) + if info.Ports == "995" { + tlsConfig := &tls.Config{ + InsecureSkipVerify: true, + } + dialer := &net.Dialer{Timeout: timeout} + tlsConn, tlsErr := tls.DialWithDialer(dialer, "tcp", address, tlsConfig) + if tlsErr == nil { + banner, authErr := c.readBanner(tlsConn, timeout) + if authErr == nil { + pop3Conn := &POP3Connection{ + conn: tlsConn, + reader: bufio.NewReader(tlsConn), + isTLS: true, + banner: banner, + } + select { + case <-ctx.Done(): + tlsConn.Close() + case resultChan <- connResult{pop3Conn, true, banner, nil}: + } + return + } + tlsConn.Close() + } + } + + select { + case <-ctx.Done(): + case resultChan <- connResult{nil, false, "", fmt.Errorf(i18n.GetText("pop3_connection_failed"), err)}: + } + }() + + // 等待连接结果 + select { + case result := <-resultChan: + if result.err != nil { + return nil, result.err + } + return result.conn, nil + case <-ctx.Done(): + return nil, ctx.Err() + } +} + +// Authenticate 进行POP3认证 +func (c *POP3Connector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error { + pop3Conn, ok := conn.(*POP3Connection) + if !ok { + return fmt.Errorf("无效的POP3连接类型") + } + + timeout := time.Duration(common.Timeout) * time.Second + + // 发送用户名 + pop3Conn.conn.SetDeadline(time.Now().Add(timeout)) + _, err := pop3Conn.conn.Write([]byte(fmt.Sprintf("USER %s\r\n", cred.Username))) + if err != nil { + return fmt.Errorf(i18n.GetText("pop3_auth_failed"), err) + } + + // 读取用户名响应 + pop3Conn.conn.SetDeadline(time.Now().Add(timeout)) + response, err := pop3Conn.reader.ReadString('\n') + if err != nil { + return fmt.Errorf(i18n.GetText("pop3_auth_failed"), err) + } + if !strings.Contains(response, "+OK") { + return fmt.Errorf("用户名无效: %s", strings.TrimSpace(response)) + } + + // 短暂延迟 + time.Sleep(300 * time.Millisecond) + + // 发送密码 + pop3Conn.conn.SetDeadline(time.Now().Add(timeout)) + _, err = pop3Conn.conn.Write([]byte(fmt.Sprintf("PASS %s\r\n", cred.Password))) + if err != nil { + return fmt.Errorf(i18n.GetText("pop3_auth_failed"), err) + } + + // 读取密码响应 + pop3Conn.conn.SetDeadline(time.Now().Add(timeout)) + response, err = pop3Conn.reader.ReadString('\n') + if err != nil { + return fmt.Errorf(i18n.GetText("pop3_auth_failed"), err) + } + + if strings.Contains(response, "+OK") { + // 认证成功,保存凭据信息 + pop3Conn.username = cred.Username + pop3Conn.password = cred.Password + return nil + } + + return fmt.Errorf("认证失败: %s", strings.TrimSpace(response)) +} + +// Close 关闭POP3连接 +func (c *POP3Connector) Close(conn interface{}) error { + if pop3Conn, ok := conn.(*POP3Connection); ok { + // 尝试优雅退出 + if pop3Conn.conn != nil { + pop3Conn.conn.Write([]byte("QUIT\r\n")) + pop3Conn.conn.Close() + } + return nil + } + return fmt.Errorf("无效的POP3连接类型") +} + +// readBanner 读取POP3服务器横幅信息 +func (c *POP3Connector) readBanner(conn net.Conn, timeout time.Duration) (string, error) { + reader := bufio.NewReader(conn) + conn.SetDeadline(time.Now().Add(timeout)) + + response, err := reader.ReadString('\n') + if err != nil { + return "", fmt.Errorf("读取横幅失败: %v", err) + } + + // 检查是否为正常的POP3响应 + if strings.Contains(response, "+OK") { + return strings.TrimSpace(response), nil + } + + // 检查错误响应 + if strings.Contains(strings.ToLower(response), "error") || + strings.Contains(strings.ToLower(response), "too many") { + return "", fmt.Errorf("服务器拒绝连接: %s", strings.TrimSpace(response)) + } + + return strings.TrimSpace(response), nil +} + +// GetConnectionInfo 获取连接信息 +func (conn *POP3Connection) GetConnectionInfo() map[string]interface{} { + info := map[string]interface{}{ + "protocol": "POP3", + "tls": conn.isTLS, + "banner": conn.banner, + } + + if conn.username != "" { + info["username"] = conn.username + info["authenticated"] = true + } + + return info +} + +// IsAlive 检查连接是否仍然有效 +func (conn *POP3Connection) IsAlive() bool { + if conn.conn == nil { + return false + } + + // 尝试发送NOOP命令检查连接 + conn.conn.SetDeadline(time.Now().Add(5 * time.Second)) + _, err := conn.conn.Write([]byte("NOOP\r\n")) + if err != nil { + return false + } + + response, err := conn.reader.ReadString('\n') + if err != nil { + return false + } + + return strings.Contains(response, "+OK") +} + +// ExecuteCommand 执行POP3命令 +func (conn *POP3Connection) ExecuteCommand(command string) (string, error) { + if conn.conn == nil { + return "", fmt.Errorf("连接未建立") + } + + timeout := time.Duration(common.Timeout) * time.Second + conn.conn.SetDeadline(time.Now().Add(timeout)) + + // 发送命令 + _, err := conn.conn.Write([]byte(command + "\r\n")) + if err != nil { + return "", fmt.Errorf("发送命令失败: %v", err) + } + + // 读取响应 + response, err := conn.reader.ReadString('\n') + if err != nil { + return "", fmt.Errorf("读取响应失败: %v", err) + } + + return strings.TrimSpace(response), nil +} \ No newline at end of file diff --git a/Plugins/services/pop3/exploiter.go b/Plugins/services/pop3/exploiter.go new file mode 100644 index 0000000..7a622ae --- /dev/null +++ b/Plugins/services/pop3/exploiter.go @@ -0,0 +1,36 @@ +package pop3 + +import ( + "context" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" +) + +// POP3Exploiter POP3利用器实现 - 最小化版本,不提供利用功能 +type POP3Exploiter struct { + *base.BaseExploiter +} + +// NewPOP3Exploiter 创建POP3利用器 +func NewPOP3Exploiter() *POP3Exploiter { + exploiter := &POP3Exploiter{ + BaseExploiter: base.NewBaseExploiter("pop3"), + } + + // POP3插件不提供利用功能 + exploiter.setupExploitMethods() + + return exploiter +} + +// setupExploitMethods 设置利用方法 +func (e *POP3Exploiter) setupExploitMethods() { + // POP3插件不提供利用功能,仅进行弱密码扫描和服务识别 +} + +// Exploit 利用接口实现 - 空实现 +func (e *POP3Exploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { + // POP3插件不提供利用功能 + return nil, nil +} \ No newline at end of file diff --git a/Plugins/services/pop3/plugin.go b/Plugins/services/pop3/plugin.go new file mode 100644 index 0000000..a93e1ed --- /dev/null +++ b/Plugins/services/pop3/plugin.go @@ -0,0 +1,210 @@ +package pop3 + +import ( + "context" + "fmt" + "strings" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" + "github.com/shadow1ng/fscan/plugins/base" +) + +// POP3Plugin POP3插件实现 +type POP3Plugin struct { + *base.ServicePlugin + exploiter *POP3Exploiter +} + +// NewPOP3Plugin 创建POP3插件 +func NewPOP3Plugin() *POP3Plugin { + // 插件元数据 + metadata := &base.PluginMetadata{ + Name: "pop3", + Version: "2.0.0", + Author: "fscan-team", + Description: "POP3邮件服务扫描和利用插件", + Category: "service", + Ports: []int{110, 995}, // POP3常用端口 + Protocols: []string{"tcp"}, + Tags: []string{"pop3", "email", "weak-password", "tls"}, + } + + // 创建连接器和服务插件 + connector := NewPOP3Connector() + servicePlugin := base.NewServicePlugin(metadata, connector) + + // 创建POP3插件 + plugin := &POP3Plugin{ + ServicePlugin: servicePlugin, + exploiter: NewPOP3Exploiter(), + } + + // 设置能力 + plugin.SetCapabilities([]base.Capability{ + base.CapWeakPassword, + }) + + return plugin +} + +// Scan 重写扫描方法,进行POP3服务扫描 +func (p *POP3Plugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + // 如果禁用了暴力破解,只进行服务识别 + if common.DisableBrute { + return p.performServiceIdentification(ctx, info) + } + + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + // 生成凭据进行暴力破解 + credentials := p.generateCredentials() + + // 遍历凭据进行测试 + for _, cred := range credentials { + result, err := p.ScanCredential(ctx, info, cred) + if err == nil && result.Success { + // 认证成功 + tlsStatus := "" + if result.Extra["tls"] == true { + tlsStatus = " (TLS)" + } + + common.LogSuccess(i18n.GetText("pop3_weak_pwd_success", target, cred.Username, cred.Password) + tlsStatus) + + return &base.ScanResult{ + Success: true, + Service: "POP3", + Credentials: []*base.Credential{cred}, + Banner: result.Banner, + Extra: map[string]interface{}{ + "service": "POP3", + "port": info.Ports, + "username": cred.Username, + "password": cred.Password, + "type": "weak-password", + "tls": result.Extra["tls"], + }, + }, nil + } + } + + // 所有凭据都失败,但可能识别到了POP3服务 + return p.performServiceIdentification(ctx, info) +} + +// generateCredentials 生成POP3凭据 +func (p *POP3Plugin) generateCredentials() []*base.Credential { + var credentials []*base.Credential + + // 获取POP3用户名字典 + usernames := common.Userdict["pop3"] + if len(usernames) == 0 { + usernames = []string{"admin", "root", "test", "mail", "postmaster", "user"} + } + + // 获取密码字典 + passwords := common.Passwords + if len(passwords) == 0 { + passwords = []string{"", "admin", "password", "123456", "admin123", "test123", "root123"} + } + + // 生成用户名密码组合 + for _, username := range usernames { + for _, password := range passwords { + // 替换密码中的用户名占位符 + actualPassword := strings.Replace(password, "{user}", username, -1) + + credentials = append(credentials, &base.Credential{ + Username: username, + Password: actualPassword, + }) + } + } + + return credentials +} + +// Exploit 使用exploiter执行利用 +func (p *POP3Plugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { + return p.exploiter.Exploit(ctx, info, creds) +} + +// GetExploitMethods 获取利用方法 +func (p *POP3Plugin) GetExploitMethods() []base.ExploitMethod { + return p.exploiter.GetExploitMethods() +} + +// IsExploitSupported 检查利用支持 +func (p *POP3Plugin) IsExploitSupported(method base.ExploitType) bool { + return p.exploiter.IsExploitSupported(method) +} + +// performServiceIdentification 执行POP3服务识别(-nobr模式) +func (p *POP3Plugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + // 尝试识别POP3服务 + connector := NewPOP3Connector() + conn, err := connector.Connect(ctx, info) + + if err == nil && conn != nil { + if pop3Conn, ok := conn.(*POP3Connection); ok { + // 记录服务识别成功 + tlsStatus := "" + if pop3Conn.isTLS { + tlsStatus = " (TLS)" + } + common.LogSuccess(i18n.GetText("pop3_service_identified", target, pop3Conn.banner) + tlsStatus) + + connector.Close(conn) + return &base.ScanResult{ + Success: true, + Service: "POP3", + Banner: pop3Conn.banner, + Extra: map[string]interface{}{ + "service": "POP3", + "port": info.Ports, + "banner": pop3Conn.banner, + "tls": pop3Conn.isTLS, + }, + }, nil + } + } + + // 如果无法识别为POP3,返回失败 + return &base.ScanResult{ + Success: false, + Error: fmt.Errorf("无法识别为POP3服务"), + }, nil +} + +// ============================================================================= +// 插件注册 +// ============================================================================= + +// RegisterPOP3Plugin 注册POP3插件 +func RegisterPOP3Plugin() { + factory := base.NewSimplePluginFactory( + &base.PluginMetadata{ + Name: "pop3", + Version: "2.0.0", + Author: "fscan-team", + Description: "POP3邮件服务扫描和利用插件", + Category: "service", + Ports: []int{110, 995}, // POP3常用端口 + Protocols: []string{"tcp"}, + Tags: []string{"pop3", "email", "weak-password", "tls"}, + }, + func() base.Plugin { + return NewPOP3Plugin() + }, + ) + + base.GlobalPluginRegistry.Register("pop3", factory) +} + +// 自动注册 +func init() { + RegisterPOP3Plugin() +} \ No newline at end of file