mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00

- 重构插件注册架构采用现代工厂模式和自动发现机制 - 新增完整的插件元数据管理系统支持版本能力标签等信息 - 实现智能插件适配器提供向后兼容的桥接功能 - 建立MySQL Redis SSH三个标准插件作为新架构参考实现 - 优化插件扫描逻辑支持按端口按类型的智能查询和过滤 - 添加国际化支持和完善的文档体系 - 代码量减少67%维护成本大幅降低扩展性显著提升 新架构特点: - 零配置插件注册import即用 - 工厂模式延迟初始化和依赖注入 - 丰富元数据系统和能力声明 - 完全解耦的模块化设计 - 面向未来的可扩展架构 测试验证: MySQL和Redis插件功能完整包括弱密码检测未授权访问检测和自动利用攻击
11 KiB
11 KiB
Fscan 新插件架构迁移指南
概述
本文档详细介绍了如何将传统的Fscan插件迁移到新的统一插件架构。新架构提供了更好的代码组织、国际化支持、自动利用功能和扩展性。
新架构的优势
🏗️ 统一的架构设计
- 标准化接口:所有插件遵循相同的接口规范
- 模块化设计:连接器、扫描器、利用器分离
- 代码复用:基础功能由框架提供,插件专注于业务逻辑
🌐 完整的国际化支持
- 多语言消息:支持中英文动态切换
- 统一消息管理:所有插件消息集中管理
- 用户友好:根据
-lang
参数自动显示对应语言
⚡ 增强的扫描能力
- 并发优化:智能的工作池管理
- 超时控制:精确的超时时间控制
- 错误处理:完善的错误分类和重试机制
🎯 自动利用集成
- 无缝集成:弱密码发现后自动执行利用
- 可控开关:通过
-nobr
参数控制是否启用 - 异步执行:不影响扫描性能
插件架构组成
1. 目录结构
plugins/services/[service_name]/
├── connector.go # 服务连接器实现
├── plugin.go # 主插件逻辑
├── exploiter.go # 利用模块(可选)
└── README.md # 插件文档
2. 核心组件
ServiceConnector(服务连接器)
- 负责与目标服务建立连接
- 实现认证逻辑
- 处理网络通信
ServicePlugin(服务插件)
- 继承基础插件功能
- 实现业务逻辑
- 集成利用模块
Exploiter(利用器,可选)
- 实现各种利用方法
- 支持自动和手动利用
- 结果记录和保存
标准迁移步骤
第一步:分析现有插件
-
识别核心功能
- 连接逻辑
- 认证方法
- 凭据生成
- 特殊检测(如未授权访问)
-
提取关键代码
- 连接字符串构建
- 网络通信代码
- 错误处理逻辑
第二步:创建连接器
参考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月