mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-02-09 10:19:19 +08:00
## 架构重构
- 全局变量消除,迁移至 Config/State 对象
- SMB 插件融合(smb/smb2/smbghost/smbinfo)
- 服务探测重构,实现 Nmap 风格 fallback 机制
- 输出系统重构,TXT 实时刷盘 + 双写机制
- i18n 框架升级至 go-i18n
## 性能优化
- 正则表达式预编译
- 内存优化 map[string]struct{}
- 并发指纹匹配
- SOCKS5 连接复用
- 滑动窗口调度 + 自适应线程池
## 新功能
- Web 管理界面
- 多格式 POC 适配(xray/afrog)
- 增强指纹库(3139条)
- Favicon hash 指纹识别
- 插件选择性编译(Build Tags)
- fscan-lab 靶场环境
- 默认端口扩展(62→133)
## 构建系统
- 添加 no_local tag 支持排除本地插件
- 多版本构建:fscan/fscan-nolocal/fscan-web
- CI 添加 snapshot 模式支持仅测试构建
## Bug 修复
- 修复 120+ 个问题,包括 RDP panic、批量扫描漏报、
JSON 输出格式、Redis 检测、Context 超时等
## 测试增强
- 单元测试覆盖率 74-100%
- 并发安全测试
- 集成测试(Web/端口/服务/SSH/ICMP)
213 lines
4.7 KiB
Go
213 lines
4.7 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, config *common.Config, state *common.State) *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, config *common.Config) *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 // 插件类型标签
|
||
}
|
||
|
||
// 插件类型常量
|
||
const (
|
||
PluginTypeWeb = "web" // Web类型插件
|
||
PluginTypeLocal = "local" // 本地类型插件
|
||
PluginTypeService = "service" // 服务类型插件
|
||
)
|
||
|
||
var (
|
||
plugins = make(map[string]*PluginInfo)
|
||
mutex sync.RWMutex
|
||
)
|
||
|
||
// RegisterWithPorts 注册带端口信息的插件
|
||
func RegisterWithPorts(name string, factory func() Plugin, ports []int) {
|
||
RegisterWithTypes(name, factory, ports, []string{PluginTypeService})
|
||
}
|
||
|
||
// RegisterWithTypes 注册带类型标签的插件
|
||
func RegisterWithTypes(name string, factory func() Plugin, ports []int, types []string) {
|
||
mutex.Lock()
|
||
defer mutex.Unlock()
|
||
plugins[name] = &PluginInfo{
|
||
factory: factory,
|
||
ports: ports,
|
||
types: types,
|
||
}
|
||
}
|
||
|
||
// 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
|
||
}
|
||
|
||
// 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{} // 返回空列表表示适用于所有端口
|
||
}
|
||
|
||
// 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
|
||
}
|