fscan/core/BaseScanStrategy.go
ZacharyZcR cc4f55374a fix: 修复Web扫描功能无法正常工作的问题
- 修复URL正则表达式支持IP地址格式
- 修复URL解析后正确设置到HostInfo.Url字段
- 修复Web扫描目标Host和Port字段的提取
- 修复Web插件适用性检查逻辑,允许Web扫描策略直接执行Web插件
- 完善URL到目标信息的转换,包含协议默认端口处理
2025-08-12 17:03:38 +08:00

332 lines
9.6 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 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"))
}
}