package services import ( "context" "database/sql" "fmt" "net" "time" _ "github.com/go-sql-driver/mysql" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/plugins" ) type MySQLPlugin struct { plugins.BasePlugin } func NewMySQLPlugin() *MySQLPlugin { return &MySQLPlugin{ BasePlugin: plugins.NewBasePlugin("mysql"), } } func (p *MySQLPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) if common.DisableBrute { return p.identifyService(info) } credentials := GenerateCredentials("mysql") if len(credentials) == 0 { return &ScanResult{ Success: false, Service: "mysql", Error: fmt.Errorf("没有可用的测试凭据"), } } for _, cred := range credentials { if p.testCredential(ctx, info, cred) { common.LogSuccess(fmt.Sprintf("MySQL %s %s:%s", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, Service: "mysql", Username: cred.Username, Password: cred.Password, } } } return &ScanResult{ Success: false, Service: "mysql", Error: fmt.Errorf("未发现弱密码"), } } func (p *MySQLPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { connStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/mysql?charset=utf8&timeout=%ds", cred.Username, cred.Password, info.Host, info.Ports, common.Timeout) db, err := sql.Open("mysql", connStr) if err != nil { return false } defer db.Close() db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Second) db.SetMaxOpenConns(1) db.SetMaxIdleConns(0) err = db.PingContext(ctx) return err == nil } func (p *MySQLPlugin) identifyService(info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) if err != nil { return &ScanResult{ Success: false, Service: "mysql", Error: err, } } defer conn.Close() if banner := p.readMySQLBanner(conn); banner != "" { common.LogSuccess(fmt.Sprintf("MySQL %s %s", target, banner)) return &ScanResult{ Success: true, Service: "mysql", Banner: banner, } } return &ScanResult{ Success: false, Service: "mysql", Error: fmt.Errorf("无法识别为MySQL服务"), } } func (p *MySQLPlugin) readMySQLBanner(conn net.Conn) string { conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) handshake := make([]byte, 256) n, err := conn.Read(handshake) if err != nil || n < 10 { return "" } if handshake[4] != 10 { return "" } versionStart := 5 versionEnd := versionStart for versionEnd < n && handshake[versionEnd] != 0 { versionEnd++ } if versionEnd <= versionStart { return "" } versionStr := string(handshake[versionStart:versionEnd]) return fmt.Sprintf("MySQL %s", versionStr) } func init() { // 使用高效注册方式:直接传递端口信息,避免实例创建 RegisterPluginWithPorts("mysql", func() Plugin { return NewMySQLPlugin() }, []int{3306, 3307, 33060}) }