diff --git a/Common/i18n/manager.go b/Common/i18n/manager.go index db91db7..79e3c65 100644 --- a/Common/i18n/manager.go +++ b/Common/i18n/manager.go @@ -188,6 +188,19 @@ func GetText(key string, args ...interface{}) string { return globalManager.GetText(key, args...) } +// GetExploitMethodName 获取利用方法的本地化名称 +func GetExploitMethodName(methodName string) string { + // 尝试获取本地化的方法名称 + key := fmt.Sprintf("exploit_method_name_%s", methodName) + localizedName := globalManager.GetText(key) + + // 如果没有找到对应的本地化名称,返回原始名称 + if localizedName == key { + return methodName + } + return localizedName +} + // ============================================================================================= // 已删除的死代码函数(未使用): // GetGlobalManager, GetLanguage, AddMessage, GetTextWithLanguage, diff --git a/Common/i18n/messages/plugins.go b/Common/i18n/messages/plugins.go index f7d7a29..96a0fae 100644 --- a/Common/i18n/messages/plugins.go +++ b/Common/i18n/messages/plugins.go @@ -39,6 +39,164 @@ var PluginMessages = map[string]map[string]string{ LangEN: "%s exploitation failed: %v", }, + // ========================= 通用成功消息模板 ========================= + "plugin_login_success": { + LangZH: "%s弱密码: %s [%s:%s]", + LangEN: "%s weak password: %s [%s:%s]", + }, + "plugin_login_success_passwd_only": { + LangZH: "%s弱密码: %s [%s]", + LangEN: "%s weak password: %s [%s]", + }, + "plugin_unauthorized_access": { + LangZH: "%s未授权访问: %s", + LangEN: "%s unauthorized access: %s", + }, + + // ========================= 利用(Exploit)消息模板 ========================= + "exploit_weak_password_success": { + LangZH: "%s %s 弱密码利用成功", + LangEN: "%s %s weak password exploit successful", + }, + "exploit_unauthorized_success": { + LangZH: "%s %s 未授权访问利用成功", + LangEN: "%s %s unauthorized access exploit successful", + }, + "exploit_command_exec_success": { + LangZH: "%s %s 命令执行利用成功", + LangEN: "%s %s command execution exploit successful", + }, + "exploit_file_write_success": { + LangZH: "%s %s 文件写入利用成功", + LangEN: "%s %s file write exploit successful", + }, + "exploit_sql_injection_success": { + LangZH: "%s %s SQL注入利用成功", + LangEN: "%s %s SQL injection exploit successful", + }, + "exploit_data_extraction_success": { + LangZH: "%s %s %s 利用成功", + LangEN: "%s %s %s exploit successful", + }, + "exploit_generic_success": { + LangZH: "%s %s %s 利用成功", + LangEN: "%s %s %s exploit successful", + }, + "exploit_with_output": { + LangZH: " 输出: %s", + LangEN: " output: %s", + }, + "exploit_files_created": { + LangZH: "创建/修改的文件: %v", + LangEN: "Files created/modified: %v", + }, + "exploit_shell_obtained": { + LangZH: "获得Shell: %s %s:%d 用户:%s", + LangEN: "Shell obtained: %s %s:%d user:%s", + }, + + // ========================= 利用方法执行消息 ========================= + "exploit_method_trying": { + LangZH: "尝试利用方法: %s", + LangEN: "Trying exploit method: %s", + }, + "exploit_method_success": { + LangZH: "利用方法 %s 执行成功", + LangEN: "Exploit method %s executed successfully", + }, + "exploit_method_failed": { + LangZH: "利用方法 %s 执行失败: %v", + LangEN: "Exploit method %s failed: %v", + }, + "exploit_method_condition_not_met": { + LangZH: "利用方法 %s 前置条件不满足,跳过", + LangEN: "Exploit method %s prerequisites not met, skipping", + }, + "exploit_all_methods_failed": { + LangZH: "所有利用方法都失败", + LangEN: "All exploit methods failed", + }, + + // ========================= MySQL利用方法消息 ========================= + "mysql_version_info": { + LangZH: "MySQL版本: %s", + LangEN: "MySQL version: %s", + }, + "mysql_current_user": { + LangZH: "当前用户: %s", + LangEN: "Current user: %s", + }, + "mysql_current_database": { + LangZH: "当前数据库: %s", + LangEN: "Current database: %s", + }, + "mysql_databases_found": { + LangZH: "发现数据库: %s", + LangEN: "Databases found: %s", + }, + "mysql_tables_found": { + LangZH: "发现表: %v", + LangEN: "Tables found: %v", + }, + "mysql_user_privileges": { + LangZH: "用户权限: %s", + LangEN: "User privileges: %s", + }, + "mysql_file_privilege_detected": { + LangZH: "检测到FILE权限,可能支持文件操作", + LangEN: "FILE privilege detected, file operations may be supported", + }, + "mysql_file_read_success": { + LangZH: "读取文件 %s:\n%s", + LangEN: "File %s read:\n%s", + }, + "mysql_file_write_success": { + LangZH: "成功写入文件: %s", + LangEN: "File written successfully: %s", + }, + "mysql_no_file_privilege": { + LangZH: "无法读取任何文件,可能没有FILE权限", + LangEN: "Cannot read any files, may lack FILE privilege", + }, + + // ========================= Redis利用方法消息 ========================= + "redis_server_info": { + LangZH: "Redis服务器信息: %s", + LangEN: "Redis server info: %s", + }, + "redis_config_info": { + LangZH: "Redis配置信息: %s", + LangEN: "Redis config info: %s", + }, + "redis_keys_found": { + LangZH: "发现Redis键: %v", + LangEN: "Redis keys found: %v", + }, + "redis_backup_created": { + LangZH: "Redis备份创建成功: %s", + LangEN: "Redis backup created: %s", + }, + "redis_cron_job_written": { + LangZH: "Cron任务写入成功: %s", + LangEN: "Cron job written successfully: %s", + }, + "redis_ssh_key_written": { + LangZH: "SSH密钥写入成功: %s", + LangEN: "SSH key written successfully: %s", + }, + "redis_webshell_written": { + LangZH: "Webshell写入成功: %s", + LangEN: "Webshell written successfully: %s", + }, + "redis_no_keys_found": { + LangZH: "未发现任何Redis键", + LangEN: "No Redis keys found", + }, + "redis_write_failed": { + LangZH: "Redis写入操作失败", + LangEN: "Redis write operation failed", + }, + // ========================= 插件架构消息 ========================= "plugin_new_arch_trying": { LangZH: "尝试使用新插件架构: %s", @@ -181,21 +339,47 @@ var PluginMessages = map[string]map[string]string{ LangEN: "%s vulnerability found: %s - %s", }, + // ========================= 利用方法名称i18n ========================= + "exploit_method_name_information_gathering": { + LangZH: "信息收集", + LangEN: "information_gathering", + }, + "exploit_method_name_database_enumeration": { + LangZH: "数据库枚举", + LangEN: "database_enumeration", + }, + "exploit_method_name_privilege_check": { + LangZH: "权限检查", + LangEN: "privilege_check", + }, + "exploit_method_name_file_read": { + LangZH: "文件读取", + LangEN: "file_read", + }, + "exploit_method_name_file_write": { + LangZH: "文件写入", + LangEN: "file_write", + }, + "exploit_method_name_arbitrary_file_write": { + LangZH: "任意文件写入", + LangEN: "arbitrary_file_write", + }, + "exploit_method_name_ssh_key_write": { + LangZH: "SSH密钥写入", + LangEN: "ssh_key_write", + }, + "exploit_method_name_crontab_injection": { + LangZH: "定时任务注入", + LangEN: "crontab_injection", + }, + "exploit_method_name_data_extraction": { + LangZH: "数据提取", + LangEN: "data_extraction", + }, + // ========================= 利用结果消息 ========================= "exploit_result_saved": { LangZH: "利用结果已保存: %s", LangEN: "Exploitation result saved: %s", }, - "exploit_method_trying": { - LangZH: "尝试利用方法: %s", - LangEN: "Trying exploitation method: %s", - }, - "exploit_method_success": { - LangZH: "利用方法成功: %s", - LangEN: "Exploitation method successful: %s", - }, - "exploit_method_failed": { - LangZH: "利用方法失败: %s - %v", - LangEN: "Exploitation method failed: %s - %v", - }, } \ No newline at end of file diff --git a/Plugins/adapter/plugin_adapter.go b/Plugins/adapter/plugin_adapter.go index c87cbde..b037cf4 100644 --- a/Plugins/adapter/plugin_adapter.go +++ b/Plugins/adapter/plugin_adapter.go @@ -57,21 +57,9 @@ func (a *PluginAdapter) AdaptPluginScan(pluginName string, info *common.HostInfo if result != nil && result.Success { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - if len(result.Credentials) > 0 { - // 有凭据的情况 - cred := result.Credentials[0] - if cred.Username != "" { - common.LogSuccess(fmt.Sprintf("%s successful login: %s [%s:%s]", - pluginName, target, cred.Username, cred.Password)) - } else { - // 仅密码的情况(如Redis) - common.LogSuccess(fmt.Sprintf("%s successful login: %s [%s]", - pluginName, target, cred.Password)) - } - } else { - // 未授权访问的情况 - common.LogSuccess(fmt.Sprintf("%s unauthorized access: %s", pluginName, target)) - } + // 适配器层不输出扫描结果,由插件层负责输出 + // 这避免了重复输出的问题 + common.LogDebug(fmt.Sprintf("插件 %s 适配成功: %s", pluginName, target)) // 保存结果到文件 a.saveResult(info, result, pluginName) diff --git a/Plugins/base/exploiter.go b/Plugins/base/exploiter.go index 18109f3..a97d140 100644 --- a/Plugins/base/exploiter.go +++ b/Plugins/base/exploiter.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" "sort" ) @@ -56,28 +57,28 @@ func (e *BaseExploiter) Exploit(ctx context.Context, info *common.HostInfo, cred for _, method := range e.exploitMethods { // 检查前置条件 if !e.checkConditions(method.Conditions, info, creds) { - common.LogDebug(fmt.Sprintf("利用方法 %s 前置条件不满足,跳过", method.Name)) + common.LogDebug(i18n.GetText("exploit_method_condition_not_met", method.Name)) continue } - common.LogDebug(fmt.Sprintf("尝试利用方法: %s", method.Name)) + common.LogDebug(i18n.GetText("exploit_method_trying", i18n.GetExploitMethodName(method.Name))) // 执行利用 result, err := method.Handler(ctx, info, creds) if err != nil { - common.LogError(fmt.Sprintf("利用方法 %s 执行失败: %v", method.Name, err)) + common.LogError(i18n.GetText("exploit_method_failed", method.Name, err)) continue } if result != nil && result.Success { - common.LogSuccess(fmt.Sprintf("利用方法 %s 执行成功", method.Name)) + common.LogSuccess(i18n.GetText("exploit_method_success", i18n.GetExploitMethodName(method.Name))) result.Type = method.Type result.Method = method.Name return result, nil } } - return nil, fmt.Errorf("所有利用方法都失败") + return nil, fmt.Errorf(i18n.GetText("exploit_all_methods_failed")) } // checkConditions 检查前置条件 @@ -167,38 +168,40 @@ func SaveExploitResult(info *common.HostInfo, result *ExploitResult, pluginName return } - target := fmt.Sprintf("%s:%d", info.Host, info.Ports) + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) var message string switch result.Type { case ExploitWeakPassword: - message = fmt.Sprintf("%s %s 弱密码利用成功", pluginName, target) + message = i18n.GetText("exploit_weak_password_success", pluginName, target) case ExploitUnauthorized: - message = fmt.Sprintf("%s %s 未授权访问利用成功", pluginName, target) + message = i18n.GetText("exploit_unauthorized_success", pluginName, target) case ExploitCommandExec: - message = fmt.Sprintf("%s %s 命令执行利用成功", pluginName, target) + message = i18n.GetText("exploit_command_exec_success", pluginName, target) case ExploitFileWrite: - message = fmt.Sprintf("%s %s 文件写入利用成功", pluginName, target) + message = i18n.GetText("exploit_file_write_success", pluginName, target) case ExploitSQLInjection: - message = fmt.Sprintf("%s %s SQL注入利用成功", pluginName, target) + message = i18n.GetText("exploit_sql_injection_success", pluginName, target) + case ExploitDataExtraction: + message = i18n.GetText("exploit_data_extraction_success", pluginName, target, i18n.GetExploitMethodName(result.Method)) default: - message = fmt.Sprintf("%s %s %s 利用成功", pluginName, target, result.Type) + message = i18n.GetText("exploit_generic_success", pluginName, target, i18n.GetExploitMethodName(result.Method)) } if result.Output != "" { - message += fmt.Sprintf(" 输出: %s", result.Output) + message += i18n.GetText("exploit_with_output", result.Output) } common.LogSuccess(message) // 保存文件信息 if len(result.Files) > 0 { - common.LogSuccess(fmt.Sprintf("创建/修改的文件: %v", result.Files)) + common.LogSuccess(i18n.GetText("exploit_files_created", result.Files)) } // 保存Shell信息 if result.Shell != nil { - common.LogSuccess(fmt.Sprintf("获得Shell: %s %s:%d 用户:%s", + common.LogSuccess(i18n.GetText("exploit_shell_obtained", result.Shell.Type, result.Shell.Host, result.Shell.Port, result.Shell.User)) } } diff --git a/Plugins/services/mysql/exploiter.go b/Plugins/services/mysql/exploiter.go index 5ac5ff2..0036661 100644 --- a/Plugins/services/mysql/exploiter.go +++ b/Plugins/services/mysql/exploiter.go @@ -5,6 +5,7 @@ import ( "database/sql" "fmt" "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/plugins/base" "strconv" "strings" @@ -90,21 +91,21 @@ func (e *MySQLExploiter) exploitInformationGathering(ctx context.Context, info * // 获取版本信息 version, err := e.getVersion(ctx, db) if err == nil { - base.AddOutputToResult(result, fmt.Sprintf("MySQL版本: %s", version)) + 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, fmt.Sprintf("当前用户: %s", user)) + 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, fmt.Sprintf("当前数据库: %s", database)) + base.AddOutputToResult(result, i18n.GetText("mysql_current_database", database)) result.Extra["current_database"] = database } @@ -124,14 +125,14 @@ func (e *MySQLExploiter) exploitDatabaseEnumeration(ctx context.Context, info *c // 枚举数据库 databases, err := e.enumerateDatabases(ctx, db) if err == nil && len(databases) > 0 { - base.AddOutputToResult(result, fmt.Sprintf("发现数据库: %s", strings.Join(databases, ", "))) + 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, fmt.Sprintf("发现表: %v", tables)) + base.AddOutputToResult(result, i18n.GetText("mysql_tables_found", tables)) result.Extra["tables"] = tables } @@ -151,7 +152,7 @@ func (e *MySQLExploiter) exploitPrivilegeCheck(ctx context.Context, info *common // 检查用户权限 privileges, err := e.getUserPrivileges(ctx, db) if err == nil && len(privileges) > 0 { - base.AddOutputToResult(result, fmt.Sprintf("用户权限: %s", strings.Join(privileges, ", "))) + base.AddOutputToResult(result, i18n.GetText("mysql_user_privileges", strings.Join(privileges, ", "))) result.Extra["privileges"] = privileges } @@ -159,7 +160,7 @@ func (e *MySQLExploiter) exploitPrivilegeCheck(ctx context.Context, info *common hasFilePriv := e.hasFilePrivilege(privileges) result.Extra["has_file_privilege"] = hasFilePriv if hasFilePriv { - base.AddOutputToResult(result, "检测到FILE权限,可能支持文件操作") + base.AddOutputToResult(result, i18n.GetText("mysql_file_privilege_detected")) } return result, nil @@ -187,14 +188,14 @@ func (e *MySQLExploiter) exploitFileRead(ctx context.Context, info *common.HostI 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)) + base.AddOutputToResult(result, i18n.GetText("mysql_file_read_success", file, content)) hasRead = true } } if !hasRead { return base.CreateFailedExploitResult(base.ExploitDataExtraction, "file_read", - fmt.Errorf("无法读取任何文件,可能没有FILE权限")), nil + fmt.Errorf(i18n.GetText("mysql_no_file_privilege"))), nil } return result, nil @@ -219,7 +220,7 @@ func (e *MySQLExploiter) exploitFileWrite(ctx context.Context, info *common.Host return base.CreateFailedExploitResult(base.ExploitFileWrite, "file_write", err), nil } - base.AddOutputToResult(result, fmt.Sprintf("成功写入文件: %s", testFile)) + base.AddOutputToResult(result, i18n.GetText("mysql_file_write_success", testFile)) base.AddFileToResult(result, testFile) return result, nil diff --git a/Plugins/services/mysql/plugin.go b/Plugins/services/mysql/plugin.go index 75e4757..e27f448 100644 --- a/Plugins/services/mysql/plugin.go +++ b/Plugins/services/mysql/plugin.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/plugins/base" ) @@ -67,10 +68,10 @@ func (p *MySQLPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.Sc return result, err // 扫描失败,直接返回 } - // 记录成功的弱密码发现(支持i18n) + // 记录成功的弱密码发现(使用i18n) target := fmt.Sprintf("%s:%s", info.Host, info.Ports) cred := result.Credentials[0] - common.LogSuccess(fmt.Sprintf("MySQL scan success: %s with %s:%s", target, cred.Username, cred.Password)) + common.LogSuccess(i18n.GetText("mysql_scan_success", target, cred.Username, cred.Password)) // 自动利用功能(可通过-nobr参数禁用) if result.Success && len(result.Credentials) > 0 && !common.DisableBrute { @@ -84,17 +85,17 @@ func (p *MySQLPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.Sc // autoExploit 自动利用 func (p *MySQLPlugin) autoExploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) - common.LogDebug(fmt.Sprintf("MySQL exploit starting for %s", target)) + common.LogDebug(i18n.GetText("plugin_exploit_start", "MySQL", target)) // 执行利用 result, err := p.exploiter.Exploit(ctx, info, creds) if err != nil { - common.LogError(fmt.Sprintf("MySQL exploit failed: %v", err)) + common.LogError(i18n.GetText("plugin_exploit_failed", "MySQL", err)) return } if result != nil && result.Success { - common.LogSuccess(fmt.Sprintf("MySQL exploit success using %s", result.Method)) + common.LogSuccess(i18n.GetText("plugin_exploit_success", "MySQL", i18n.GetExploitMethodName(result.Method))) base.SaveExploitResult(info, result, "MySQL") } } diff --git a/Plugins/services/redis/exploiter.go b/Plugins/services/redis/exploiter.go index e6b0176..022cb68 100644 --- a/Plugins/services/redis/exploiter.go +++ b/Plugins/services/redis/exploiter.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/plugins/base" "os" "path/filepath" @@ -129,7 +130,7 @@ func (e *RedisExploiter) exploitArbitraryFileWrite(ctx context.Context, info *co fmt.Errorf("写入失败: %s", msg)), nil } - base.AddOutputToResult(result, fmt.Sprintf("成功写入文件: %s", common.RedisWritePath)) + base.AddOutputToResult(result, i18n.GetText("redis_webshell_written", common.RedisWritePath)) base.AddFileToResult(result, common.RedisWritePath) return result, nil @@ -216,7 +217,7 @@ func (e *RedisExploiter) exploitCrontabInjection(ctx context.Context, info *comm fmt.Errorf("写入失败: %s", msg)), nil } - base.AddOutputToResult(result, fmt.Sprintf("成功注入Crontab任务,反弹Shell到: %s", common.RedisShell)) + base.AddOutputToResult(result, i18n.GetText("redis_cron_job_written", common.RedisShell)) // 创建Shell信息 shellParts := strings.Split(common.RedisShell, ":") @@ -245,7 +246,7 @@ func (e *RedisExploiter) exploitDataExtraction(ctx context.Context, info *common // 获取所有键 keys, err := e.getAllKeys(redisConn) if err == nil && len(keys) > 0 { - base.AddOutputToResult(result, fmt.Sprintf("发现 %d 个键: %s", len(keys), strings.Join(keys[:min(10, len(keys))], ", "))) + base.AddOutputToResult(result, i18n.GetText("redis_keys_found", strings.Join(keys[:min(10, len(keys))], ", "))) result.Extra["keys"] = keys // 获取部分键值 @@ -255,7 +256,7 @@ func (e *RedisExploiter) exploitDataExtraction(ctx context.Context, info *common } value, err := e.getKeyValue(redisConn, key) if err == nil && value != "" { - base.AddOutputToResult(result, fmt.Sprintf("键 %s = %s", key, value)) + base.AddOutputToResult(result, fmt.Sprintf("%s = %s", key, value)) } } } @@ -288,8 +289,8 @@ func (e *RedisExploiter) exploitInfoGathering(ctx context.Context, info *common. } // 获取配置信息 - base.AddOutputToResult(result, fmt.Sprintf("数据库目录: %s", redisConn.config.Dir)) - base.AddOutputToResult(result, fmt.Sprintf("数据库文件: %s", redisConn.config.DBFilename)) + base.AddOutputToResult(result, i18n.GetText("redis_config_info", fmt.Sprintf("Dir: %s", redisConn.config.Dir))) + base.AddOutputToResult(result, i18n.GetText("redis_config_info", fmt.Sprintf("DBFilename: %s", redisConn.config.DBFilename))) return result, nil }