mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00

- 实现本地插件严格单个指定控制,拒绝多插件分隔符 - 修复本地插件自动调用问题,避免不必要的插件实例创建 - 添加-local与-h/-u参数的互斥性检查 - 优化插件存在性检查,使用pluginExists()替代plugins.Get() - 完善统一插件系统的端口信息管理 - 增强Web插件的协议智能检测功能 主要变更: * 本地插件现在只能通过-local参数明确指定单个插件运行 * 插件适用性检查不再创建不必要的插件实例,提升性能 * 本地扫描与网络扫描参数完全隔离,避免配置冲突
284 lines
7.9 KiB
Go
284 lines
7.9 KiB
Go
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
|
||
} |