diff --git a/pkg/fscan/scanner.go b/pkg/fscan/scanner.go index fe0b5c0..8e144e9 100644 --- a/pkg/fscan/scanner.go +++ b/pkg/fscan/scanner.go @@ -39,7 +39,9 @@ var defaultSafePlugins = []string{ "rabbitmq", "rdp", "redis", + "imap", "jdwp", + "pop3", "rsync", "smb", "smtp", @@ -537,6 +539,7 @@ var serviceAuthPlugins = map[string]bool{ "cassandra": true, "elasticsearch": true, "ftp": true, + "imap": true, "kafka": true, "ldap": true, "memcached": true, @@ -545,6 +548,7 @@ var serviceAuthPlugins = map[string]bool{ "mysql": true, "neo4j": true, "oracle": true, + "pop3": true, "postgresql": true, "rabbitmq": true, "redis": true, diff --git a/plugins/services/imap.go b/plugins/services/imap.go new file mode 100644 index 0000000..bbf212b --- /dev/null +++ b/plugins/services/imap.go @@ -0,0 +1,118 @@ +//go:build plugin_imap || !plugin_selective + +package services + +import ( + "bufio" + "context" + "fmt" + "strings" + "time" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins" +) + +type IMAPPlugin struct { + plugins.BasePlugin +} + +func NewIMAPPlugin() *IMAPPlugin { + return &IMAPPlugin{BasePlugin: plugins.NewBasePlugin("imap")} +} + +func (p *IMAPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { + config := session.Config + timeout := config.Timeout + if timeout <= 0 { + timeout = 3 * time.Second + } + + addr := fmt.Sprintf("%s:%d", info.Host, info.Port) + conn, err := session.DialTCP(ctx, "tcp", addr, timeout) + if err != nil { + return &ScanResult{Success: false, Service: "imap"} + } + defer conn.Close() + _ = conn.SetDeadline(time.Now().Add(timeout)) + + reader := bufio.NewReader(conn) + banner, err := reader.ReadString('\n') + if err != nil || !strings.Contains(banner, "OK") { + return &ScanResult{Success: false, Service: "imap"} + } + banner = strings.TrimSpace(banner) + + serviceResult := &ScanResult{ + Success: true, + Type: plugins.ResultTypeService, + Service: "imap", + Banner: banner, + } + + if config.DisableBrute { + return serviceResult + } + + credentials := GenerateCredentials("imap", config) + if len(credentials) == 0 { + return serviceResult + } + + for _, cred := range credentials { + select { + case <-ctx.Done(): + return serviceResult + default: + } + + if result := p.tryLogin(ctx, info, cred, timeout, session); result != nil { + return result + } + } + + return serviceResult +} + +func (p *IMAPPlugin) tryLogin(ctx context.Context, info *common.HostInfo, cred plugins.Credential, timeout time.Duration, session *common.ScanSession) *ScanResult { + addr := fmt.Sprintf("%s:%d", info.Host, info.Port) + conn, err := session.DialTCP(ctx, "tcp", addr, timeout) + if err != nil { + return nil + } + defer conn.Close() + _ = conn.SetDeadline(time.Now().Add(timeout)) + + reader := bufio.NewReader(conn) + if _, err := reader.ReadString('\n'); err != nil { + return nil + } + + loginCmd := fmt.Sprintf("a001 LOGIN %s %s\r\n", cred.Username, cred.Password) + if _, err := conn.Write([]byte(loginCmd)); err != nil { + return nil + } + + response, err := reader.ReadString('\n') + if err != nil { + return nil + } + + if strings.Contains(response, "a001 OK") { + _, _ = conn.Write([]byte("a002 LOGOUT\r\n")) + return &ScanResult{ + Success: true, + Type: plugins.ResultTypeCredential, + Service: "imap", + Username: cred.Username, + Password: cred.Password, + } + } + return nil +} + +func init() { + RegisterPluginWithPorts("imap", func() Plugin { + return NewIMAPPlugin() + }, []int{143, 993}) +} diff --git a/plugins/services/pop3.go b/plugins/services/pop3.go new file mode 100644 index 0000000..67867e4 --- /dev/null +++ b/plugins/services/pop3.go @@ -0,0 +1,124 @@ +//go:build plugin_pop3 || !plugin_selective + +package services + +import ( + "bufio" + "context" + "fmt" + "strings" + "time" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins" +) + +type POP3Plugin struct { + plugins.BasePlugin +} + +func NewPOP3Plugin() *POP3Plugin { + return &POP3Plugin{BasePlugin: plugins.NewBasePlugin("pop3")} +} + +func (p *POP3Plugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { + config := session.Config + timeout := config.Timeout + if timeout <= 0 { + timeout = 3 * time.Second + } + + addr := fmt.Sprintf("%s:%d", info.Host, info.Port) + conn, err := session.DialTCP(ctx, "tcp", addr, timeout) + if err != nil { + return &ScanResult{Success: false, Service: "pop3"} + } + defer conn.Close() + _ = conn.SetDeadline(time.Now().Add(timeout)) + + reader := bufio.NewReader(conn) + banner, err := reader.ReadString('\n') + if err != nil || !strings.HasPrefix(banner, "+OK") { + return &ScanResult{Success: false, Service: "pop3"} + } + banner = strings.TrimSpace(banner) + + serviceResult := &ScanResult{ + Success: true, + Type: plugins.ResultTypeService, + Service: "pop3", + Banner: banner, + } + + if config.DisableBrute { + return serviceResult + } + + credentials := GenerateCredentials("pop3", config) + if len(credentials) == 0 { + return serviceResult + } + + for _, cred := range credentials { + select { + case <-ctx.Done(): + return serviceResult + default: + } + + if result := p.tryLogin(ctx, info, cred, timeout, session); result != nil { + return result + } + } + + return serviceResult +} + +func (p *POP3Plugin) tryLogin(ctx context.Context, info *common.HostInfo, cred plugins.Credential, timeout time.Duration, session *common.ScanSession) *ScanResult { + addr := fmt.Sprintf("%s:%d", info.Host, info.Port) + conn, err := session.DialTCP(ctx, "tcp", addr, timeout) + if err != nil { + return nil + } + defer conn.Close() + _ = conn.SetDeadline(time.Now().Add(timeout)) + + reader := bufio.NewReader(conn) + if _, err := reader.ReadString('\n'); err != nil { + return nil + } + + if _, err := fmt.Fprintf(conn, "USER %s\r\n", cred.Username); err != nil { + return nil + } + resp, err := reader.ReadString('\n') + if err != nil || !strings.HasPrefix(resp, "+OK") { + return nil + } + + if _, err := fmt.Fprintf(conn, "PASS %s\r\n", cred.Password); err != nil { + return nil + } + resp, err = reader.ReadString('\n') + if err != nil { + return nil + } + + if strings.HasPrefix(resp, "+OK") { + _, _ = conn.Write([]byte("QUIT\r\n")) + return &ScanResult{ + Success: true, + Type: plugins.ResultTypeCredential, + Service: "pop3", + Username: cred.Username, + Password: cred.Password, + } + } + return nil +} + +func init() { + RegisterPluginWithPorts("pop3", func() Plugin { + return NewPOP3Plugin() + }, []int{110, 995}) +}