mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00

- 修复URL正则表达式支持IP地址格式 - 修复URL解析后正确设置到HostInfo.Url字段 - 修复Web扫描目标Host和Port字段的提取 - 修复Web插件适用性检查逻辑,允许Web扫描策略直接执行Web插件 - 完善URL到目标信息的转换,包含协议默认端口处理
332 lines
9.6 KiB
Go
332 lines
9.6 KiB
Go
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"))
|
||
}
|
||
} |