fscan/PLUGIN_MIGRATION_GUIDE.md
ZacharyZcR 43f210ffc6 feat: 实现新一代插件注册系统完全替代传统手动注册模式
- 重构插件注册架构采用现代工厂模式和自动发现机制
- 新增完整的插件元数据管理系统支持版本能力标签等信息
- 实现智能插件适配器提供向后兼容的桥接功能
- 建立MySQL Redis SSH三个标准插件作为新架构参考实现
- 优化插件扫描逻辑支持按端口按类型的智能查询和过滤
- 添加国际化支持和完善的文档体系
- 代码量减少67%维护成本大幅降低扩展性显著提升

新架构特点:
- 零配置插件注册import即用
- 工厂模式延迟初始化和依赖注入
- 丰富元数据系统和能力声明
- 完全解耦的模块化设计
- 面向未来的可扩展架构

测试验证: MySQL和Redis插件功能完整包括弱密码检测未授权访问检测和自动利用攻击
2025-08-07 11:28:34 +08:00

11 KiB
Raw Blame History

Fscan 新插件架构迁移指南

概述

本文档详细介绍了如何将传统的Fscan插件迁移到新的统一插件架构。新架构提供了更好的代码组织、国际化支持、自动利用功能和扩展性。

新架构的优势

🏗️ 统一的架构设计

  • 标准化接口:所有插件遵循相同的接口规范
  • 模块化设计:连接器、扫描器、利用器分离
  • 代码复用:基础功能由框架提供,插件专注于业务逻辑

🌐 完整的国际化支持

  • 多语言消息:支持中英文动态切换
  • 统一消息管理:所有插件消息集中管理
  • 用户友好:根据-lang参数自动显示对应语言

增强的扫描能力

  • 并发优化:智能的工作池管理
  • 超时控制:精确的超时时间控制
  • 错误处理:完善的错误分类和重试机制

🎯 自动利用集成

  • 无缝集成:弱密码发现后自动执行利用
  • 可控开关:通过-nobr参数控制是否启用
  • 异步执行:不影响扫描性能

插件架构组成

1. 目录结构

plugins/services/[service_name]/
├── connector.go    # 服务连接器实现
├── plugin.go       # 主插件逻辑
├── exploiter.go    # 利用模块(可选)
└── README.md       # 插件文档

2. 核心组件

ServiceConnector服务连接器

  • 负责与目标服务建立连接
  • 实现认证逻辑
  • 处理网络通信

ServicePlugin服务插件

  • 继承基础插件功能
  • 实现业务逻辑
  • 集成利用模块

Exploiter利用器可选

  • 实现各种利用方法
  • 支持自动和手动利用
  • 结果记录和保存

标准迁移步骤

第一步:分析现有插件

  1. 识别核心功能

    • 连接逻辑
    • 认证方法
    • 凭据生成
    • 特殊检测(如未授权访问)
  2. 提取关键代码

    • 连接字符串构建
    • 网络通信代码
    • 错误处理逻辑

第二步:创建连接器

参考MySQL连接器实现

// connector.go
package [service]

import (
    "context"
    "fmt"
    "time"
    
    "github.com/shadow1ng/fscan/common"
    "github.com/shadow1ng/fscan/plugins/base"
)

// [Service]Connector 服务连接器
type [Service]Connector struct {
    timeout time.Duration
    host    string
    port    int
}

// NewConnector 创建连接器实例
func New[Service]Connector() *[Service]Connector {
    return &[Service]Connector{
        timeout: time.Duration(common.Timeout) * time.Second,
    }
}

// Connect 实现基础连接
func (c *[Service]Connector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
    // 1. 解析端口
    // 2. 保存目标信息
    // 3. 建立基础连接
    // 4. 返回连接对象
}

// Authenticate 实现身份认证
func (c *[Service]Connector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
    // 关键使用独立Context避免超时问题
    timeout := time.Duration(common.Timeout) * time.Second
    authCtx, cancel := context.WithTimeout(context.Background(), timeout)
    defer cancel()
    
    // 实现具体认证逻辑
}

// Close 关闭连接
func (c *[Service]Connector) Close(conn interface{}) error {
    // 清理资源
}

第三步:实现主插件

参考MySQL插件实现

// plugin.go
package [service]

import (
    "context"
    "fmt"
    
    "github.com/shadow1ng/fscan/common"
    "github.com/shadow1ng/fscan/common/i18n"
    "github.com/shadow1ng/fscan/plugins/base"
)

// [Service]Plugin 服务插件
type [Service]Plugin struct {
    *base.ServicePlugin
    exploiter *[Service]Exploiter // 可选
}

// New[Service]Plugin 创建插件实例
func New[Service]Plugin() *[Service]Plugin {
    metadata := &base.PluginMetadata{
        Name:        "[service]",
        Version:     "2.0.0",
        Author:      "fscan-team",
        Description: "[Service]扫描和利用插件",
        Category:    "service",
        Ports:       []int{[default_port]},
        Protocols:   []string{"tcp"},
        Tags:        []string{"database", "[service]", "bruteforce"},
    }
    
    connector := New[Service]Connector()
    servicePlugin := base.NewServicePlugin(metadata, connector)
    
    plugin := &[Service]Plugin{
        ServicePlugin: servicePlugin,
        exploiter:     New[Service]Exploiter(),
    }
    
    plugin.SetCapabilities([]base.Capability{
        base.CapWeakPassword,
        // 其他能力...
    })
    
    return plugin
}

// Scan 重写扫描方法(如需要)
func (p *[Service]Plugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
    // 调用基础扫描
    result, err := p.ServicePlugin.Scan(ctx, info)
    if err != nil || !result.Success {
        return result, err
    }
    
    // 记录成功结果
    target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
    common.LogSuccess(i18n.GetText("[service]_scan_success", target, result.Credentials[0].Username))
    
    // 自动利用(可选)
    if !common.DisableBrute {
        go p.autoExploit(context.Background(), info, result.Credentials[0])
    }
    
    return result, nil
}

// generateCredentials 自定义凭据生成(可选)
func (p *[Service]Plugin) generateCredentials() []*base.Credential {
    usernames := common.Userdict["[service]"]
    if len(usernames) == 0 {
        usernames = []string{"admin", "root"} // 默认用户名
    }
    return base.GenerateCredentials(usernames, common.Passwords)
}

第四步:添加国际化支持

更新i18n消息文件

// common/i18n/messages/plugins.go

var PluginMessages = map[string]map[string]string{
    "[service]_scan_success": {
        LangZH: "[Service]弱密码扫描成功: %s [%s:%s]",
        LangEN: "[Service] weak password scan successful: %s [%s:%s]",
    },
    "[service]_unauth_success": {
        LangZH: "[Service]未授权访问: %s",
        LangEN: "[Service] unauthorized access: %s",
    },
    // 添加更多消息...
}

第五步:简化旧插件

将旧插件文件简化为适配器调用:

// Plugins/[Service].go
package Plugins

import (
    "github.com/shadow1ng/fscan/common"
    "github.com/shadow1ng/fscan/plugins/adapter"
)

// [Service]Scan 执行[Service]服务扫描
// 现在完全使用新的插件架构
func [Service]Scan(info *common.HostInfo) error {
    // 使用新的插件架构
    if adapter.TryNewArchitecture("[service]", info) {
        return nil // 新架构处理成功
    }
    
    // 理论上不应该到达这里
    common.LogError("[Service]插件新架构不可用,请检查插件注册")
    return nil
}

第六步:注册插件

在插件包的init函数中注册

// Register[Service]Plugin 注册插件
func Register[Service]Plugin() {
    factory := base.NewSimplePluginFactory(
        &base.PluginMetadata{
            Name:        "[service]",
            Version:     "2.0.0",
            // ... 其他元数据
        },
        func() base.Plugin {
            return New[Service]Plugin()
        },
    )
    
    base.GlobalPluginRegistry.Register("[service]", factory)
}

// 自动注册
func init() {
    Register[Service]Plugin()
}

关键技术要点

1. Context超时处理

问题新架构中传递的Context可能存在超时问题。

解决方案在认证方法中创建独立的Context。

func (c *Connector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
    // 创建独立的超时Context避免上游Context超时问题
    timeout := time.Duration(common.Timeout) * time.Second
    authCtx, cancel := context.WithTimeout(context.Background(), timeout)
    defer cancel()
    
    // 使用authCtx进行认证操作
}

2. 端口类型处理

问题common.HostInfo.Ports是string类型不是int。

解决方案:正确进行类型转换。

// 错误:使用%d格式化string
target := fmt.Sprintf("%s:%d", info.Host, info.Ports)

// 正确:使用%s格式化string
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)

// 或者转换为int
port, err := strconv.Atoi(info.Ports)
if err != nil {
    return fmt.Errorf("无效端口号: %s", info.Ports)
}

3. 连接字符串兼容性

原则:保持与老版本的连接字符串格式一致,确保稳定性。

// 保持老版本格式
connStr := fmt.Sprintf("%v:%v@tcp(%v:%v)/mysql?charset=utf8&timeout=%v",
    username, password, host, port, c.timeout)

4. 代理支持

确保正确处理SOCKS代理

func (c *Connector) registerProxyDialer() {
    if common.Socks5Proxy == "" {
        return
    }
    
    // 注册代理拨号器
    driver.RegisterDialContext("tcp-proxy", func(ctx context.Context, addr string) (net.Conn, error) {
        return common.WrapperTcpWithContext(ctx, "tcp", addr)
    })
}

测试清单

基础功能测试

  • 端口扫描正常
  • 弱密码检测成功
  • 错误处理正确
  • 超时控制有效

国际化测试

  • 中文消息显示正确
  • 英文消息显示正确(-lang en
  • 消息参数格式化正确

代理测试

  • 直连模式工作正常
  • SOCKS代理模式工作正常

利用功能测试

  • 自动利用正常执行
  • -nobr参数能正确禁用利用
  • 利用结果正确保存

性能测试

  • 并发扫描性能良好
  • 内存使用合理
  • 无资源泄露

最佳实践

1. 代码组织

  • 保持文件结构清晰
  • 添加详细的文档注释
  • 使用有意义的变量名

2. 错误处理

  • 分类不同类型的错误
  • 提供有意义的错误消息
  • 适当的重试机制

3. 日志记录

  • 使用i18n支持的消息
  • 区分不同级别的日志
  • 避免敏感信息泄露

4. 性能优化

  • 合理的超时设置
  • 避免不必要的连接
  • 及时释放资源

常见问题

Q: 为什么需要创建独立的Context

A: 新架构中传递的Context可能已经接近超时或有其他限制创建独立Context确保认证有足够时间完成。

Q: 如何处理特殊的认证逻辑?

A: 可以在Scan方法中重写扫描逻辑如Redis的未授权访问检测。

Q: 如何添加新的利用方法?

A: 在exploiter中实现新方法并在GetExploitMethods中注册。

Q: 如何调试插件问题?

A: 使用-log debug参数查看详细日志检查Context、连接字符串、端口格式等关键部分。

成功案例

本指南基于MySQL和Redis插件的成功迁移经验

MySQL插件

  • 完美的弱密码检测
  • 自动利用功能
  • 完整的i18n支持
  • SOCKS代理支持

Redis插件

  • 未授权访问检测
  • 弱密码爆破
  • 双语消息支持
  • 高性能扫描

这两个插件可作为其他服务插件迁移的标准参考模板。

结论

新插件架构提供了更强大、更灵活、更易维护的插件开发框架。通过遵循本指南,可以顺利将旧插件迁移到新架构,并获得更好的功能和性能。


开发团队: fscan-team
文档版本: 1.0.0
最后更新: 2025年1月