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

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

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

438 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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月