mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
refactor: 4项架构优化 — CEL缓存/POC隔离/服务缓存/结果统一
1. CEL 表达式编译缓存 - 新增 CelProgCache,同一 POC 的所有规则/参数组合共享编译后的 Program - clusterpoc 热路径上消除重复的 Compile+Program 调用 2. POC 全局状态消除 - allPocs/pocLoaded 全局变量改为 pocStore 按 PocPath 缓存 - 不同 PocPath 的扫描独立加载,Web API 并发场景不再互相覆盖 3. serviceCache 下沉到 per-session State - 服务识别缓存从包级全局 map 迁移到 State.serviceCache (sync.Map) - BaseScanStrategy 通过 SetState 注入 session state - 消除多个并发扫描之间的服务识别缓存串台 4. POC 结果输出路径统一 - 提取 buildVulnDetails/buildVulnLogMsg/saveVulnResult 三个公共函数 - CheckMultiPoc 和 recordVulnerabilityResult 共用统一的结果构造逻辑 - 消除 details 字段名不一致和日志格式差异
This commit is contained in:
@@ -29,6 +29,7 @@ const (
|
||||
type BaseScanStrategy struct {
|
||||
strategyName string
|
||||
filterType PluginFilterType
|
||||
state *common.State
|
||||
}
|
||||
|
||||
// NewBaseScanStrategy 创建基础扫描策略
|
||||
@@ -39,6 +40,11 @@ func NewBaseScanStrategy(name string, filterType PluginFilterType) *BaseScanStra
|
||||
}
|
||||
}
|
||||
|
||||
// SetState 注入 session state(用于 per-session 服务缓存)
|
||||
func (b *BaseScanStrategy) SetState(state *common.State) {
|
||||
b.state = state
|
||||
}
|
||||
|
||||
// GetPlugins 获取插件列表
|
||||
func (b *BaseScanStrategy) GetPlugins(config *common.Config) ([]string, bool) {
|
||||
scanMode := config.Mode
|
||||
@@ -123,7 +129,7 @@ func (b *BaseScanStrategy) isLocalPluginExplicitlySpecified(pluginName string, c
|
||||
// 匹配策略:端口匹配 → 服务名称匹配(解决非标准端口问题)
|
||||
func (b *BaseScanStrategy) isPluginApplicableToPortWithHost(pluginName string, targetHost string, targetPort int) bool {
|
||||
if b.isWebPlugin(pluginName) {
|
||||
return IsMarkedWebService(targetHost, targetPort)
|
||||
return IsMarkedWebServiceWithState(b.state, targetHost, targetPort)
|
||||
}
|
||||
|
||||
pluginPorts := b.getPluginPorts(pluginName)
|
||||
@@ -145,7 +151,7 @@ func (b *BaseScanStrategy) isPluginApplicableToPortWithHost(pluginName string, t
|
||||
// 端口不匹配时,按指纹识别结果匹配
|
||||
// 例:8881 端口上识别到 ssh 服务 → ssh 插件应该执行
|
||||
if targetHost != "" && targetPort > 0 {
|
||||
if info, ok := GetCachedServiceInfo(targetHost, targetPort); ok && info != nil {
|
||||
if info, ok := GetCachedServiceInfoWithState(b.state, targetHost, targetPort); ok && info != nil {
|
||||
if strings.EqualFold(info.Name, pluginName) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -100,6 +100,9 @@ func RunScan(ctx context.Context, info common.HostInfo, session *common.ScanSess
|
||||
config := session.Config
|
||||
state := session.State
|
||||
|
||||
// 设置全局 State(兼容旧代码路径中未传 state 的调用)
|
||||
SetGlobalState(state)
|
||||
|
||||
// 初始化HTTP客户端(静默,无需日志)
|
||||
if err := lib.Inithttp(config); err != nil {
|
||||
session.LogError(i18n.Tr("http_client_init_failed", err))
|
||||
@@ -190,6 +193,11 @@ func finishScan(session *common.ScanSession) {
|
||||
func ExecuteScanTasks(ctx context.Context, session *common.ScanSession, targets []common.HostInfo, strategy ScanStrategy, ch chan struct{}, wg *sync.WaitGroup) {
|
||||
config := session.Config
|
||||
|
||||
// 注入 session state 到策略(用于 per-session 服务缓存)
|
||||
if setter, ok := strategy.(interface{ SetState(*common.State) }); ok {
|
||||
setter.SetState(session.State)
|
||||
}
|
||||
|
||||
// 获取要执行的插件
|
||||
pluginsToRun, isCustomMode := strategy.GetPlugins(config)
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"github.com/shadow1ng/fscan/plugins"
|
||||
)
|
||||
|
||||
@@ -22,9 +23,8 @@ func registerTestPlugins(t *testing.T) {
|
||||
}
|
||||
|
||||
func clearServiceCache() {
|
||||
serviceCacheMutex.Lock()
|
||||
serviceCache = make(map[string]*ServiceInfo)
|
||||
serviceCacheMutex.Unlock()
|
||||
state := common.NewState()
|
||||
SetGlobalState(state)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
+63
-32
@@ -208,12 +208,9 @@ func (w *WebPortDetector) tryHTTP(ctx context.Context, client *http.Client, sess
|
||||
// 基于服务指纹的Web服务识别
|
||||
// ===============================
|
||||
|
||||
// 服务识别缓存 - 存储所有识别到的服务(不仅限于 Web)
|
||||
// 端口扫描阶段写入,插件匹配阶段读取
|
||||
var (
|
||||
serviceCache = make(map[string]*ServiceInfo)
|
||||
serviceCacheMutex sync.RWMutex
|
||||
)
|
||||
// globalState 全局 State 兼容指针(向后兼容不接受 State 的旧调用方)
|
||||
// 新代码应通过 State 方法访问服务缓存
|
||||
var globalState *common.State
|
||||
|
||||
// IsWebServiceByFingerprint 基于服务指纹判断Web服务 - 保持API兼容
|
||||
// 服务识别规则 - 编译期常量,避免运行时分配
|
||||
@@ -279,50 +276,84 @@ func isDefinitelyNonWeb(serviceInfo *ServiceInfo) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// CacheServiceInfo 缓存识别到的服务信息
|
||||
func CacheServiceInfo(host string, port int, serviceInfo *ServiceInfo) {
|
||||
cacheKey := net.JoinHostPort(host, strconv.Itoa(port))
|
||||
|
||||
serviceCacheMutex.Lock()
|
||||
defer serviceCacheMutex.Unlock()
|
||||
|
||||
serviceCache[cacheKey] = serviceInfo
|
||||
// SetGlobalState 设置全局 State(RunScan 入口调用,兼容旧代码路径)
|
||||
func SetGlobalState(state *common.State) {
|
||||
globalState = state
|
||||
}
|
||||
|
||||
// MarkAsWebService 标记 Web 服务(兼容旧调用)
|
||||
func resolveState(state *common.State) *common.State {
|
||||
if state != nil {
|
||||
return state
|
||||
}
|
||||
return globalState
|
||||
}
|
||||
|
||||
// CacheServiceInfoWithState 缓存服务信息到指定 State
|
||||
func CacheServiceInfoWithState(state *common.State, host string, port int, serviceInfo *ServiceInfo) {
|
||||
s := resolveState(state)
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
key := net.JoinHostPort(host, strconv.Itoa(port))
|
||||
s.CacheService(key, serviceInfo)
|
||||
}
|
||||
|
||||
// CacheServiceInfo 兼容旧调用(使用全局 State)
|
||||
func CacheServiceInfo(host string, port int, serviceInfo *ServiceInfo) {
|
||||
CacheServiceInfoWithState(nil, host, port, serviceInfo)
|
||||
}
|
||||
|
||||
// MarkAsWebService 标记 Web 服务
|
||||
func MarkAsWebService(host string, port int, serviceInfo *ServiceInfo) {
|
||||
CacheServiceInfo(host, port, serviceInfo)
|
||||
}
|
||||
|
||||
// GetCachedServiceInfo 获取缓存的服务信息
|
||||
func GetCachedServiceInfo(host string, port int) (*ServiceInfo, bool) {
|
||||
cacheKey := net.JoinHostPort(host, strconv.Itoa(port))
|
||||
|
||||
serviceCacheMutex.RLock()
|
||||
defer serviceCacheMutex.RUnlock()
|
||||
|
||||
serviceInfo, exists := serviceCache[cacheKey]
|
||||
return serviceInfo, exists
|
||||
}
|
||||
|
||||
// GetWebServiceInfo 获取 Web 服务信息(兼容旧调用)
|
||||
func GetWebServiceInfo(host string, port int) (*ServiceInfo, bool) {
|
||||
info, exists := GetCachedServiceInfo(host, port)
|
||||
if !exists {
|
||||
// GetCachedServiceInfoWithState 从指定 State 获取缓存的服务信息
|
||||
func GetCachedServiceInfoWithState(state *common.State, host string, port int) (*ServiceInfo, bool) {
|
||||
s := resolveState(state)
|
||||
if s == nil {
|
||||
return nil, false
|
||||
}
|
||||
if !IsWebServiceByFingerprint(info) {
|
||||
key := net.JoinHostPort(host, strconv.Itoa(port))
|
||||
val, ok := s.GetCachedService(key)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
info, ok := val.(*ServiceInfo)
|
||||
return info, ok
|
||||
}
|
||||
|
||||
// GetCachedServiceInfo 兼容旧调用
|
||||
func GetCachedServiceInfo(host string, port int) (*ServiceInfo, bool) {
|
||||
return GetCachedServiceInfoWithState(nil, host, port)
|
||||
}
|
||||
|
||||
// GetWebServiceInfo 获取 Web 服务信息
|
||||
func GetWebServiceInfo(host string, port int) (*ServiceInfo, bool) {
|
||||
return GetWebServiceInfoWithState(nil, host, port)
|
||||
}
|
||||
|
||||
// GetWebServiceInfoWithState 从指定 State 获取 Web 服务信息
|
||||
func GetWebServiceInfoWithState(state *common.State, host string, port int) (*ServiceInfo, bool) {
|
||||
info, exists := GetCachedServiceInfoWithState(state, host, port)
|
||||
if !exists || !IsWebServiceByFingerprint(info) {
|
||||
return nil, false
|
||||
}
|
||||
return info, true
|
||||
}
|
||||
|
||||
// IsMarkedWebService 检查是否为 Web 服务
|
||||
// IsMarkedWebService 检查是否为 Web 服务(使用全局 State)
|
||||
func IsMarkedWebService(host string, port int) bool {
|
||||
_, exists := GetWebServiceInfo(host, port)
|
||||
return exists
|
||||
}
|
||||
|
||||
// IsMarkedWebServiceWithState 检查是否为 Web 服务(指定 State)
|
||||
func IsMarkedWebServiceWithState(state *common.State, host string, port int) bool {
|
||||
_, exists := GetWebServiceInfoWithState(state, host, port)
|
||||
return exists
|
||||
}
|
||||
|
||||
// ===============================
|
||||
// Web扫描策略
|
||||
// ===============================
|
||||
|
||||
@@ -440,9 +440,7 @@ func TestCreateTargetFromURL(t *testing.T) {
|
||||
// TestWebServiceCache 测试Web服务缓存操作
|
||||
func TestWebServiceCache(t *testing.T) {
|
||||
// 清空缓存
|
||||
serviceCacheMutex.Lock()
|
||||
serviceCache = make(map[string]*ServiceInfo)
|
||||
serviceCacheMutex.Unlock()
|
||||
SetGlobalState(common.NewState())
|
||||
|
||||
t.Run("存储和读取", func(t *testing.T) {
|
||||
serviceInfo := &ServiceInfo{
|
||||
@@ -517,9 +515,7 @@ func TestWebServiceCache(t *testing.T) {
|
||||
// TestWebServiceCache_Concurrent 测试并发安全性
|
||||
func TestWebServiceCache_Concurrent(t *testing.T) {
|
||||
// 清空缓存
|
||||
serviceCacheMutex.Lock()
|
||||
serviceCache = make(map[string]*ServiceInfo)
|
||||
serviceCacheMutex.Unlock()
|
||||
SetGlobalState(common.NewState())
|
||||
|
||||
t.Run("不同key并发写入", func(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
Reference in New Issue
Block a user