From 6c93129cb111fd51b66c35730c09a5b689c9250e Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Thu, 7 Aug 2025 09:13:17 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E6=89=AB?= =?UTF-8?q?=E6=8F=8F=E5=99=A8=E6=9E=B6=E6=9E=84=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=A4=8D=E7=94=A8=E5=92=8C=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要改进: - 创建BaseScanStrategy基础类提取通用功能,减少60%代码重复 - 新增PortDiscoveryService分离端口发现逻辑,提升职责清晰度 - 优化插件匹配算法,从O(n×m×p)降至O(n×p)复杂度 - 修复插件适用性判断逻辑错误,确保精确端口匹配 - 完善国际化支持,新增21个i18n消息定义 - 代码行数显著减少:LocalScanner(-50%)、ServiceScanner(-42%)、WebScanner(-44%) 技术优化: - 组合模式替代继承,提升扩展性 - 策略模式实现插件过滤器,支持Local/Service/Web类型 - 服务分离提升可测试性和维护性 - 性能优化减少嵌套循环和重复计算 确保漏洞扫描插件列表与实际执行插件保持精确一致。 --- Common/i18n/messages/parse.go | 4 + Common/i18n/messages/scan.go | 82 +++++++++++++ Core/BaseScanStrategy.go | 212 ++++++++++++++++++++++++++++++++ Core/LocalScanner.go | 80 ++---------- Core/PortDiscoveryService.go | 98 +++++++++++++++ Core/ServiceScanner.go | 222 +++++++++------------------------- Core/WebScanner.go | 80 ++---------- 7 files changed, 476 insertions(+), 302 deletions(-) create mode 100644 Core/BaseScanStrategy.go create mode 100644 Core/PortDiscoveryService.go diff --git a/Common/i18n/messages/parse.go b/Common/i18n/messages/parse.go index 246f364..667b194 100644 --- a/Common/i18n/messages/parse.go +++ b/Common/i18n/messages/parse.go @@ -276,4 +276,8 @@ var ParseMessages = map[string]map[string]string{ LangZH: "无效的扫描模式: %s", LangEN: "Invalid scan mode: %s", }, + "parse_error_invalid_target_format": { + LangZH: "无效的目标地址格式: %s", + LangEN: "Invalid target address format: %s", + }, } \ No newline at end of file diff --git a/Common/i18n/messages/scan.go b/Common/i18n/messages/scan.go index 9c9aa81..00830f9 100644 --- a/Common/i18n/messages/scan.go +++ b/Common/i18n/messages/scan.go @@ -73,6 +73,88 @@ var ScanMessages = map[string]map[string]string{ LangEN: "Scan error %v:%v - %v", }, + // ========================= 扫描器插件消息 ========================= + "scan_local_start": { + LangZH: "开始本地信息收集", + LangEN: "Starting local information collection", + }, + "scan_service_start": { + LangZH: "开始服务扫描", + LangEN: "Starting service scan", + }, + "scan_web_start": { + LangZH: "开始Web扫描", + LangEN: "Starting web scan", + }, + "scan_general_start": { + LangZH: "开始扫描", + LangEN: "Starting scan", + }, + "scan_mode_local_prefix": { + LangZH: "本地模式", + LangEN: "Local mode", + }, + "scan_mode_service_prefix": { + LangZH: "服务模式", + LangEN: "Service mode", + }, + "scan_mode_web_prefix": { + LangZH: "Web模式", + LangEN: "Web mode", + }, + "scan_plugins_local": { + LangZH: "使用本地插件: %s", + LangEN: "Using local plugins: %s", + }, + "scan_plugins_service": { + LangZH: "使用服务插件: %s", + LangEN: "Using service plugins: %s", + }, + "scan_plugins_web": { + LangZH: "使用Web插件: %s", + LangEN: "Using web plugins: %s", + }, + "scan_plugins_custom_specified": { + LangZH: "使用指定插件: %s", + LangEN: "Using specified plugins: %s", + }, + "scan_no_local_plugins": { + LangZH: "未找到可用的本地插件", + LangEN: "No available local plugins found", + }, + "scan_no_web_plugins": { + LangZH: "未找到可用的Web插件", + LangEN: "No available web plugins found", + }, + "scan_strategy_local_name": { + LangZH: "本地扫描", + LangEN: "Local Scan", + }, + "scan_strategy_local_desc": { + LangZH: "收集本地系统信息", + LangEN: "Collect local system information", + }, + "scan_strategy_service_name": { + LangZH: "服务扫描", + LangEN: "Service Scan", + }, + "scan_strategy_service_desc": { + LangZH: "扫描主机服务和漏洞", + LangEN: "Scan host services and vulnerabilities", + }, + "scan_strategy_web_name": { + LangZH: "Web扫描", + LangEN: "Web Scan", + }, + "scan_strategy_web_desc": { + LangZH: "扫描Web应用漏洞和信息", + LangEN: "Scan web application vulnerabilities and information", + }, + "scan_alive_hosts_count": { + LangZH: "存活主机数量: %d", + LangEN: "Alive hosts count: %d", + }, + // ========================= 进度条消息 ========================= "progress_scanning_description": { LangZH: "扫描进度", diff --git a/Core/BaseScanStrategy.go b/Core/BaseScanStrategy.go new file mode 100644 index 0000000..98a1032 --- /dev/null +++ b/Core/BaseScanStrategy.go @@ -0,0 +1,212 @@ +package core + +import ( + "fmt" + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" + "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) { + // 如果指定了特定插件且不是"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 _, exists := common.PluginManager[name]; exists { + validPlugins = append(validPlugins, name) + } + } + + return validPlugins, true + } + + // 未指定或使用"all":获取所有插件,由IsPluginApplicable做类型过滤 + return GetAllPlugins(), false +} + +// GetApplicablePlugins 获取适用的插件列表(用于日志显示) +func (b *BaseScanStrategy) GetApplicablePlugins(allPlugins []string, isCustomMode bool) []string { + if isCustomMode { + return allPlugins + } + + var applicablePlugins []string + for _, pluginName := range allPlugins { + plugin, exists := common.PluginManager[pluginName] + if !exists { + continue + } + + if b.isPluginTypeMatched(plugin) { + applicablePlugins = append(applicablePlugins, pluginName) + } + } + + return applicablePlugins +} + +// isPluginTypeMatched 检查插件类型是否匹配过滤器 +func (b *BaseScanStrategy) isPluginTypeMatched(plugin common.ScanPlugin) bool { + switch b.filterType { + case FilterLocal: + return plugin.HasType(common.PluginTypeLocal) + case FilterService: + return !plugin.HasType(common.PluginTypeLocal) + case FilterWeb: + return plugin.HasType(common.PluginTypeWeb) + default: + return true + } +} + +// IsPluginApplicable 判断插件是否适用(通用实现) +func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool { + // 自定义模式下运行所有明确指定的插件 + if isCustomMode { + return true + } + + // 检查插件类型过滤 + if !b.isPluginTypeMatched(plugin) { + 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")) + } +} \ No newline at end of file diff --git a/Core/LocalScanner.go b/Core/LocalScanner.go index e55cfe9..fbe87d2 100644 --- a/Core/LocalScanner.go +++ b/Core/LocalScanner.go @@ -1,36 +1,40 @@ package core import ( - "fmt" "github.com/shadow1ng/fscan/common" - "strings" + "github.com/shadow1ng/fscan/common/i18n" "sync" ) // LocalScanStrategy 本地扫描策略 -type LocalScanStrategy struct{} +type LocalScanStrategy struct { + *BaseScanStrategy +} // NewLocalScanStrategy 创建新的本地扫描策略 func NewLocalScanStrategy() *LocalScanStrategy { - return &LocalScanStrategy{} + return &LocalScanStrategy{ + BaseScanStrategy: NewBaseScanStrategy("本地扫描", FilterLocal), + } } // Name 返回策略名称 func (s *LocalScanStrategy) Name() string { - return "本地扫描" + return i18n.GetText("scan_strategy_local_name") } // Description 返回策略描述 func (s *LocalScanStrategy) Description() string { - return "收集本地系统信息" + return i18n.GetText("scan_strategy_local_desc") } // Execute 执行本地扫描策略 func (s *LocalScanStrategy) Execute(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { - common.LogBase("执行本地信息收集") + // 输出扫描开始信息 + s.LogScanStart() // 验证插件配置 - if err := validateScanPlugins(); err != nil { + if err := s.ValidateConfiguration(); err != nil { common.LogError(err.Error()) return } @@ -50,63 +54,3 @@ func (s *LocalScanStrategy) PrepareTargets(info common.HostInfo) []common.HostIn // 本地扫描只使用传入的目标信息,不做额外处理 return []common.HostInfo{info} } - -// GetPlugins 获取本地扫描插件列表 -func (s *LocalScanStrategy) GetPlugins() ([]string, bool) { - // 如果指定了特定插件且不是"all" - if common.ScanMode != "" && common.ScanMode != "all" { - requestedPlugins := parsePluginList(common.ScanMode) - if len(requestedPlugins) == 0 { - requestedPlugins = []string{common.ScanMode} - } - - // 验证插件是否存在,不做Local类型过滤 - var validPlugins []string - for _, name := range requestedPlugins { - if _, exists := common.PluginManager[name]; exists { - validPlugins = append(validPlugins, name) - } - } - - return validPlugins, true - } - - // 未指定或使用"all":获取所有插件,由IsPluginApplicable做类型过滤 - return GetAllPlugins(), false -} - -// LogPluginInfo 输出本地扫描插件信息 -func (s *LocalScanStrategy) LogPluginInfo() { - allPlugins, isCustomMode := s.GetPlugins() - - // 如果是自定义模式,直接显示用户指定的插件 - if isCustomMode { - common.LogBase(fmt.Sprintf("本地模式: 使用指定插件: %s", strings.Join(allPlugins, ", "))) - return - } - - // 在自动模式下,只显示Local类型的插件 - var applicablePlugins []string - for _, pluginName := range allPlugins { - plugin, exists := common.PluginManager[pluginName] - if exists && plugin.HasType(common.PluginTypeLocal) { - applicablePlugins = append(applicablePlugins, pluginName) - } - } - - if len(applicablePlugins) > 0 { - common.LogBase(fmt.Sprintf("本地模式: 使用本地插件: %s", strings.Join(applicablePlugins, ", "))) - } else { - common.LogBase("本地模式: 未找到可用的本地插件") - } -} - -// IsPluginApplicable 判断插件是否适用于本地扫描 -func (s *LocalScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool { - // 自定义模式下运行所有明确指定的插件 - if isCustomMode { - return true - } - // 非自定义模式下,只运行Local类型插件 - return plugin.HasType(common.PluginTypeLocal) -} diff --git a/Core/PortDiscoveryService.go b/Core/PortDiscoveryService.go new file mode 100644 index 0000000..8a3965f --- /dev/null +++ b/Core/PortDiscoveryService.go @@ -0,0 +1,98 @@ +package core + +import ( + "fmt" + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" + "github.com/shadow1ng/fscan/common/parsers" + "strings" +) + +/* +PortDiscoveryService.go - 端口发现服务 + +负责主机存活检测、端口扫描等端口发现相关功能, +从ServiceScanner中分离出来提高代码可维护性。 +*/ + +// PortDiscoveryService 端口发现服务 +type PortDiscoveryService struct{} + +// NewPortDiscoveryService 创建端口发现服务 +func NewPortDiscoveryService() *PortDiscoveryService { + return &PortDiscoveryService{} +} + +// DiscoverTargets 发现目标主机和端口 +func (p *PortDiscoveryService) DiscoverTargets(hostInput string, baseInfo common.HostInfo) ([]common.HostInfo, error) { + // 解析目标主机 + hosts, err := parsers.ParseIP(hostInput, common.HostsFile, common.ExcludeHosts) + if err != nil { + return nil, fmt.Errorf(i18n.GetText("parse_error_target_failed"), err) + } + + var targetInfos []common.HostInfo + + // 主机存活性检测和端口扫描 + if len(hosts) > 0 || len(common.HostPort) > 0 { + // 主机存活检测 + if p.shouldPerformLivenessCheck(hosts) { + hosts = CheckLive(hosts, false) + common.LogBase(i18n.GetText("scan_alive_hosts_count", len(hosts))) + } + + // 端口扫描 + alivePorts := p.discoverAlivePorts(hosts) + if len(alivePorts) > 0 { + targetInfos = p.convertToTargetInfos(alivePorts, baseInfo) + } + } + + return targetInfos, nil +} + +// shouldPerformLivenessCheck 判断是否需要执行存活性检测 +func (p *PortDiscoveryService) shouldPerformLivenessCheck(hosts []string) bool { + return common.DisablePing == false && len(hosts) > 1 +} + +// discoverAlivePorts 发现存活的端口 +func (p *PortDiscoveryService) discoverAlivePorts(hosts []string) []string { + var alivePorts []string + + // 根据扫描模式选择端口扫描方式 + if len(hosts) > 0 { + alivePorts = EnhancedPortScan(hosts, common.Ports, common.Timeout) + common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts))) + } + + // 合并额外指定的端口 + if len(common.HostPort) > 0 { + alivePorts = append(alivePorts, common.HostPort...) + alivePorts = common.RemoveDuplicate(alivePorts) + common.HostPort = nil + common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts))) + } + + return alivePorts +} + +// convertToTargetInfos 将端口列表转换为目标信息 +func (p *PortDiscoveryService) convertToTargetInfos(ports []string, baseInfo common.HostInfo) []common.HostInfo { + var infos []common.HostInfo + + for _, targetIP := range ports { + hostParts := strings.Split(targetIP, ":") + if len(hostParts) != 2 { + common.LogError(i18n.GetText("parse_error_invalid_target_format", targetIP)) + continue + } + + info := baseInfo + info.Host = hostParts[0] + info.Ports = hostParts[1] + infos = append(infos, info) + } + + return infos +} \ No newline at end of file diff --git a/Core/ServiceScanner.go b/Core/ServiceScanner.go index be5a2d8..5c81f34 100644 --- a/Core/ServiceScanner.go +++ b/Core/ServiceScanner.go @@ -1,80 +1,70 @@ package core import ( - "fmt" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common/i18n" - "github.com/shadow1ng/fscan/common/parsers" "strconv" "strings" "sync" ) // ServiceScanStrategy 服务扫描策略 -type ServiceScanStrategy struct{} +type ServiceScanStrategy struct { + *BaseScanStrategy + portDiscovery *PortDiscoveryService +} // NewServiceScanStrategy 创建新的服务扫描策略 func NewServiceScanStrategy() *ServiceScanStrategy { - return &ServiceScanStrategy{} + return &ServiceScanStrategy{ + BaseScanStrategy: NewBaseScanStrategy("服务扫描", FilterService), + portDiscovery: NewPortDiscoveryService(), + } } // Name 返回策略名称 func (s *ServiceScanStrategy) Name() string { - return "服务扫描" + return i18n.GetText("scan_strategy_service_name") } // Description 返回策略描述 func (s *ServiceScanStrategy) Description() string { - return "扫描主机服务和漏洞" + return i18n.GetText("scan_strategy_service_desc") } // Execute 执行服务扫描策略 func (s *ServiceScanStrategy) Execute(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { // 验证扫描目标 if info.Host == "" { - common.LogError("未指定扫描目标") + common.LogError(i18n.GetText("parse_error_target_empty")) return } + // 输出扫描开始信息 + s.LogScanStart() + // 验证插件配置 - if err := validateScanPlugins(); err != nil { + if err := s.ValidateConfiguration(); err != nil { common.LogError(err.Error()) return } - // 解析目标主机 - hosts, err := parsers.ParseIP(info.Host, common.HostsFile, common.ExcludeHosts) - if err != nil { - common.LogError(fmt.Sprintf("解析主机错误: %v", err)) - return - } - common.LogBase(i18n.GetText("scan_host_start")) // 输出插件信息 s.LogPluginInfo() // 执行主机扫描流程 - s.performHostScan(hosts, info, ch, wg) + s.performHostScan(info, ch, wg) } // performHostScan 执行主机扫描的完整流程 -func (s *ServiceScanStrategy) performHostScan(hosts []string, info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { - var targetInfos []common.HostInfo - - // 主机存活性检测和端口扫描 - if len(hosts) > 0 || len(common.HostPort) > 0 { - // 主机存活检测 - if s.shouldPerformLivenessCheck(hosts) { - hosts = CheckLive(hosts, false) - common.LogBase(fmt.Sprintf("存活主机数量: %d", len(hosts))) - } - - // 端口扫描 - alivePorts := s.discoverAlivePorts(hosts) - if len(alivePorts) > 0 { - targetInfos = s.convertToTargetInfos(alivePorts, info) - } +func (s *ServiceScanStrategy) performHostScan(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { + // 使用端口发现服务发现目标 + targetInfos, err := s.portDiscovery.DiscoverTargets(info.Host, info) + if err != nil { + common.LogError(err.Error()) + return } // 执行漏洞扫描 @@ -86,161 +76,55 @@ func (s *ServiceScanStrategy) performHostScan(hosts []string, info common.HostIn } } -// shouldPerformLivenessCheck 判断是否需要执行存活性检测 -func (s *ServiceScanStrategy) shouldPerformLivenessCheck(hosts []string) bool { - return common.DisablePing == false && len(hosts) > 1 -} - -// discoverAlivePorts 发现存活的端口 -func (s *ServiceScanStrategy) discoverAlivePorts(hosts []string) []string { - var alivePorts []string - - // 根据扫描模式选择端口扫描方式 - if len(hosts) > 0 { - alivePorts = EnhancedPortScan(hosts, common.Ports, common.Timeout) - common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts))) - } - - // 合并额外指定的端口 - if len(common.HostPort) > 0 { - alivePorts = append(alivePorts, common.HostPort...) - alivePorts = common.RemoveDuplicate(alivePorts) - common.HostPort = nil - common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts))) - } - - return alivePorts -} - // PrepareTargets 准备目标信息 func (s *ServiceScanStrategy) PrepareTargets(info common.HostInfo) []common.HostInfo { - // 解析目标主机 - hosts, err := parsers.ParseIP(info.Host, common.HostsFile, common.ExcludeHosts) + // 使用端口发现服务发现目标 + targetInfos, err := s.portDiscovery.DiscoverTargets(info.Host, info) if err != nil { - common.LogError(fmt.Sprintf("解析主机错误: %v", err)) + common.LogError(err.Error()) return nil } - - var targetInfos []common.HostInfo - - // 主机存活性检测和端口扫描 - if len(hosts) > 0 || len(common.HostPort) > 0 { - // 主机存活检测 - if s.shouldPerformLivenessCheck(hosts) { - hosts = CheckLive(hosts, false) - } - - // 端口扫描 - alivePorts := s.discoverAlivePorts(hosts) - if len(alivePorts) > 0 { - targetInfos = s.convertToTargetInfos(alivePorts, info) - } - } - return targetInfos } -// convertToTargetInfos 将端口列表转换为目标信息 -func (s *ServiceScanStrategy) convertToTargetInfos(ports []string, baseInfo common.HostInfo) []common.HostInfo { - var infos []common.HostInfo - - for _, targetIP := range ports { - hostParts := strings.Split(targetIP, ":") - if len(hostParts) != 2 { - common.LogError(fmt.Sprintf("无效的目标地址格式: %s", targetIP)) - continue - } - - info := baseInfo - info.Host = hostParts[0] - info.Ports = hostParts[1] - infos = append(infos, info) - } - - return infos -} - -// GetPlugins 获取服务扫描插件列表 -func (s *ServiceScanStrategy) GetPlugins() ([]string, bool) { - // 如果指定了插件列表且不是"all" - if common.ScanMode != "" && common.ScanMode != "all" { - plugins := parsePluginList(common.ScanMode) - if len(plugins) > 0 { - return plugins, true - } - return []string{common.ScanMode}, true - } - - // 未指定或使用"all":获取所有插件,由IsPluginApplicable做类型过滤 - return GetAllPlugins(), false -} - -// LogPluginInfo 输出服务扫描插件信息 -func (s *ServiceScanStrategy) LogPluginInfo() { - allPlugins, isCustomMode := s.GetPlugins() - - // 如果是自定义模式,直接显示用户指定的插件 - if isCustomMode { - common.LogBase(fmt.Sprintf("使用指定插件: %s", strings.Join(allPlugins, ", "))) - return - } - - // 在自动模式下,过滤掉本地插件,只显示服务类型插件 - var applicablePlugins []string - for _, pluginName := range allPlugins { - plugin, exists := common.PluginManager[pluginName] - if exists && !plugin.HasType(common.PluginTypeLocal) { - applicablePlugins = append(applicablePlugins, pluginName) - } - } - - if len(applicablePlugins) > 0 { - common.LogBase(fmt.Sprintf("使用服务插件: %s", strings.Join(applicablePlugins, ", "))) - } else { - common.LogBase(i18n.GetText("scan_no_service_plugins")) - } -} - // LogVulnerabilityPluginInfo 输出漏洞扫描插件信息 func (s *ServiceScanStrategy) LogVulnerabilityPluginInfo(targets []common.HostInfo) { allPlugins, isCustomMode := s.GetPlugins() - // 获取实际会被使用的插件列表 - var vulnerabilityPlugins []string - pluginUsed := make(map[string]bool) - + // 收集所有目标端口用于插件适用性检查 + portSet := make(map[int]bool) for _, target := range targets { - targetPort := 0 if target.Ports != "" { - targetPort, _ = strconv.Atoi(target.Ports) + if port, err := strconv.Atoi(target.Ports); err == nil { + portSet[port] = true + } + } + } + + // 获取实际会被使用的插件列表(优化版本) + var vulnerabilityPlugins []string + for _, pluginName := range allPlugins { + plugin, exists := common.PluginManager[pluginName] + if !exists { + continue } - for _, pluginName := range allPlugins { - plugin, exists := common.PluginManager[pluginName] - if !exists { - continue - } - - // 检查插件是否适用于当前目标(使用与ExecuteScanTasks相同的逻辑) - if s.IsPluginApplicable(plugin, targetPort, isCustomMode) { - if !pluginUsed[pluginName] { - vulnerabilityPlugins = append(vulnerabilityPlugins, pluginName) - pluginUsed[pluginName] = true - } - } + // 检查插件是否对任何目标端口适用 + if s.isPluginApplicableToAnyPort(plugin, portSet, isCustomMode) { + vulnerabilityPlugins = append(vulnerabilityPlugins, pluginName) } } // 输出插件信息 if len(vulnerabilityPlugins) > 0 { - common.LogBase(fmt.Sprintf(i18n.GetText("scan_vulnerability_plugins"), strings.Join(vulnerabilityPlugins, ", "))) + common.LogBase(i18n.GetText("scan_vulnerability_plugins", strings.Join(vulnerabilityPlugins, ", "))) } else { common.LogBase(i18n.GetText("scan_no_vulnerability_plugins")) } } -// IsPluginApplicable 判断插件是否适用于服务扫描 -func (s *ServiceScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool { +// isPluginApplicableToAnyPort 检查插件是否对任何端口适用(性能优化) +func (s *ServiceScanStrategy) isPluginApplicableToAnyPort(plugin common.ScanPlugin, portSet map[int]bool, isCustomMode bool) bool { // 自定义模式下运行所有明确指定的插件 if isCustomMode { return true @@ -251,11 +135,17 @@ func (s *ServiceScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targe return false } - // 检查端口是否匹配 - if len(plugin.Ports) > 0 && targetPort > 0 { - return plugin.HasPort(targetPort) + // 无端口限制的插件适用于所有端口 + if len(plugin.Ports) == 0 { + return true } - // 无端口限制的插件或适用于服务扫描的插件 - return len(plugin.Ports) == 0 || plugin.HasType(common.PluginTypeService) + // 有端口限制的插件:检查是否匹配任何目标端口 + for port := range portSet { + if plugin.HasPort(port) { + return true + } + } + + return false } diff --git a/Core/WebScanner.go b/Core/WebScanner.go index ed54f25..97f9932 100644 --- a/Core/WebScanner.go +++ b/Core/WebScanner.go @@ -1,36 +1,41 @@ package core import ( - "fmt" "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" "strings" "sync" ) // WebScanStrategy Web扫描策略 -type WebScanStrategy struct{} +type WebScanStrategy struct { + *BaseScanStrategy +} // NewWebScanStrategy 创建新的Web扫描策略 func NewWebScanStrategy() *WebScanStrategy { - return &WebScanStrategy{} + return &WebScanStrategy{ + BaseScanStrategy: NewBaseScanStrategy("Web扫描", FilterWeb), + } } // Name 返回策略名称 func (s *WebScanStrategy) Name() string { - return "Web扫描" + return i18n.GetText("scan_strategy_web_name") } // Description 返回策略描述 func (s *WebScanStrategy) Description() string { - return "扫描Web应用漏洞和信息" + return i18n.GetText("scan_strategy_web_desc") } // Execute 执行Web扫描策略 func (s *WebScanStrategy) Execute(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { - common.LogBase("开始Web扫描") + // 输出扫描开始信息 + s.LogScanStart() // 验证插件配置 - if err := validateScanPlugins(); err != nil { + if err := s.ValidateConfiguration(); err != nil { common.LogError(err.Error()) return } @@ -62,64 +67,3 @@ func (s *WebScanStrategy) PrepareTargets(baseInfo common.HostInfo) []common.Host return targetInfos } -// GetPlugins 获取Web扫描插件列表 -func (s *WebScanStrategy) GetPlugins() ([]string, bool) { - // 如果指定了自定义插件并且不是"all" - if common.ScanMode != "" && common.ScanMode != "all" { - requestedPlugins := parsePluginList(common.ScanMode) - if len(requestedPlugins) == 0 { - requestedPlugins = []string{common.ScanMode} - } - - // 验证插件是否存在,不做Web类型过滤 - var validPlugins []string - for _, name := range requestedPlugins { - if _, exists := common.PluginManager[name]; exists { - validPlugins = append(validPlugins, name) - } - } - - if len(validPlugins) > 0 { - return validPlugins, true - } - } - - // 未指定或使用"all":获取所有插件,由IsPluginApplicable做类型过滤 - return GetAllPlugins(), false -} - -// LogPluginInfo 输出Web扫描插件信息 -func (s *WebScanStrategy) LogPluginInfo() { - allPlugins, isCustomMode := s.GetPlugins() - - // 如果是自定义模式,直接显示用户指定的插件 - if isCustomMode { - common.LogBase(fmt.Sprintf("Web扫描模式: 使用指定插件: %s", strings.Join(allPlugins, ", "))) - return - } - - // 在自动模式下,只显示Web类型的插件 - var applicablePlugins []string - for _, pluginName := range allPlugins { - plugin, exists := common.PluginManager[pluginName] - if exists && plugin.HasType(common.PluginTypeWeb) { - applicablePlugins = append(applicablePlugins, pluginName) - } - } - - if len(applicablePlugins) > 0 { - common.LogBase(fmt.Sprintf("Web扫描模式: 使用Web插件: %s", strings.Join(applicablePlugins, ", "))) - } else { - common.LogBase("Web扫描模式: 未找到可用的Web插件") - } -} - -// IsPluginApplicable 判断插件是否适用于Web扫描 -func (s *WebScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool { - // 自定义模式下运行所有明确指定的插件 - if isCustomMode { - return true - } - // 非自定义模式下,只运行Web类型插件 - return plugin.HasType(common.PluginTypeWeb) -}