fscan/core/BaseScanStrategy.go
ZacharyZcR 43ddb3630d feat: 完善本地插件控制机制和参数验证
- 实现本地插件严格单个指定控制,拒绝多插件分隔符
- 修复本地插件自动调用问题,避免不必要的插件实例创建
- 添加-local与-h/-u参数的互斥性检查
- 优化插件存在性检查,使用pluginExists()替代plugins.Get()
- 完善统一插件系统的端口信息管理
- 增强Web插件的协议智能检测功能

主要变更:
* 本地插件现在只能通过-local参数明确指定单个插件运行
* 插件适用性检查不再创建不必要的插件实例,提升性能
* 本地扫描与网络扫描参数完全隔离,避免配置冲突
2025-08-26 19:34:14 +08:00

284 lines
7.9 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/plugins"
"strings"
)
// 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 b.pluginExists(name) {
validPlugins = append(validPlugins, name)
}
}
return validPlugins, true
}
// 未指定或使用"all":获取所有插件
return plugins.All(), false
}
// IsPluginApplicable 判断插件是否适用(传统接口兼容)
func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件
if isCustomMode {
return true
}
// 检查插件类型过滤
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 !b.pluginExists(pluginName) {
return false
}
// 自定义模式下强制运行所有明确指定的插件n*m调用
if isCustomMode {
return true
}
// 本地插件特殊处理:优先检查,避免不必要的端口获取
if b.isLocalPlugin(pluginName) {
result := b.isLocalPluginExplicitlySpecified(pluginName)
common.LogDebug(fmt.Sprintf("本地插件 %s 检查结果: %v (LocalPlugin='%s')", pluginName, result, common.LocalPlugin))
return result
}
// 检查插件端口匹配(特殊端口自动调用具体插件)
pluginPorts := b.getPluginPorts(pluginName)
// Web插件特殊处理使用智能HTTP检测
if len(pluginPorts) == 0 && b.isWebPlugin(pluginName) {
return b.isWebServicePort(targetHost, targetPort)
}
// 无端口限制的其他插件适用于所有端口
if len(pluginPorts) == 0 {
return true
}
// 有端口限制的插件:检查端口匹配
if targetPort > 0 {
for _, port := range pluginPorts {
if port == targetPort {
return true
}
}
}
return false
}
// pluginExists 检查插件是否存在,不创建实例
func (b *BaseScanStrategy) pluginExists(pluginName string) bool {
// 使用All()获取所有注册插件名称避免调用Get()创建实例
allPlugins := plugins.All()
for _, name := range allPlugins {
if name == pluginName {
return true
}
}
return false
}
// getPluginPorts 获取插件端口列表
func (b *BaseScanStrategy) getPluginPorts(pluginName string) []int {
// 使用统一插件系统获取端口信息
return plugins.GetPluginPorts(pluginName)
}
// isWebPlugin 判断是否为Web插件
func (b *BaseScanStrategy) isWebPlugin(pluginName string) bool {
// 已知的Web插件列表
webPlugins := []string{"webtitle", "webpoc"}
for _, webPlugin := range webPlugins {
if pluginName == webPlugin {
return true
}
}
return false
}
// isLocalPlugin 判断是否为本地插件
func (b *BaseScanStrategy) isLocalPlugin(pluginName string) bool {
// 已知的本地插件列表从RegisterLocalPlugin调用中获取
localPlugins := []string{
"avdetect", "crontask", "cleaner", "dcinfo", "envinfo", "forwardshell",
"minidump", "socks5proxy", "shellenv", "downloader", "reverseshell",
"systemdservice", "fileinfo", "keylogger", "winwmi", "winstartup",
"winschtask", "systeminfo", "winregistry", "ldpreload", "winservice",
}
for _, localPlugin := range localPlugins {
if pluginName == localPlugin {
return true
}
}
return false
}
// isWebServicePort 使用智能检测判断端口是否运行Web服务
func (b *BaseScanStrategy) isWebServicePort(host string, port int) bool {
// 创建Web端口检测器实例
detector := NewWebPortDetector()
return detector.IsWebService(host, port)
}
// isLocalPluginExplicitlySpecified 检查本地插件是否明确通过-local参数指定
func (b *BaseScanStrategy) isLocalPluginExplicitlySpecified(pluginName string) bool {
// 只有通过-local参数明确指定的单个插件才能调用
return common.LocalPlugin == pluginName
}
// LogPluginInfo 输出插件信息简化版将被各Strategy重写
func (b *BaseScanStrategy) LogPluginInfo() {
// 基础实现:显示所有插件(无端口过滤)
// 各个具体Strategy应该重写这个方法以提供更精确的显示
allPlugins, isCustomMode := b.GetPlugins()
var prefix string
switch b.filterType {
case FilterLocal:
prefix = "本地插件"
case FilterService:
prefix = "服务插件"
case FilterWeb:
prefix = "Web插件"
default:
prefix = "插件"
}
if len(allPlugins) > 0 {
if isCustomMode {
common.LogBase(fmt.Sprintf("%s: 自定义指定 (%s)", prefix, strings.Join(allPlugins, ", ")))
} else {
common.LogBase(fmt.Sprintf("%s: %s", prefix, strings.Join(allPlugins, ", ")))
}
} else {
common.LogBase(fmt.Sprintf("%s: 无可用插件", prefix))
}
}
// LogPluginInfoWithPort 带端口信息的插件显示(供子类使用)
func (b *BaseScanStrategy) LogPluginInfoWithPort(targetPort int) {
allPlugins, isCustomMode := b.GetPlugins()
var prefix string
switch b.filterType {
case FilterLocal:
prefix = "本地插件"
case FilterService:
prefix = "服务插件"
case FilterWeb:
prefix = "Web插件"
default:
prefix = "插件"
}
// 过滤适用的插件
var applicablePlugins []string
for _, pluginName := range allPlugins {
if b.pluginExists(pluginName) {
if b.IsPluginApplicableByName(pluginName, "127.0.0.1", targetPort, isCustomMode) {
applicablePlugins = append(applicablePlugins, pluginName)
}
}
}
if len(applicablePlugins) > 0 {
if isCustomMode {
common.LogBase(fmt.Sprintf("%s: 自定义指定 (%s)", prefix, strings.Join(applicablePlugins, ", ")))
} else {
common.LogBase(fmt.Sprintf("%s: %s", prefix, strings.Join(applicablePlugins, ", ")))
}
} else {
common.LogBase(fmt.Sprintf("%s: 无可用插件", prefix))
}
}
// ValidateConfiguration 验证扫描配置
func (b *BaseScanStrategy) ValidateConfiguration() error {
return nil
}
// LogScanStart 输出扫描开始信息
func (b *BaseScanStrategy) LogScanStart() {
switch b.filterType {
case FilterLocal:
common.LogBase("开始本地扫描")
case FilterService:
common.LogBase("开始服务扫描")
case FilterWeb:
common.LogBase("开始Web扫描")
default:
common.LogBase("开始扫描")
}
}
// parsePluginList 解析插件列表字符串
func parsePluginList(pluginStr string) []string {
if pluginStr == "" {
return []string{}
}
// 支持逗号分隔的插件列表
plugins := strings.Split(pluginStr, ",")
var result []string
for _, plugin := range plugins {
plugin = strings.TrimSpace(plugin)
if plugin != "" {
result = append(result, plugin)
}
}
return result
}