mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00

- 修改SMTP插件,在多个连接点添加发包控制 - 修改Telnet插件,在identifyService中使用SafeTCPDial包装器 - 保持现有功能不变,统一发包控制逻辑
260 lines
6.0 KiB
Go
260 lines
6.0 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/smtp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/shadow1ng/fscan/common"
|
|
"github.com/shadow1ng/fscan/plugins"
|
|
)
|
|
|
|
type SMTPPlugin struct {
|
|
plugins.BasePlugin
|
|
}
|
|
|
|
func NewSMTPPlugin() *SMTPPlugin {
|
|
return &SMTPPlugin{
|
|
BasePlugin: plugins.NewBasePlugin("smtp"),
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (p *SMTPPlugin) Scan(ctx context.Context, info *common.HostInfo) *plugins.Result {
|
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
|
|
|
if common.DisableBrute {
|
|
return p.identifyService(ctx, info)
|
|
}
|
|
|
|
// 首先测试匿名访问(最重要!)
|
|
if p.testCredential(ctx, info, plugins.Credential{Username: "", Password: ""}) {
|
|
common.LogSuccess(fmt.Sprintf("SMTP服务 %s 允许匿名访问", target))
|
|
return &plugins.Result{
|
|
Success: true,
|
|
Service: "smtp",
|
|
Banner: "允许匿名访问",
|
|
}
|
|
}
|
|
|
|
// 检查开放中继
|
|
if result := p.testOpenRelay(ctx, info); result != nil && result.Success {
|
|
common.LogSuccess(fmt.Sprintf("SMTP %s 开放中继", target))
|
|
return result
|
|
}
|
|
|
|
credentials := plugins.GenerateCredentials("smtp")
|
|
if len(credentials) == 0 {
|
|
return &plugins.Result{
|
|
Success: false,
|
|
Service: "smtp",
|
|
Error: fmt.Errorf("没有可用的测试凭据"),
|
|
}
|
|
}
|
|
|
|
for _, cred := range credentials {
|
|
// 检查上下文是否已取消
|
|
select {
|
|
case <-ctx.Done():
|
|
return &plugins.Result{
|
|
Success: false,
|
|
Service: "smtp",
|
|
Error: ctx.Err(),
|
|
}
|
|
default:
|
|
}
|
|
|
|
if p.testCredential(ctx, info, cred) {
|
|
common.LogSuccess(fmt.Sprintf("SMTP %s %s:%s", target, cred.Username, cred.Password))
|
|
return &plugins.Result{
|
|
Success: true,
|
|
Service: "smtp",
|
|
Username: cred.Username,
|
|
Password: cred.Password,
|
|
}
|
|
}
|
|
}
|
|
|
|
return &plugins.Result{
|
|
Success: false,
|
|
Service: "smtp",
|
|
Error: fmt.Errorf("未发现弱密码"),
|
|
}
|
|
}
|
|
|
|
|
|
func (p *SMTPPlugin) testOpenRelay(ctx context.Context, info *common.HostInfo) *plugins.Result {
|
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
|
|
|
// 检查发包限制
|
|
if canSend, reason := common.CanSendPacket(); !canSend {
|
|
common.LogError(fmt.Sprintf("SMTP连接 %s 受限: %s", target, reason))
|
|
return nil
|
|
}
|
|
|
|
// 设置超时的Dialer
|
|
dialer := &net.Dialer{
|
|
Timeout: time.Duration(common.Timeout) * time.Second,
|
|
}
|
|
|
|
conn, err := dialer.DialContext(ctx, "tcp", target)
|
|
if err == nil {
|
|
common.IncrementTCPSuccessPacketCount()
|
|
} else {
|
|
common.IncrementTCPFailedPacketCount()
|
|
}
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
defer conn.Close()
|
|
|
|
client, err := smtp.NewClient(conn, info.Host)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
defer client.Quit()
|
|
|
|
if err := client.Hello("fscan.test"); err != nil {
|
|
return nil
|
|
}
|
|
|
|
if err := client.Mail("test@fscan.test"); err != nil {
|
|
return nil
|
|
}
|
|
|
|
if err := client.Rcpt("external@example.com"); err != nil {
|
|
return nil
|
|
}
|
|
|
|
return &plugins.Result{
|
|
Success: true,
|
|
Service: "smtp",
|
|
Banner: "开放中继",
|
|
}
|
|
}
|
|
|
|
func (p *SMTPPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred plugins.Credential) bool {
|
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
|
timeout := time.Duration(common.Timeout) * time.Second
|
|
|
|
common.LogDebug(fmt.Sprintf("SMTP测试凭据: %s:%s", cred.Username, cred.Password))
|
|
|
|
// 检查发包限制
|
|
if canSend, reason := common.CanSendPacket(); !canSend {
|
|
common.LogError(fmt.Sprintf("SMTP凭据测试 %s 受限: %s", target, reason))
|
|
return false
|
|
}
|
|
|
|
// 设置连接超时
|
|
dialer := &net.Dialer{
|
|
Timeout: timeout,
|
|
}
|
|
|
|
conn, err := dialer.DialContext(ctx, "tcp", target)
|
|
if err == nil {
|
|
common.IncrementTCPSuccessPacketCount()
|
|
} else {
|
|
common.IncrementTCPFailedPacketCount()
|
|
}
|
|
if err != nil {
|
|
common.LogDebug(fmt.Sprintf("SMTP连接失败: %v", err))
|
|
return false
|
|
}
|
|
defer conn.Close()
|
|
|
|
// 设置读写超时
|
|
conn.SetDeadline(time.Now().Add(timeout))
|
|
|
|
client, err := smtp.NewClient(conn, info.Host)
|
|
if err != nil {
|
|
common.LogDebug(fmt.Sprintf("SMTP客户端创建失败: %v", err))
|
|
return false
|
|
}
|
|
defer client.Close()
|
|
|
|
// 如果有用户名密码,则尝试认证
|
|
if cred.Username != "" {
|
|
auth := smtp.PlainAuth("", cred.Username, cred.Password, info.Host)
|
|
if err := client.Auth(auth); err != nil {
|
|
common.LogDebug(fmt.Sprintf("SMTP认证失败 %s:%s - %v", cred.Username, cred.Password, err))
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 尝试发送邮件测试权限(仿照原版)
|
|
if err := client.Mail("test@test.com"); err != nil {
|
|
common.LogDebug(fmt.Sprintf("SMTP Mail命令失败 %s:%s - %v", cred.Username, cred.Password, err))
|
|
return false
|
|
}
|
|
|
|
common.LogDebug(fmt.Sprintf("SMTP认证成功: %s:%s", cred.Username, cred.Password))
|
|
return true
|
|
}
|
|
|
|
|
|
func (p *SMTPPlugin) getServerInfo(ctx context.Context, info *common.HostInfo) string {
|
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
|
|
|
// 使用统一TCP包装器
|
|
conn, err := common.SafeTCPDial(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
|
|
}
|
|
|
|
func (p *SMTPPlugin) identifyService(ctx context.Context, info *common.HostInfo) *plugins.Result {
|
|
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 {
|
|
// 使用统一TCP包装器进行简单连接测试
|
|
conn, err := common.SafeTCPDial(target, time.Duration(common.Timeout)*time.Second)
|
|
if err != nil {
|
|
return &plugins.Result{
|
|
Success: false,
|
|
Service: "smtp",
|
|
Error: err,
|
|
}
|
|
}
|
|
defer conn.Close()
|
|
banner = "SMTP邮件服务"
|
|
}
|
|
|
|
common.LogSuccess(fmt.Sprintf("SMTP %s %s", target, banner))
|
|
|
|
return &plugins.Result{
|
|
Success: true,
|
|
Service: "smtp",
|
|
Banner: banner,
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
plugins.RegisterWithPorts("smtp", func() plugins.Plugin {
|
|
return NewSMTPPlugin()
|
|
}, []int{25, 465, 587, 2525})
|
|
}
|