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

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

203 lines
5.5 KiB
Go

package core
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"strconv"
"strings"
"sync"
)
// ServiceScanStrategy 服务扫描策略
type ServiceScanStrategy struct {
*BaseScanStrategy
portDiscovery *PortDiscoveryService
}
// NewServiceScanStrategy 创建新的服务扫描策略
func NewServiceScanStrategy() *ServiceScanStrategy {
return &ServiceScanStrategy{
BaseScanStrategy: NewBaseScanStrategy("服务扫描", FilterService),
portDiscovery: NewPortDiscoveryService(),
}
}
// LogPluginInfo 重写以提供基于端口的插件过滤
func (s *ServiceScanStrategy) LogPluginInfo() {
// 需要从命令行参数获取端口信息来进行过滤
// 如果没有指定端口,使用默认端口进行过滤显示
if common.Ports == "" || common.Ports == "all" {
// 默认端口扫描:显示所有插件
s.BaseScanStrategy.LogPluginInfo()
} else {
// 指定端口扫描:只显示匹配的插件
s.showPluginsForSpecifiedPorts()
}
}
// showPluginsForSpecifiedPorts 显示指定端口的匹配插件
func (s *ServiceScanStrategy) showPluginsForSpecifiedPorts() {
allPlugins, isCustomMode := s.GetPlugins()
// 解析端口
ports := s.parsePortList(common.Ports)
if len(ports) == 0 {
s.BaseScanStrategy.LogPluginInfo()
return
}
// 收集所有匹配的插件(去重)
pluginSet := make(map[string]bool)
for _, port := range ports {
for _, pluginName := range allPlugins {
if s.pluginExists(pluginName) {
if s.IsPluginApplicableByName(pluginName, "127.0.0.1", port, isCustomMode) {
pluginSet[pluginName] = true
}
}
}
}
// 转换为列表
var applicablePlugins []string
for pluginName := range pluginSet {
applicablePlugins = append(applicablePlugins, pluginName)
}
// 输出结果
if len(applicablePlugins) > 0 {
if isCustomMode {
common.LogBase(fmt.Sprintf("服务插件: 自定义指定 (%s)", strings.Join(applicablePlugins, ", ")))
} else {
common.LogBase(fmt.Sprintf("服务插件: %s", strings.Join(applicablePlugins, ", ")))
}
} else {
common.LogBase("服务插件: 无可用插件")
}
}
// parsePortList 解析端口列表
func (s *ServiceScanStrategy) parsePortList(portStr string) []int {
if portStr == "" || portStr == "all" {
return []int{}
}
var ports []int
parts := strings.Split(portStr, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if port, err := strconv.Atoi(part); err == nil {
ports = append(ports, port)
}
}
return ports
}
// Name 返回策略名称
func (s *ServiceScanStrategy) Name() string {
return i18n.GetText("scan_strategy_service_name")
}
// Description 返回策略描述
func (s *ServiceScanStrategy) Description() string {
return i18n.GetText("scan_strategy_service_desc")
}
// Execute 执行服务扫描策略
func (s *ServiceScanStrategy) Execute(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
// 验证扫描目标
if info.Host == "" {
common.LogError(i18n.GetText("parse_error_target_empty"))
return
}
// 输出扫描开始信息
s.LogScanStart()
// 验证插件配置
if err := s.ValidateConfiguration(); err != nil {
common.LogError(err.Error())
return
}
common.LogBase(i18n.GetText("scan_host_start"))
// 输出插件信息(重写以提供端口过滤)
s.LogPluginInfo()
// 执行主机扫描流程
s.performHostScan(info, ch, wg)
}
// performHostScan 执行主机扫描的完整流程
func (s *ServiceScanStrategy) performHostScan(info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
// 使用端口发现服务发现目标
targetInfos, err := s.portDiscovery.DiscoverTargets(info.Host, info)
if err != nil {
common.LogError(err.Error())
return
}
// 执行漏洞扫描
if len(targetInfos) > 0 {
common.LogBase(i18n.GetText("scan_vulnerability_start"))
// 显示即将使用的漏洞扫描插件
s.LogVulnerabilityPluginInfo(targetInfos)
ExecuteScanTasks(targetInfos, s, ch, wg)
}
}
// PrepareTargets 准备目标信息
func (s *ServiceScanStrategy) PrepareTargets(info common.HostInfo) []common.HostInfo {
// 使用端口发现服务发现目标
targetInfos, err := s.portDiscovery.DiscoverTargets(info.Host, info)
if err != nil {
common.LogError(err.Error())
return nil
}
return targetInfos
}
// LogVulnerabilityPluginInfo 输出服务扫描插件信息
func (s *ServiceScanStrategy) LogVulnerabilityPluginInfo(targets []common.HostInfo) {
allPlugins, isCustomMode := s.GetPlugins()
// 收集所有目标端口用于插件适用性检查
portSet := make(map[int]bool)
for _, target := range targets {
if target.Ports != "" {
if port, err := strconv.Atoi(target.Ports); err == nil {
portSet[port] = true
}
}
}
// 获取实际会被使用的插件列表(考虑端口匹配)
var servicePlugins []string
// 提取第一个目标端口用于匹配检查
var firstTargetPort int
if len(targets) > 0 && targets[0].Ports != "" {
firstTargetPort, _ = strconv.Atoi(targets[0].Ports)
}
for _, pluginName := range allPlugins {
// 使用统一插件系统检查插件存在性
if s.pluginExists(pluginName) {
// 检查插件是否适用于目标端口
if s.IsPluginApplicableByName(pluginName, "127.0.0.1", firstTargetPort, isCustomMode) {
servicePlugins = append(servicePlugins, pluginName)
}
}
}
// 输出插件信息
if len(servicePlugins) > 0 {
common.LogBase(i18n.GetText("scan_service_plugins", strings.Join(servicePlugins, ", ")))
} else {
common.LogBase(i18n.GetText("scan_no_service_plugins"))
}
}