Files
fscan/plugins/README.md
ZacharyZcR 71b92d4408 feat: v2.1.0 核心重构与功能增强
## 架构重构
- 全局变量消除,迁移至 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)
2026-01-11 20:16:23 +08:00

6.7 KiB
Raw Permalink Blame History

FScan 插件开发规范

概述

FScan 采用简化的单文件插件架构,每个插件一个 .go 文件,消除了过度设计的多文件结构。

  1. 简洁至上:消除所有不必要的抽象层
  2. 直击本质:专注于解决实际问题,不为架构而架构
  3. 向后兼容:不破坏用户接口和现有功能
  4. 消除特殊情况:统一处理逻辑,减少 if/else 分支

插件架构

核心接口

// Plugin 插件接口 - 只保留必要的方法
type Plugin interface {
    GetName() string                                        // 插件名称
    GetPorts() []int                                       // 支持的端口
    Scan(ctx context.Context, info *common.HostInfo) *ScanResult  // 扫描功能
}

// 可选接口:如果插件支持利用功能
type Exploiter interface {
    Exploit(ctx context.Context, info *common.HostInfo, creds Credential, config *common.Config) *ExploitResult
}

数据结构

// ScanResult 扫描结果 - 删除所有冗余字段
type ScanResult struct {
    Success  bool   // 扫描是否成功
    Service  string // 服务类型
    Username string // 发现的用户名(弱密码)
    Password string // 发现的密码(弱密码)  
    Banner   string // 服务版本信息
    Error    error  // 错误信息(如果失败)
}

// ExploitResult 利用结果(仅有利用功能的插件需要)
type ExploitResult struct {
    Success bool   // 利用是否成功
    Output  string // 命令执行输出
    Error   error  // 错误信息
}

// Credential 凭据结构
type Credential struct {
    Username string
    Password string
    KeyData  []byte // SSH私钥等
}

插件开发模板

1. 纯扫描插件如MySQL

package plugins

import (
    "context"
    "fmt"
    // 其他必要导入
)

// PluginName服务扫描插件
type PluginNamePlugin struct {
    name  string
    ports []int
}

// 构造函数
func NewPluginNamePlugin() *PluginNamePlugin {
    return &PluginNamePlugin{
        name:  "plugin_name",
        ports: []int{default_port},
    }
}

// 实现Plugin接口
func (p *PluginNamePlugin) GetName() string { return p.name }
func (p *PluginNamePlugin) GetPorts() []int { return p.ports }

func (p *PluginNamePlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
    // 如果禁用暴力破解,只做服务识别
    if common.DisableBrute {
        return p.identifyService(info)
    }

    // 生成测试凭据
    credentials := GenerateCredentials("plugin_name")
    
    // 逐个测试凭据
    for _, cred := range credentials {
        select {
        case <-ctx.Done():
            return &ScanResult{Success: false, Error: ctx.Err()}
        default:
        }

        if p.testCredential(ctx, info, cred) {
            return &ScanResult{
                Success:  true,
                Service:  "plugin_name",
                Username: cred.Username,
                Password: cred.Password,
            }
        }
    }

    return &ScanResult{Success: false, Service: "plugin_name"}
}

// 核心认证逻辑
func (p *PluginNamePlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
    // 实现具体的认证测试逻辑
    return false
}

// 服务识别(-nobr模式
func (p *PluginNamePlugin) identifyService(info *common.HostInfo) *ScanResult {
    // 实现服务识别逻辑
    return &ScanResult{Success: false, Service: "plugin_name"}
}

// 自动注册
func init() {
    RegisterPlugin("plugin_name", func() Plugin {
        return NewPluginNamePlugin()
    })
}

2. 带利用功能的插件如SSH

package plugins

// SSH插件结构
type SSHPlugin struct {
    name  string
    ports []int
}

// 同时实现Plugin和Exploiter接口
func (p *SSHPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
    // 扫描逻辑(同上)
}

func (p *SSHPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential, config *common.Config) *ExploitResult {
    // 建立SSH连接
    client, err := p.connectSSH(info, creds)
    if err != nil {
        return &ExploitResult{Success: false, Error: err}
    }
    defer client.Close()

    // 执行命令或其他利用操作
    output, err := p.executeCommand(client, "whoami")
    return &ExploitResult{
        Success: err == nil,
        Output:  output,
        Error:   err,
    }
}

// 辅助方法
func (p *SSHPlugin) connectSSH(info *common.HostInfo, creds Credential) (*ssh.Client, error) {
    // SSH连接实现
}

func (p *SSHPlugin) executeCommand(client *ssh.Client, cmd string) (string, error) {
    // 命令执行实现
}

开发规范

文件组织

plugins/
├── base.go           # 核心接口和注册系统
├── mysql.go          # MySQL插件
├── ssh.go            # SSH插件  
├── redis.go          # Redis插件
└── README.md         # 开发文档(本文件)

命名规范

  • 插件文件{service_name}.go
  • 插件结构体{ServiceName}Plugin
  • 构造函数New{ServiceName}Plugin()
  • 插件名称:小写,与文件名一致

代码规范

  1. 错误处理始终使用Context进行超时控制
  2. 日志输出:成功时使用 common.LogSuccess,调试用 common.LogDebug
  3. 凭据生成:使用 GenerateCredentials(service_name) 生成测试凭据
  4. 资源管理:及时关闭连接,使用 defer 确保清理

测试要求

每个插件必须支持:

  1. 暴力破解模式common.DisableBrute = false
  2. 服务识别模式common.DisableBrute = true
  3. Context超时处理:正确响应 ctx.Done()
  4. 代理支持:如果 common.Socks5Proxy 不为空

迁移指南

从三文件架构迁移

  1. 提取核心逻辑:从 connector.go 提取认证逻辑
  2. 合并实现:将 plugin.go 中的组装逻辑内联
  3. 删除垃圾:删除空的 exploiter.go
  4. 简化数据结构:只保留必要的字段

从Legacy插件迁移

  1. 保留核心逻辑:复制扫描和认证的核心算法
  2. 标准化接口实现统一的Plugin接口
  3. 移除全局依赖:通过返回值而不是全局变量传递结果
  4. 统一日志:使用统一的日志接口

性能优化

  1. 连接复用:在同一次扫描中复用连接
  2. 内存管理:及时释放不需要的资源
  3. 并发控制通过Context控制并发度
  4. 超时设置:合理设置各阶段超时时间

示例

参考 mysql.go 作为标准的纯扫描插件实现 参考 ssh.go 作为带利用功能的插件实现


记住:好的代码不是写出来的,是重构出来的。消除所有不必要的复杂性,直击问题本质。