package core import ( "fmt" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/plugins/base" "strings" ) /* BaseScanStrategy.go - 扫描策略基础类 提供所有扫描策略的通用功能,包括插件管理、验证、 日志输出等,减少代码重复并提升维护性。 */ // PluginFilterType 插件过滤类型 type PluginFilterType int const ( FilterNone PluginFilterType = iota // 不过滤 FilterLocal // 仅本地插件 FilterService // 仅服务插件(排除本地) FilterWeb // 仅Web插件 ) // BaseScanStrategy 扫描策略基础类 type BaseScanStrategy struct { strategyName string filterType PluginFilterType } // NewBaseScanStrategy 创建基础扫描策略 func NewBaseScanStrategy(name string, filterType PluginFilterType) *BaseScanStrategy { return &BaseScanStrategy{ strategyName: name, filterType: filterType, } } // ============================================================================= // 插件管理通用方法 // ============================================================================= // GetPlugins 获取插件列表(通用实现) func (b *BaseScanStrategy) GetPlugins() ([]string, bool) { // 本地模式优先使用LocalPlugin参数 if b.filterType == FilterLocal && common.LocalPlugin != "" { if GlobalPluginAdapter.PluginExists(common.LocalPlugin) { return []string{common.LocalPlugin}, true } else { common.LogError(fmt.Sprintf("指定的本地插件 '%s' 不存在", common.LocalPlugin)) return []string{}, true } } // 如果指定了特定插件且不是"all" if common.ScanMode != "" && common.ScanMode != "all" { requestedPlugins := parsePluginList(common.ScanMode) if len(requestedPlugins) == 0 { requestedPlugins = []string{common.ScanMode} } // 验证插件是否存在(使用新插件系统) var validPlugins []string for _, name := range requestedPlugins { if GlobalPluginAdapter.PluginExists(name) { validPlugins = append(validPlugins, name) } } return validPlugins, true } // 未指定或使用"all":获取所有插件,由IsPluginApplicable做类型过滤 return GlobalPluginAdapter.GetAllPluginNames(), false } // GetApplicablePlugins 获取适用的插件列表(用于日志显示) func (b *BaseScanStrategy) GetApplicablePlugins(allPlugins []string, isCustomMode bool) []string { if isCustomMode { return allPlugins } var applicablePlugins []string for _, pluginName := range allPlugins { if !GlobalPluginAdapter.PluginExists(pluginName) { continue } if b.isPluginTypeMatchedByName(pluginName) { applicablePlugins = append(applicablePlugins, pluginName) } } return applicablePlugins } // isPluginTypeMatchedByName 根据插件名称检查类型是否匹配过滤器 func (b *BaseScanStrategy) isPluginTypeMatchedByName(pluginName string) bool { metadata := GlobalPluginAdapter.registry.GetMetadata(pluginName) if metadata == nil { return false } switch b.filterType { case FilterLocal: return metadata.Category == "local" case FilterService: // 服务扫描允许service类型,以及通过智能检测的web类型(在上层逻辑中处理) return metadata.Category == "service" || metadata.Category == "web" case FilterWeb: return metadata.Category == "web" default: return true } } // isPluginTypeMatched 检查插件类型是否匹配过滤器 func (b *BaseScanStrategy) isPluginTypeMatched(plugin common.ScanPlugin) bool { switch b.filterType { case FilterLocal: return plugin.HasType(common.PluginTypeLocal) case FilterService: // 服务扫描排除本地插件,允许服务和Web插件 return !plugin.HasType(common.PluginTypeLocal) case FilterWeb: return plugin.HasType(common.PluginTypeWeb) default: return true } } // IsPluginApplicableByName 根据插件名称判断是否适用(新方法) func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetHost string, targetPort int, isCustomMode bool) bool { // 自定义模式下运行所有明确指定的插件 if isCustomMode { return true } metadata := GlobalPluginAdapter.registry.GetMetadata(pluginName) if metadata == nil { return false } // 智能Web插件检测:如果是Web插件且检测到Web服务,则包含Web插件 if b.shouldIncludeWebPlugin(metadata, targetHost, targetPort) { return true } // 检查类型匹配 if !b.isPluginTypeMatchedByName(pluginName) { return false } // 检查端口匹配(如果指定了端口) if targetPort > 0 && len(metadata.Ports) > 0 { for _, port := range metadata.Ports { if port == targetPort { return true } } return false } // 对于Web插件的特殊处理 if metadata.Category == "web" { // Web扫描策略下直接允许Web插件执行(用户明确指定了Web目标) if b.filterType == FilterWeb { return true } // 其他策略下必须通过智能检测才能执行 return false } return true } // shouldIncludeWebPlugin 判断是否应该包含Web插件(智能检测) func (b *BaseScanStrategy) shouldIncludeWebPlugin(metadata *base.PluginMetadata, targetHost string, targetPort int) bool { // 只对服务扫描策略启用Web插件智能检测 if b.filterType != FilterService { return false } // 只对Web类别的插件进行检测 if metadata.Category != "web" { return false } // 如果没有指定端口,跳过检测 if targetPort <= 0 { return false } // 获取Web检测器实例(延迟初始化) if globalWebDetector == nil { globalWebDetector = NewWebPortDetector() } // 检测是否为Web服务(使用完整的智能检测,包括HTTP协议探测) return globalWebDetector.IsWebService(targetHost, targetPort) } // globalWebDetector Web检测器全局实例 var globalWebDetector *WebPortDetector // IsPluginApplicable 判断插件是否适用(通用实现,传统插件系统) func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool { // 自定义模式下运行所有明确指定的插件 if isCustomMode { return true } // 检查插件类型过滤 if !b.isPluginTypeMatched(plugin) { return false } // 对于服务扫描中的Web插件,需要进行智能检测(传统插件系统) if b.filterType == FilterService && plugin.HasType(common.PluginTypeWeb) { // 获取Web检测器实例(延迟初始化) if globalWebDetector == nil { globalWebDetector = NewWebPortDetector() } // 注意:传统插件系统无法传递host参数,使用空字符串 // 这是传统插件系统的限制,新插件系统已经解决了这个问题 if targetPort > 0 { return globalWebDetector.IsWebService("", targetPort) } return false } // 对于服务扫描,还需检查端口匹配 if b.filterType == FilterService { // 无端口限制的插件适用于所有端口 if len(plugin.Ports) == 0 { return true } // 有端口限制的插件:检查端口是否匹配 if targetPort > 0 { return plugin.HasPort(targetPort) } // 如果没有提供目标端口,则不执行有端口限制的插件 return false } return true } // ============================================================================= // 日志输出通用方法 // ============================================================================= // LogPluginInfo 输出插件信息(通用实现) func (b *BaseScanStrategy) LogPluginInfo() { allPlugins, isCustomMode := b.GetPlugins() applicablePlugins := b.GetApplicablePlugins(allPlugins, isCustomMode) // 生成日志消息 var messageKey, prefix string switch b.filterType { case FilterLocal: messageKey = "scan_plugins_local" prefix = i18n.GetText("scan_mode_local_prefix") case FilterService: messageKey = "scan_plugins_service" prefix = i18n.GetText("scan_mode_service_prefix") case FilterWeb: messageKey = "scan_plugins_web" prefix = i18n.GetText("scan_mode_web_prefix") default: messageKey = "scan_plugins_custom" prefix = "" } if len(applicablePlugins) > 0 { if isCustomMode { common.LogBase(fmt.Sprintf("%s: %s", prefix, i18n.GetText("scan_plugins_custom_specified", strings.Join(applicablePlugins, ", ")))) } else { common.LogBase(fmt.Sprintf("%s: %s", prefix, i18n.GetText(messageKey, strings.Join(applicablePlugins, ", ")))) } } else { noPluginsKey := fmt.Sprintf("scan_no_%s_plugins", b.getFilterTypeName()) common.LogBase(fmt.Sprintf("%s: %s", prefix, i18n.GetText(noPluginsKey))) } } // getFilterTypeName 获取过滤器类型名称 func (b *BaseScanStrategy) getFilterTypeName() string { switch b.filterType { case FilterLocal: return "local" case FilterService: return "service" case FilterWeb: return "web" default: return "general" } } // ============================================================================= // 验证通用方法 // ============================================================================= // ValidateConfiguration 验证扫描配置(通用实现) func (b *BaseScanStrategy) ValidateConfiguration() error { return validateScanPlugins() } // ============================================================================= // 通用辅助方法 // ============================================================================= // LogScanStart 输出扫描开始信息 func (b *BaseScanStrategy) LogScanStart() { switch b.filterType { case FilterLocal: common.LogBase(i18n.GetText("scan_local_start")) case FilterService: common.LogBase(i18n.GetText("scan_service_start")) case FilterWeb: common.LogBase(i18n.GetText("scan_web_start")) default: common.LogBase(i18n.GetText("scan_general_start")) } }