# Fscan 新插件架构迁移指南 ## 概述 本文档详细介绍了如何将传统的Fscan插件迁移到新的统一插件架构。新架构提供了更好的代码组织、国际化支持、自动利用功能和扩展性。 ## 新架构的优势 ### 🏗️ 统一的架构设计 - **标准化接口**:所有插件遵循相同的接口规范 - **模块化设计**:连接器、扫描器、利用器分离 - **代码复用**:基础功能由框架提供,插件专注于业务逻辑 ### 🌐 完整的国际化支持 - **多语言消息**:支持中英文动态切换 - **统一消息管理**:所有插件消息集中管理 - **用户友好**:根据`-lang`参数自动显示对应语言 ### ⚡ 增强的扫描能力 - **并发优化**:智能的工作池管理 - **超时控制**:精确的超时时间控制 - **错误处理**:完善的错误分类和重试机制 ### 🎯 自动利用集成 - **无缝集成**:弱密码发现后自动执行利用 - **可控开关**:通过`-nobr`参数控制是否启用 - **异步执行**:不影响扫描性能 ## 插件架构组成 ### 1. 目录结构 ``` plugins/services/[service_name]/ ├── connector.go # 服务连接器实现 ├── plugin.go # 主插件逻辑 ├── exploiter.go # 利用模块(可选) └── README.md # 插件文档 ``` ### 2. 核心组件 #### ServiceConnector(服务连接器) - 负责与目标服务建立连接 - 实现认证逻辑 - 处理网络通信 #### ServicePlugin(服务插件) - 继承基础插件功能 - 实现业务逻辑 - 集成利用模块 #### Exploiter(利用器,可选) - 实现各种利用方法 - 支持自动和手动利用 - 结果记录和保存 ## 标准迁移步骤 ### 第一步:分析现有插件 1. **识别核心功能** - 连接逻辑 - 认证方法 - 凭据生成 - 特殊检测(如未授权访问) 2. **提取关键代码** - 连接字符串构建 - 网络通信代码 - 错误处理逻辑 ### 第二步:创建连接器 参考MySQL连接器实现: ```go // 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插件实现: ```go // 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消息文件: ```go // 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", }, // 添加更多消息... } ``` ### 第五步:简化旧插件 将旧插件文件简化为适配器调用: ```go // 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函数中注册: ```go // 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。 ```go 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。 **解决方案**:正确进行类型转换。 ```go // 错误:使用%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. 连接字符串兼容性 **原则**:保持与老版本的连接字符串格式一致,确保稳定性。 ```go // 保持老版本格式 connStr := fmt.Sprintf("%v:%v@tcp(%v:%v)/mysql?charset=utf8&timeout=%v", username, password, host, port, c.timeout) ``` ### 4. 代理支持 确保正确处理SOCKS代理: ```go 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月