package plugins import ( "context" "database/sql" "fmt" "net" "regexp" "time" _ "github.com/go-sql-driver/mysql" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common/i18n" ) // MySQLPlugin MySQL数据库弱密码扫描插件 - 单文件实现 type MySQLPlugin struct { name string ports []int } // NewMySQLPlugin 创建MySQL插件 func NewMySQLPlugin() *MySQLPlugin { return &MySQLPlugin{ name: "mysql", ports: []int{3306, 3307, 33060}, } } // GetName 实现Plugin接口 func (p *MySQLPlugin) GetName() string { return p.name } // GetPorts 实现Plugin接口 func (p *MySQLPlugin) GetPorts() []int { return p.ports } // Scan 执行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 { // 检查Context是否被取消 select { case <-ctx.Done(): return &ScanResult{ Success: false, Service: "mysql", Error: ctx.Err(), } default: } // 测试凭据 if p.testCredential(ctx, info, cred) { // 弱密码发现成功 common.LogSuccess(i18n.GetText("mysql_scan_success", 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("未发现弱密码"), } } // testCredential 测试单个凭据 - 核心认证逻辑 func (p *MySQLPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { // 构建连接字符串 connStr := p.buildConnectionString(info.Host, info.Ports, cred.Username, cred.Password) // 创建数据库连接 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) // 测试连接 - 使用Context超时控制 err = db.PingContext(ctx) return err == nil } // buildConnectionString 构建MySQL连接字符串 func (p *MySQLPlugin) buildConnectionString(host, port, username, password string) string { // 支持SOCKS代理 if common.Socks5Proxy != "" { // 如果使用代理,需要注册自定义拨号器 p.registerProxyDialer() return fmt.Sprintf("%s:%s@tcp-proxy(%s:%s)/mysql?charset=utf8&timeout=%ds", username, password, host, port, common.Timeout) } // 标准TCP连接 return fmt.Sprintf("%s:%s@tcp(%s:%s)/mysql?charset=utf8&timeout=%ds", username, password, host, port, common.Timeout) } // identifyService 服务识别 - 不进行暴力破解时的功能 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() // 读取MySQL握手包 if banner := p.readMySQLBanner(conn); banner != "" { common.LogSuccess(i18n.GetText("mysql_service_identified", target, banner)) return &ScanResult{ Success: true, Service: "mysql", Banner: banner, } } return &ScanResult{ Success: false, Service: "mysql", Error: fmt.Errorf("无法识别为MySQL服务"), } } // readMySQLBanner 读取MySQL服务器握手包 func (p *MySQLPlugin) readMySQLBanner(conn net.Conn) string { // 设置读取超时 conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) // 读取握手包 handshake := make([]byte, 512) n, err := conn.Read(handshake) if err != nil || n < 10 { return "" } // 检查MySQL协议版本(通常是10) 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]) // 验证版本字符串格式 if regexp.MustCompile(`\d+\.\d+`).MatchString(versionStr) { return fmt.Sprintf("MySQL %s", versionStr) } return "" } // registerProxyDialer 注册SOCKS代理拨号器 func (p *MySQLPlugin) registerProxyDialer() { // TODO: 实现代理拨号器注册 // 这里简化处理,实际需要注册到MySQL驱动 } // init 自动注册插件 func init() { RegisterPlugin("mysql", func() Plugin { return NewMySQLPlugin() }) }