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

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

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

9.9 KiB
Raw Blame History

Fscan 插件架构最佳实践

插件系统设计原则

1. 接口统一性

所有插件必须遵循 base.Plugin 接口规范,确保:

  • 统一的初始化流程
  • 标准化的扫描接口
  • 一致的错误处理
  • 规范的结果返回

2. 模块化设计

插件采用分层架构:

Plugin (业务逻辑层)
  ↓
ServicePlugin (服务抽象层)  
  ↓
ServiceConnector (连接实现层)

3. 关注点分离

  • Connector: 专注网络连接和认证
  • Plugin: 专注业务逻辑和工作流
  • Exploiter: 专注安全利用和攻击

代码质量标准

1. 命名规范

包命名

// 服务插件包名使用小写服务名
package mysql
package redis  
package postgres

结构体命名

// 连接器使用[Service]Connector格式
type MySQLConnector struct {}
type RedisConnector struct {}

// 插件使用[Service]Plugin格式
type MySQLPlugin struct {}
type RedisPlugin struct {}

// 利用器使用[Service]Exploiter格式
type MySQLExploiter struct {}
type RedisExploiter struct {}

方法命名

// 工厂函数使用New[Type]格式
func NewMySQLConnector() *MySQLConnector
func NewMySQLPlugin() *MySQLPlugin

// 注册函数使用Register[Service]Plugin格式
func RegisterMySQLPlugin()

2. 注释标准

包级别注释

// MySQL插件新一代插件架构的完整实现示例
// 展示了如何正确实现服务扫描、凭据爆破、自动利用等功能
// 本插件可作为其他数据库插件迁移的标准参考模板

结构体注释

// MySQLConnector 实现MySQL数据库服务连接器
// 遵循 base.ServiceConnector 接口规范提供标准化的MySQL连接和认证功能
type MySQLConnector struct {
    timeout time.Duration // 连接超时时间
    host    string        // 目标主机地址
    port    int           // 目标端口号
}

方法注释

// NewMySQLConnector 创建新的MySQL连接器实例
// 自动注册SOCKS代理支持配置适当的超时时间
func NewMySQLConnector() *MySQLConnector {}

// Authenticate 使用凭据对MySQL服务进行身份认证
// 实现 base.ServiceConnector 接口的 Authenticate 方法
// 关键优化使用独立的Context避免上游超时问题
func (c *MySQLConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {}

3. 错误处理

错误分类

// 网络连接错误
return fmt.Errorf("连接失败: %v", err)

// 认证失败错误  
return fmt.Errorf("认证失败: %v", err)

// 配置错误
return fmt.Errorf("无效的端口号: %s", info.Ports)

错误日志

// Debug级别详细信息用于调试
common.LogDebug(fmt.Sprintf("MySQL尝试认证: %s@%s:%d", cred.Username, c.host, c.port))

// Error级别错误信息用于排查问题
common.LogError("MySQL插件新架构不可用请检查插件注册")

// Success级别成功信息用户可见
common.LogSuccess(i18n.GetText("mysql_scan_success", target, cred.Username, cred.Password))

性能优化指导

1. Context管理

问题上游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. 连接池管理

合理的连接池配置

// 设置合理的连接生命周期
db.SetConnMaxLifetime(c.timeout)
db.SetConnMaxIdleTime(c.timeout)
db.SetMaxIdleConns(0) // 对于扫描工具,不保持空闲连接

3. 内存管理

及时释放资源

defer db.Close()      // 数据库连接
defer conn.Close()    // 网络连接
defer cancel()        // Context取消

避免内存泄露

// 使用有限大小的channel
resultChan := make(chan Result, 1)

// 确保goroutine正确退出
select {
case result := <-resultChan:
    return result
case <-ctx.Done():
    return ctx.Err()
}

国际化实践

1. 消息定义

标准消息格式

var PluginMessages = map[string]map[string]string{
    "mysql_scan_success": {
        LangZH: "MySQL弱密码扫描成功: %s [%s:%s]",
        LangEN: "MySQL weak password scan successful: %s [%s:%s]",
    },
    "mysql_auth_failed": {
        LangZH: "MySQL认证失败: %s",  
        LangEN: "MySQL authentication failed: %s",
    },
}

消息使用

// 在插件中使用i18n消息
common.LogSuccess(i18n.GetText("mysql_scan_success", target, username, password))
common.LogError(i18n.GetText("mysql_auth_failed", err.Error()))

2. 参数占位符

使用标准的printf格式化占位符

  • %s - 字符串
  • %d - 整数
  • %v - 任意类型

安全考虑

1. 敏感信息处理

日志安全

// 错误:在生产日志中输出密码
common.LogInfo(fmt.Sprintf("尝试密码: %s", password))

// 正确在debug日志中输出生产环境可关闭
common.LogDebug(fmt.Sprintf("尝试密码: %s", password))

内存安全

// 使用完毕后清理敏感数据
defer func() {
    if password != "" {
        password = ""
    }
}()

2. 网络安全

超时控制

// 设置合理的超时时间避免DoS
timeout := time.Duration(common.Timeout) * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

连接复用

// 避免过多的并发连接
db.SetMaxOpenConns(1)  // 对于扫描工具限制并发连接

扩展性设计

1. 插件能力声明

标准能力集

plugin.SetCapabilities([]base.Capability{
    base.CapWeakPassword,     // 弱密码检测
    base.CapUnauthorized,     // 未授权访问检测  
    base.CapDataExtraction,   // 数据提取
    base.CapFileWrite,        // 文件写入
    base.CapCommandExecution, // 命令执行
    base.CapSQLInjection,     // SQL注入
    base.CapInformationLeak,  // 信息泄露
})

2. 自定义扫描逻辑

重写Scan方法

// 重写扫描方法实现自定义逻辑(如未授权访问检测)
func (p *RedisPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
    // 先检查未授权访问
    if result := p.checkUnauthorizedAccess(ctx, info); result != nil {
        return result, nil
    }
    
    // 再执行标准弱密码扫描
    return p.ServicePlugin.Scan(ctx, info)
}

3. 利用模块集成

可选的利用功能

// 自动利用功能(可通过-nobr参数禁用
if result.Success && len(result.Credentials) > 0 && !common.DisableBrute {
    // 异步执行利用攻击,避免阻塞扫描进程
    go p.autoExploit(context.Background(), info, result.Credentials[0])
}

测试策略

1. 单元测试

连接器测试

func TestMySQLConnector_Connect(t *testing.T) {
    connector := NewMySQLConnector()
    info := &common.HostInfo{
        Host:  "127.0.0.1",
        Ports: "3306",
    }
    
    conn, err := connector.Connect(context.Background(), info)
    if err != nil {
        t.Errorf("Connect failed: %v", err)
    }
    defer connector.Close(conn)
}

认证测试

func TestMySQLConnector_Authenticate(t *testing.T) {
    // 测试正确凭据
    cred := &base.Credential{Username: "root", Password: "123456"}
    err := connector.Authenticate(context.Background(), conn, cred)
    if err != nil {
        t.Errorf("Authentication failed: %v", err)
    }
    
    // 测试错误凭据
    invalidCred := &base.Credential{Username: "invalid", Password: "invalid"}
    err = connector.Authenticate(context.Background(), conn, invalidCred)
    if err == nil {
        t.Error("Expected authentication to fail")
    }
}

2. 集成测试

完整扫描流程

func TestMySQLPlugin_FullScan(t *testing.T) {
    plugin := NewMySQLPlugin()
    info := &common.HostInfo{
        Host:  "127.0.0.1", 
        Ports: "3306",
    }
    
    result, err := plugin.Scan(context.Background(), info)
    if err != nil {
        t.Errorf("Scan failed: %v", err)
    }
    
    if !result.Success {
        t.Error("Expected scan to succeed")
    }
}

3. 性能测试

并发扫描测试

func BenchmarkMySQLPlugin_Scan(b *testing.B) {
    plugin := NewMySQLPlugin()
    info := &common.HostInfo{Host: "127.0.0.1", Ports: "3306"}
    
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            plugin.Scan(context.Background(), info)
        }
    })
}

部署检查清单

1. 代码质量

  • 所有公共方法都有文档注释
  • 错误处理完整且合理
  • 无明显的内存泄露风险
  • 遵循Go代码规范

2. 功能完整性

  • 基础连接功能正常
  • 认证逻辑正确实现
  • 错误场景处理完善
  • 国际化消息完整

3. 性能表现

  • 超时控制合理
  • 并发性能良好
  • 资源使用合理
  • 无性能瓶颈

4. 安全性

  • 不在日志中暴露敏感信息
  • 超时控制防止DoS
  • 输入验证充分
  • 权限控制适当

5. 兼容性

  • 支持SOCKS代理
  • 与老版本行为兼容
  • 多语言环境工作正常
  • 跨平台兼容性

维护指南

1. 版本管理

  • 使用语义化版本号
  • 在元数据中更新版本信息
  • 维护变更日志

2. 文档更新

  • 同步更新代码注释
  • 更新用户使用文档
  • 维护最佳实践文档

3. 社区贡献

  • 遵循项目贡献指南
  • 提供清晰的PR描述
  • 包含必要的测试用例

通过遵循这些最佳实践可以确保插件的质量、性能和可维护性为fscan项目提供稳定可靠的插件支持。