mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-25 12:41:53 +08:00
feat: SDK agent integration + UDP plugin framework + SNMP plugin
SDK enhancements for endpoint agent embedding: - ScanWithController for pause/resume and live stats - OnProgress callback for periodic progress reporting - TaskID injection into every scan result - ScanController with goroutine-safe pause/resume/stats - Multi-target stats aggregation (race-free) UDP plugin infrastructure: - PluginTypeUDP registry with dedicated dispatch path - DialUDP on ScanSession with rate limiting and packet counting - UDP plugins bypass TCP port scan, probe targets directly - FilterService excludes UDP plugins from TCP port matching SNMP plugin (first UDP plugin): - SNMPv2c GetRequest probe for sysDescr detection - Community string brute force (public/private/community/etc) - Pure stdlib implementation (encoding/asn1) - Registered as safe default plugin on port 161/UDP Tests: 95.7% SDK coverage, race-free, 50+ new test cases
This commit is contained in:
@@ -106,6 +106,10 @@ func (b *BaseScanStrategy) isLocalPlugin(pluginName string) bool {
|
||||
return plugins.HasType(pluginName, plugins.PluginTypeLocal)
|
||||
}
|
||||
|
||||
func (b *BaseScanStrategy) isUDPPlugin(pluginName string) bool {
|
||||
return plugins.IsUDP(pluginName)
|
||||
}
|
||||
|
||||
func (b *BaseScanStrategy) isLocalPluginExplicitlySpecified(pluginName string, config *common.Config) bool {
|
||||
return config.LocalPlugin == pluginName
|
||||
}
|
||||
@@ -141,6 +145,11 @@ func (b *BaseScanStrategy) isPluginApplicableToPort(pluginName string, targetPor
|
||||
|
||||
// isPluginPassesFilterType 检查插件是否通过过滤器类型检查
|
||||
func (b *BaseScanStrategy) isPluginPassesFilterType(pluginName string, isCustomMode bool, config *common.Config) bool {
|
||||
// UDP 插件有独立分发路径,不参与 TCP 端口匹配流水线
|
||||
if b.isUDPPlugin(pluginName) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 自定义模式下强制运行所有明确指定的插件
|
||||
if isCustomMode {
|
||||
return true
|
||||
@@ -155,8 +164,8 @@ func (b *BaseScanStrategy) isPluginPassesFilterType(pluginName string, isCustomM
|
||||
}
|
||||
return false
|
||||
case FilterService:
|
||||
// 服务扫描策略:排除本地插件
|
||||
return !b.isLocalPlugin(pluginName)
|
||||
// 服务扫描策略:排除本地插件和UDP插件(UDP有独立分发路径)
|
||||
return !b.isLocalPlugin(pluginName) && !b.isUDPPlugin(pluginName)
|
||||
case FilterWeb:
|
||||
// Web扫描策略:只允许Web插件
|
||||
return b.isWebPlugin(pluginName)
|
||||
@@ -231,9 +240,9 @@ func (b *BaseScanStrategy) getPluginsByFilterType() []string {
|
||||
}
|
||||
}
|
||||
case FilterService:
|
||||
// 服务扫描策略:排除本地插件和纯Web插件,保留服务插件
|
||||
// 服务扫描策略:排除本地插件和UDP插件,保留TCP服务插件
|
||||
for _, pluginName := range allPlugins {
|
||||
if !b.isLocalPlugin(pluginName) {
|
||||
if !b.isLocalPlugin(pluginName) && !b.isUDPPlugin(pluginName) {
|
||||
filteredPlugins = append(filteredPlugins, pluginName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,6 +211,12 @@ func ExecuteScanTasks(ctx context.Context, session *common.ScanSession, targets
|
||||
default:
|
||||
}
|
||||
|
||||
if session.PauseGate != nil {
|
||||
if err := session.PauseGate(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
targetPort := target.Port
|
||||
|
||||
for _, pluginName := range pluginsToRun {
|
||||
@@ -262,6 +268,12 @@ func executeScanTask(ctx context.Context, session *common.ScanSession, pluginNam
|
||||
default:
|
||||
}
|
||||
|
||||
if session.PauseGate != nil {
|
||||
if err := session.PauseGate(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 长驻插件不进 WaitGroup,通过 ctx 管理生命周期
|
||||
if longRunningPlugins[pluginName] {
|
||||
ready := make(chan struct{}, 1)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"github.com/shadow1ng/fscan/common/i18n"
|
||||
"github.com/shadow1ng/fscan/common/parsers"
|
||||
"github.com/shadow1ng/fscan/plugins"
|
||||
)
|
||||
|
||||
// ServiceScanStrategy 服务扫描策略
|
||||
@@ -161,6 +162,11 @@ func (s *ServiceScanStrategy) performHostScan(ctx context.Context, session *comm
|
||||
return
|
||||
}
|
||||
|
||||
// UDP 插件并行分发:直接对存活主机发协议探测包,不走端口扫描
|
||||
if len(hosts) > 0 {
|
||||
s.dispatchUDPPlugins(ctx, session, hosts, info, config, ch, wg)
|
||||
}
|
||||
|
||||
// 流式 channel:端口扫描发现开放端口后立即通知插件执行
|
||||
stream := make(chan string, 64)
|
||||
|
||||
@@ -213,6 +219,34 @@ func (s *ServiceScanStrategy) performHostScan(ctx context.Context, session *comm
|
||||
}
|
||||
}
|
||||
|
||||
// dispatchUDPPlugins 分发UDP协议插件,跳过TCP端口扫描链路
|
||||
func (s *ServiceScanStrategy) dispatchUDPPlugins(ctx context.Context, session *common.ScanSession, hosts []string, baseInfo common.HostInfo, config *common.Config, ch chan struct{}, wg *sync.WaitGroup) {
|
||||
allPlugins, isCustomMode := s.GetPlugins(config)
|
||||
|
||||
var udpPlugins []string
|
||||
for _, name := range allPlugins {
|
||||
if plugins.IsUDP(name) {
|
||||
if isCustomMode || plugins.IsSafe(name) {
|
||||
udpPlugins = append(udpPlugins, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(udpPlugins) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
for _, pluginName := range udpPlugins {
|
||||
for _, port := range plugins.GetPluginPorts(pluginName) {
|
||||
target := baseInfo
|
||||
target.Host = host
|
||||
target.Port = port
|
||||
executeScanTask(ctx, session, pluginName, target, ch, wg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareTargets 准备目标信息
|
||||
func (s *ServiceScanStrategy) PrepareTargets(info common.HostInfo, session *common.ScanSession) []common.HostInfo {
|
||||
// 发现目标主机和端口
|
||||
|
||||
Reference in New Issue
Block a user