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

主要修复: 1. 修复时间显示Bug - StartTime初始化问题 2. 修复Web智能探测错误检测预定义端口而非用户指定端口 3. 修复本地插件被错误调用到端口扫描中的问题 4. 修复host:port格式双重处理导致的多余端口扫描 5. 统一插件过滤逻辑,消除接口不一致性 6. 优化Web检测缓存机制,减少重复HTTP请求 技术改进: - 重构插件适用性检查逻辑,确保策略过滤器正确工作 - 区分Web检测的自动发现模式和用户指定端口模式 - 在解析阶段正确处理host:port格式,避免与默认端口冲突 - 完善缓存机制,提升性能 测试验证: - ./fscan -h 127.0.0.1:3306 现在只检测3306端口 - 本地插件不再参与端口扫描 - Web检测只对指定端口进行协议检测 - 时间显示正确
352 lines
10 KiB
Go
352 lines
10 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 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 := NewWebPortDetector()
|
||
return detector.IsWebService(host, port)
|
||
}
|
||
|
||
// isWebServicePortByProtocol 仅通过HTTP协议检测判断端口是否运行Web服务(不使用预定义端口列表)
|
||
func (b *BaseScanStrategy) isWebServicePortByProtocol(host string, port int) bool {
|
||
// 创建Web端口检测器实例
|
||
detector := NewWebPortDetector()
|
||
// 直接调用协议检测,跳过预定义端口检查
|
||
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
|
||
} |