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

- 重构插件注册架构采用现代工厂模式和自动发现机制 - 新增完整的插件元数据管理系统支持版本能力标签等信息 - 实现智能插件适配器提供向后兼容的桥接功能 - 建立MySQL Redis SSH三个标准插件作为新架构参考实现 - 优化插件扫描逻辑支持按端口按类型的智能查询和过滤 - 添加国际化支持和完善的文档体系 - 代码量减少67%维护成本大幅降低扩展性显著提升 新架构特点: - 零配置插件注册import即用 - 工厂模式延迟初始化和依赖注入 - 丰富元数据系统和能力声明 - 完全解耦的模块化设计 - 面向未来的可扩展架构 测试验证: MySQL和Redis插件功能完整包括弱密码检测未授权访问检测和自动利用攻击
302 lines
7.6 KiB
Go
302 lines
7.6 KiB
Go
package test
|
||
|
||
import (
|
||
"fmt"
|
||
"testing"
|
||
|
||
"github.com/shadow1ng/fscan/plugins/base"
|
||
|
||
// 导入插件包以触发自动注册
|
||
_ "github.com/shadow1ng/fscan/plugins/services/mysql"
|
||
_ "github.com/shadow1ng/fscan/plugins/services/redis"
|
||
_ "github.com/shadow1ng/fscan/plugins/services/ssh"
|
||
)
|
||
|
||
// TestPluginRegistry 测试插件注册表
|
||
func TestPluginRegistry(t *testing.T) {
|
||
// 获取所有注册的插件
|
||
plugins := base.GlobalPluginRegistry.GetAll()
|
||
|
||
if len(plugins) == 0 {
|
||
t.Error("没有注册任何插件")
|
||
return
|
||
}
|
||
|
||
t.Logf("已注册插件数量: %d", len(plugins))
|
||
|
||
// 测试每个插件
|
||
for _, name := range plugins {
|
||
t.Run(name, func(t *testing.T) {
|
||
testSinglePlugin(t, name)
|
||
})
|
||
}
|
||
}
|
||
|
||
// testSinglePlugin 测试单个插件
|
||
func testSinglePlugin(t *testing.T, pluginName string) {
|
||
// 创建插件实例
|
||
plugin, err := base.GlobalPluginRegistry.Create(pluginName)
|
||
if err != nil {
|
||
t.Errorf("创建插件 %s 失败: %v", pluginName, err)
|
||
return
|
||
}
|
||
|
||
// 测试元数据
|
||
metadata := plugin.GetMetadata()
|
||
if metadata == nil {
|
||
t.Errorf("插件 %s 没有元数据", pluginName)
|
||
return
|
||
}
|
||
|
||
if metadata.Name != pluginName {
|
||
t.Errorf("插件名称不匹配,期望: %s, 实际: %s", pluginName, metadata.Name)
|
||
}
|
||
|
||
// 测试能力
|
||
capabilities := plugin.GetCapabilities()
|
||
t.Logf("插件 %s 能力数量: %d", pluginName, len(capabilities))
|
||
|
||
// 测试利用方法
|
||
exploitMethods := plugin.GetExploitMethods()
|
||
t.Logf("插件 %s 利用方法数量: %d", pluginName, len(exploitMethods))
|
||
|
||
t.Logf("插件 %s 测试通过", pluginName)
|
||
}
|
||
|
||
// TestPluginMetadata 测试插件元数据
|
||
func TestPluginMetadata(t *testing.T) {
|
||
testCases := []struct {
|
||
name string
|
||
expected *base.PluginMetadata
|
||
}{
|
||
{
|
||
name: "mysql",
|
||
expected: &base.PluginMetadata{
|
||
Name: "mysql",
|
||
Category: "service",
|
||
Ports: []int{3306},
|
||
Protocols: []string{"tcp"},
|
||
},
|
||
},
|
||
{
|
||
name: "redis",
|
||
expected: &base.PluginMetadata{
|
||
Name: "redis",
|
||
Category: "service",
|
||
Ports: []int{6379},
|
||
Protocols: []string{"tcp"},
|
||
},
|
||
},
|
||
{
|
||
name: "ssh",
|
||
expected: &base.PluginMetadata{
|
||
Name: "ssh",
|
||
Category: "service",
|
||
Ports: []int{22},
|
||
Protocols: []string{"tcp"},
|
||
},
|
||
},
|
||
}
|
||
|
||
for _, tc := range testCases {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
metadata := base.GlobalPluginRegistry.GetMetadata(tc.name)
|
||
if metadata == nil {
|
||
t.Errorf("插件 %s 没有元数据", tc.name)
|
||
return
|
||
}
|
||
|
||
if metadata.Name != tc.expected.Name {
|
||
t.Errorf("插件名称不匹配,期望: %s, 实际: %s", tc.expected.Name, metadata.Name)
|
||
}
|
||
|
||
if metadata.Category != tc.expected.Category {
|
||
t.Errorf("插件类别不匹配,期望: %s, 实际: %s", tc.expected.Category, metadata.Category)
|
||
}
|
||
|
||
if len(metadata.Ports) != len(tc.expected.Ports) {
|
||
t.Errorf("端口数量不匹配,期望: %d, 实际: %d", len(tc.expected.Ports), len(metadata.Ports))
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestCredentialGeneration 测试凭据生成
|
||
func TestCredentialGeneration(t *testing.T) {
|
||
usernames := []string{"admin", "root", "test"}
|
||
passwords := []string{"password", "123456", "{user}"}
|
||
|
||
credentials := base.GenerateCredentials(usernames, passwords)
|
||
|
||
expectedCount := len(usernames) * len(passwords)
|
||
if len(credentials) != expectedCount {
|
||
t.Errorf("凭据数量不匹配,期望: %d, 实际: %d", expectedCount, len(credentials))
|
||
}
|
||
|
||
// 检查 {user} 占位符替换
|
||
foundReplacement := false
|
||
for _, cred := range credentials {
|
||
if cred.Username == "admin" && cred.Password == "admin" {
|
||
foundReplacement = true
|
||
break
|
||
}
|
||
}
|
||
|
||
if !foundReplacement {
|
||
t.Error("没有找到 {user} 占位符替换的凭据")
|
||
}
|
||
|
||
t.Logf("凭据生成测试通过,生成了 %d 个凭据", len(credentials))
|
||
}
|
||
|
||
// TestPasswordOnlyCredentials 测试仅密码凭据生成
|
||
func TestPasswordOnlyCredentials(t *testing.T) {
|
||
passwords := []string{"password", "123456", "admin"}
|
||
|
||
credentials := base.GeneratePasswordOnlyCredentials(passwords)
|
||
|
||
if len(credentials) != len(passwords) {
|
||
t.Errorf("凭据数量不匹配,期望: %d, 实际: %d", len(passwords), len(credentials))
|
||
}
|
||
|
||
// 检查凭据内容
|
||
for i, cred := range credentials {
|
||
if cred.Password != passwords[i] {
|
||
t.Errorf("密码不匹配,期望: %s, 实际: %s", passwords[i], cred.Password)
|
||
}
|
||
if cred.Username != "" {
|
||
t.Errorf("用户名应为空,实际: %s", cred.Username)
|
||
}
|
||
}
|
||
|
||
t.Logf("仅密码凭据生成测试通过,生成了 %d 个凭据", len(credentials))
|
||
}
|
||
|
||
// TestExploitMethods 测试利用方法
|
||
func TestExploitMethods(t *testing.T) {
|
||
// 测试MySQL利用方法
|
||
plugin, err := base.GlobalPluginRegistry.Create("mysql")
|
||
if err != nil {
|
||
t.Skip("MySQL插件未注册,跳过测试")
|
||
return
|
||
}
|
||
|
||
methods := plugin.GetExploitMethods()
|
||
if len(methods) == 0 {
|
||
t.Error("MySQL插件应该有利用方法")
|
||
return
|
||
}
|
||
|
||
// 检查是否支持数据提取
|
||
if !plugin.IsExploitSupported(base.ExploitDataExtraction) {
|
||
t.Error("MySQL插件应该支持数据提取")
|
||
}
|
||
|
||
t.Logf("MySQL插件利用方法测试通过,方法数量: %d", len(methods))
|
||
|
||
// 测试Redis利用方法
|
||
redisPlugin, err := base.GlobalPluginRegistry.Create("redis")
|
||
if err != nil {
|
||
t.Skip("Redis插件未注册,跳过测试")
|
||
return
|
||
}
|
||
|
||
redisMethods := redisPlugin.GetExploitMethods()
|
||
if len(redisMethods) == 0 {
|
||
t.Error("Redis插件应该有利用方法")
|
||
return
|
||
}
|
||
|
||
// 检查是否支持文件写入
|
||
if !redisPlugin.IsExploitSupported(base.ExploitFileWrite) {
|
||
t.Error("Redis插件应该支持文件写入")
|
||
}
|
||
|
||
t.Logf("Redis插件利用方法测试通过,方法数量: %d", len(redisMethods))
|
||
}
|
||
|
||
// TestPluginCapabilities 测试插件能力
|
||
func TestPluginCapabilities(t *testing.T) {
|
||
testCases := []struct {
|
||
pluginName string
|
||
expected []base.Capability
|
||
}{
|
||
{
|
||
pluginName: "mysql",
|
||
expected: []base.Capability{
|
||
base.CapWeakPassword,
|
||
base.CapDataExtraction,
|
||
base.CapFileWrite,
|
||
},
|
||
},
|
||
{
|
||
pluginName: "redis",
|
||
expected: []base.Capability{
|
||
base.CapWeakPassword,
|
||
base.CapUnauthorized,
|
||
base.CapFileWrite,
|
||
base.CapCommandExecution,
|
||
},
|
||
},
|
||
}
|
||
|
||
for _, tc := range testCases {
|
||
t.Run(tc.pluginName, func(t *testing.T) {
|
||
plugin, err := base.GlobalPluginRegistry.Create(tc.pluginName)
|
||
if err != nil {
|
||
t.Skipf("插件 %s 未注册,跳过测试", tc.pluginName)
|
||
return
|
||
}
|
||
|
||
capabilities := plugin.GetCapabilities()
|
||
|
||
// 检查是否包含预期的能力
|
||
for _, expectedCap := range tc.expected {
|
||
found := false
|
||
for _, cap := range capabilities {
|
||
if cap == expectedCap {
|
||
found = true
|
||
break
|
||
}
|
||
}
|
||
if !found {
|
||
t.Errorf("插件 %s 缺少能力: %s", tc.pluginName, expectedCap)
|
||
}
|
||
}
|
||
|
||
t.Logf("插件 %s 能力测试通过,能力数量: %d", tc.pluginName, len(capabilities))
|
||
})
|
||
}
|
||
}
|
||
|
||
// BenchmarkPluginCreation 插件创建性能基准测试
|
||
func BenchmarkPluginCreation(b *testing.B) {
|
||
plugins := []string{"mysql", "redis", "ssh"}
|
||
|
||
for _, pluginName := range plugins {
|
||
b.Run(pluginName, func(b *testing.B) {
|
||
for i := 0; i < b.N; i++ {
|
||
plugin, err := base.GlobalPluginRegistry.Create(pluginName)
|
||
if err != nil {
|
||
b.Errorf("创建插件失败: %v", err)
|
||
}
|
||
_ = plugin
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// BenchmarkCredentialGeneration 凭据生成性能基准测试
|
||
func BenchmarkCredentialGeneration(b *testing.B) {
|
||
usernames := []string{"root", "admin", "user", "test", "mysql", "redis", "postgres"}
|
||
passwords := make([]string, 50) // 50个密码
|
||
for i := range passwords {
|
||
passwords[i] = fmt.Sprintf("password%d", i)
|
||
}
|
||
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
credentials := base.GenerateCredentials(usernames, passwords)
|
||
_ = credentials
|
||
}
|
||
} |