mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
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
274 lines
6.3 KiB
Go
274 lines
6.3 KiB
Go
package plugins
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/shadow1ng/fscan/common"
|
|
)
|
|
|
|
// Plugin 统一插件接口
|
|
type Plugin interface {
|
|
Name() string
|
|
Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *Result
|
|
}
|
|
|
|
// BasePlugin 基础插件结构,提供通用的name字段
|
|
type BasePlugin struct {
|
|
name string
|
|
}
|
|
|
|
// NewBasePlugin 创建基础插件
|
|
func NewBasePlugin(name string) BasePlugin {
|
|
return BasePlugin{name: name}
|
|
}
|
|
|
|
// Name 实现Plugin接口
|
|
func (b BasePlugin) Name() string {
|
|
return b.name
|
|
}
|
|
|
|
// ResultType 结果类型
|
|
type ResultType string
|
|
|
|
const (
|
|
ResultTypeCredential ResultType = "credential" // 弱密码发现
|
|
ResultTypeService ResultType = "service" // 服务识别
|
|
ResultTypeVuln ResultType = "vuln" // 漏洞发现
|
|
ResultTypeWeb ResultType = "web" // Web识别
|
|
)
|
|
|
|
// Result 统一结果结构
|
|
type Result struct {
|
|
Type ResultType
|
|
Success bool
|
|
Skipped bool // 扫描被跳过,不应输出结果
|
|
Service string
|
|
Username string
|
|
Password string
|
|
Banner string
|
|
Output string // web/local插件使用
|
|
Error error
|
|
|
|
// Web插件字段
|
|
Title string // 网页标题
|
|
Status int // HTTP状态码
|
|
Server string // 服务器信息
|
|
Length int // 响应长度
|
|
VulInfo string // 漏洞信息
|
|
Fingerprints []string // 指纹信息
|
|
}
|
|
|
|
// Exploiter 利用接口
|
|
type Exploiter interface {
|
|
Exploit(ctx context.Context, info *common.HostInfo, creds Credential, session *common.ScanSession) *ExploitResult
|
|
}
|
|
|
|
// ExploitResult 利用结果
|
|
type ExploitResult struct {
|
|
Success bool
|
|
Output string
|
|
Error error
|
|
}
|
|
|
|
// Credential 认证凭据
|
|
type Credential struct {
|
|
Username string
|
|
Password string
|
|
KeyData []byte
|
|
}
|
|
|
|
// PluginInfo 插件信息结构
|
|
type PluginInfo struct {
|
|
factory func() Plugin
|
|
ports []int
|
|
types []string // 插件类型标签
|
|
safe bool // 是否适合默认嵌入式扫描
|
|
}
|
|
|
|
// 插件类型常量
|
|
const (
|
|
PluginTypeWeb = "web" // Web类型插件
|
|
PluginTypeLocal = "local" // 本地类型插件
|
|
PluginTypeService = "service" // 服务类型插件
|
|
PluginTypeUDP = "udp" // UDP协议插件,跳过TCP端口扫描
|
|
)
|
|
|
|
var (
|
|
plugins = make(map[string]*PluginInfo)
|
|
mutex sync.RWMutex
|
|
)
|
|
|
|
func init() {
|
|
common.IsLocalMode = func(mode string) bool {
|
|
if mode == "" || mode == "all" {
|
|
return false
|
|
}
|
|
for _, name := range strings.Split(mode, ",") {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
continue
|
|
}
|
|
if !HasType(name, PluginTypeLocal) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
// RegisterWithPorts 注册带端口信息的插件
|
|
func RegisterWithPorts(name string, factory func() Plugin, ports []int) {
|
|
RegisterWithTypes(name, factory, ports, []string{PluginTypeService})
|
|
}
|
|
|
|
// RegisterUDPWithPorts 注册UDP协议插件,跳过TCP端口扫描链路
|
|
func RegisterUDPWithPorts(name string, factory func() Plugin, ports []int) {
|
|
RegisterWithTypes(name, factory, ports, []string{PluginTypeUDP})
|
|
}
|
|
|
|
// IsUDP 检查插件是否为UDP协议插件
|
|
func IsUDP(pluginName string) bool {
|
|
return HasType(pluginName, PluginTypeUDP)
|
|
}
|
|
|
|
// RegisterWithTypes 注册带类型标签的插件
|
|
func RegisterWithTypes(name string, factory func() Plugin, ports []int, types []string) {
|
|
RegisterWithOptions(name, factory, ports, types, !hasPluginType(types, PluginTypeLocal))
|
|
}
|
|
|
|
// RegisterUnsafeWithTypes 注册不适合默认嵌入式扫描的插件。
|
|
func RegisterUnsafeWithTypes(name string, factory func() Plugin, ports []int, types []string) {
|
|
RegisterWithOptions(name, factory, ports, types, false)
|
|
}
|
|
|
|
// RegisterWithOptions 注册带完整元数据的插件。
|
|
func RegisterWithOptions(name string, factory func() Plugin, ports []int, types []string, safe bool) {
|
|
mutex.Lock()
|
|
defer mutex.Unlock()
|
|
plugins[name] = &PluginInfo{
|
|
factory: factory,
|
|
ports: ports,
|
|
types: types,
|
|
safe: safe,
|
|
}
|
|
}
|
|
|
|
// HasType 检查插件是否具有指定类型
|
|
func HasType(pluginName string, typeName string) bool {
|
|
mutex.RLock()
|
|
defer mutex.RUnlock()
|
|
|
|
if info, exists := plugins[pluginName]; exists {
|
|
for _, t := range info.types {
|
|
if t == typeName {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsSafe 检查插件是否适合默认嵌入式扫描。
|
|
func IsSafe(pluginName string) bool {
|
|
mutex.RLock()
|
|
defer mutex.RUnlock()
|
|
|
|
if info, exists := plugins[pluginName]; exists {
|
|
return info.safe
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Get 获取插件实例
|
|
func Get(name string) Plugin {
|
|
mutex.RLock()
|
|
defer mutex.RUnlock()
|
|
|
|
if info, exists := plugins[name]; exists {
|
|
return info.factory()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// All 获取所有插件名称
|
|
func All() []string {
|
|
mutex.RLock()
|
|
defer mutex.RUnlock()
|
|
|
|
names := make([]string, 0, len(plugins))
|
|
for name := range plugins {
|
|
names = append(names, name)
|
|
}
|
|
return names
|
|
}
|
|
|
|
// Exists 检查插件是否存在
|
|
func Exists(name string) bool {
|
|
mutex.RLock()
|
|
defer mutex.RUnlock()
|
|
|
|
_, exists := plugins[name]
|
|
return exists
|
|
}
|
|
|
|
// GetPluginPorts 获取插件端口列表
|
|
func GetPluginPorts(name string) []int {
|
|
mutex.RLock()
|
|
defer mutex.RUnlock()
|
|
|
|
if info, exists := plugins[name]; exists {
|
|
return info.ports
|
|
}
|
|
return []int{} // 返回空列表表示适用于所有端口
|
|
}
|
|
|
|
func hasPluginType(types []string, typeName string) bool {
|
|
for _, t := range types {
|
|
if t == typeName {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GenerateCredentials 生成测试凭据
|
|
func GenerateCredentials(service string, config *common.Config) []Credential {
|
|
var credentials []Credential
|
|
credConfig := config.Credentials
|
|
|
|
// 优先使用精确的用户密码对
|
|
if len(credConfig.UserPassPairs) > 0 {
|
|
for _, pair := range credConfig.UserPassPairs {
|
|
credentials = append(credentials, Credential{
|
|
Username: pair.Username,
|
|
Password: pair.Password,
|
|
})
|
|
}
|
|
return credentials
|
|
}
|
|
|
|
// 否则使用笛卡尔积方式
|
|
users := credConfig.Userdict[service]
|
|
if len(users) == 0 {
|
|
users = []string{"admin", "root", "administrator", "user", "guest", ""}
|
|
}
|
|
|
|
passwords := credConfig.Passwords
|
|
if len(passwords) == 0 {
|
|
passwords = []string{"", "admin", "root", "password", "123456"}
|
|
}
|
|
|
|
for _, user := range users {
|
|
for _, pass := range passwords {
|
|
actualPass := strings.ReplaceAll(pass, "{user}", user)
|
|
credentials = append(credentials, Credential{
|
|
Username: user,
|
|
Password: actualPass,
|
|
})
|
|
}
|
|
}
|
|
return credentials
|
|
}
|