fscan/Core/BaseScanStrategy.go
ZacharyZcR eeaa4c3b3a feat: 实现本地插件架构迁移与统一管理
- 新建本地插件统一架构,包含接口定义和基础类
- 实现三个本地插件:fileinfo(文件信息收集)、dcinfo(域控信息收集)、minidump(内存转储)
- 添加-localplugin参数,支持指定单个本地插件执行
- 完善参数验证机制,本地模式必须指定插件
- 集成新插件系统到核心扫描策略
- 修复Go方法调用机制导致的插件执行问题
- 支持跨平台和权限检查功能

支持的本地插件:
- fileinfo: 敏感文件扫描
- dcinfo: Windows域控信息收集
- minidump: lsass进程内存转储

使用方式: fscan -local -localplugin <plugin_name>
2025-08-09 22:43:28 +08:00

305 lines
8.5 KiB
Go
Raw Permalink 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:
return metadata.Category == "service"
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:
return !plugin.HasType(common.PluginTypeLocal)
case FilterWeb:
return plugin.HasType(common.PluginTypeWeb)
default:
return true
}
}
// IsPluginApplicableByName 根据插件名称判断是否适用(新方法)
func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName 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, 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
}
return true
}
// shouldIncludeWebPlugin 判断是否应该包含Web插件智能检测
func (b *BaseScanStrategy) shouldIncludeWebPlugin(metadata *base.PluginMetadata, 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.IsCommonWebPort(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
}
// 对于服务扫描,还需检查端口匹配
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"))
}
}