fscan/core/BaseScanStrategy.go
ZacharyZcR 9ad397f58f perf: 使用单例模式消除重复协议检测
核心修复:
- 将WebPortDetector改为全局单例,消除多实例问题
- 所有协议检测共享同一个缓存,避免重复TCP连接
- 使用sync.Once确保线程安全的单例初始化

性能提升:
- 每个端口的协议检测从多次减少到1次
- 大幅降低TCP连接数,减少网络开销
- 缓存命中率显著提升

技术实现:
- GetWebPortDetector() 替代 NewWebPortDetector()
- newWebPortDetector() 改为私有方法
- BaseScanStrategy统一使用单例实例

这是数据结构决定性能的经典案例 - 通过正确的实例管理
彻底解决了重复检测问题
2025-09-02 00:06:17 +00:00

352 lines
10 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 b.getPluginsByFilterType(), 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
}
// 应用过滤器类型检查,确保与传统接口逻辑一致
switch b.filterType {
case FilterLocal:
// 本地扫描策略:只允许本地插件且必须通过-local参数明确指定
if b.isLocalPlugin(pluginName) {
result := b.isLocalPluginExplicitlySpecified(pluginName)
common.LogDebug(fmt.Sprintf("本地插件 %s 检查结果: %v (LocalPlugin='%s')", pluginName, result, common.LocalPlugin))
return result
}
return false
case FilterService:
// 服务扫描策略:排除本地插件,本地插件不应在端口扫描中被调用
if b.isLocalPlugin(pluginName) {
common.LogDebug(fmt.Sprintf("本地插件 %s 被服务扫描策略过滤(本地插件不应与端口关联)", pluginName))
return false
}
case FilterWeb:
// Web扫描策略只允许Web插件
if !b.isWebPlugin(pluginName) {
return false
}
default:
// 无过滤器:允许所有插件类型
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插件特殊处理仅在用户未指定端口范围时才进行智能检测
// 如果用户明确指定了端口(如 -p 3306则只对该端口进行检测不使用预定义端口列表
if len(pluginPorts) == 0 && b.isWebPlugin(pluginName) {
// 检查用户是否指定了具体端口范围
if common.Ports != "" && common.Ports != "all" {
// 用户指定了端口进行实际HTTP协议检测而不是预定义端口匹配
return b.isWebServicePortByProtocol(targetHost, targetPort)
} else {
// 用户未指定端口,使用智能检测(包含预定义端口)
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 {
// 使用统一插件系统的Exists方法避免创建实例和遍历
return plugins.Exists(pluginName)
}
// 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 := GetWebPortDetector()
return detector.IsWebService(host, port)
}
// isWebServicePortByProtocol 仅通过HTTP协议检测判断端口是否运行Web服务不使用预定义端口列表
func (b *BaseScanStrategy) isWebServicePortByProtocol(host string, port int) bool {
// 使用全局单例Web端口检测器
detector := GetWebPortDetector()
// 直接调用协议检测,跳过预定义端口检查
return detector.DetectHTTPServiceOnly(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("开始扫描")
}
}
// getPluginsByFilterType 根据过滤器类型获取插件列表
func (b *BaseScanStrategy) getPluginsByFilterType() []string {
allPlugins := plugins.All()
var filteredPlugins []string
switch b.filterType {
case FilterLocal:
// 本地扫描策略:只返回本地插件
for _, pluginName := range allPlugins {
if b.isLocalPlugin(pluginName) {
filteredPlugins = append(filteredPlugins, pluginName)
}
}
case FilterService:
// 服务扫描策略排除本地插件和纯Web插件保留服务插件
for _, pluginName := range allPlugins {
if !b.isLocalPlugin(pluginName) {
filteredPlugins = append(filteredPlugins, pluginName)
}
}
case FilterWeb:
// Web扫描策略只返回Web插件
for _, pluginName := range allPlugins {
if b.isWebPlugin(pluginName) {
filteredPlugins = append(filteredPlugins, pluginName)
}
}
default:
// 无过滤器:返回所有插件
filteredPlugins = allPlugins
}
return filteredPlugins
}
// 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
}