Files
fscan/plugins/init_test.go
ZacharyZcR 71b92d4408 feat: v2.1.0 核心重构与功能增强
## 架构重构
- 全局变量消除,迁移至 Config/State 对象
- SMB 插件融合(smb/smb2/smbghost/smbinfo)
- 服务探测重构,实现 Nmap 风格 fallback 机制
- 输出系统重构,TXT 实时刷盘 + 双写机制
- i18n 框架升级至 go-i18n

## 性能优化
- 正则表达式预编译
- 内存优化 map[string]struct{}
- 并发指纹匹配
- SOCKS5 连接复用
- 滑动窗口调度 + 自适应线程池

## 新功能
- Web 管理界面
- 多格式 POC 适配(xray/afrog)
- 增强指纹库(3139条)
- Favicon hash 指纹识别
- 插件选择性编译(Build Tags)
- fscan-lab 靶场环境
- 默认端口扩展(62→133)

## 构建系统
- 添加 no_local tag 支持排除本地插件
- 多版本构建:fscan/fscan-nolocal/fscan-web
- CI 添加 snapshot 模式支持仅测试构建

## Bug 修复
- 修复 120+ 个问题,包括 RDP panic、批量扫描漏报、
  JSON 输出格式、Redis 检测、Context 超时等

## 测试增强
- 单元测试覆盖率 74-100%
- 并发安全测试
- 集成测试(Web/端口/服务/SSH/ICMP)
2026-01-11 20:16:23 +08:00

330 lines
9.5 KiB
Go
Raw Permalink 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.
package plugins
import (
"testing"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/config"
)
/*
init_test.go - 插件系统核心逻辑测试
测试目标GenerateCredentials 函数
价值:这个函数生成所有服务的暴力破解凭据,逻辑错误会导致:
- 漏掉有效凭据(少生成)
- 浪费时间测试重复凭据(多生成)
- {user} 占位符不生效(密码错误)
"凭据生成是暴力破解的弹药库。弹药错了,仗就打不赢。"
*/
// =============================================================================
// GenerateCredentials - 核心凭据生成逻辑
// =============================================================================
func TestGenerateCredentials_UserPassPairs_Priority(t *testing.T) {
/*
关键测试UserPassPairs 应该优先于笛卡尔积
为什么重要:
- UserPassPairs 是用户精确指定的凭据对
- 不应该和 Userdict/Passwords 混合使用
- 避免生成大量无用凭据
Bug 场景:
- UserPassPairs + 笛卡尔积混用 → 凭据爆炸
- 忽略 UserPassPairs → 用户指定的凭据不生效
*/
// 保存原始值
cfg := common.GetGlobalConfig()
origUserPassPairs := cfg.Credentials.UserPassPairs
origUserdict := cfg.Credentials.Userdict
origPasswords := cfg.Credentials.Passwords
defer func() {
cfg.Credentials.UserPassPairs = origUserPassPairs
cfg.Credentials.Userdict = origUserdict
cfg.Credentials.Passwords = origPasswords
}()
// 设置测试数据
cfg.Credentials.UserPassPairs = []config.CredentialPair{
{Username: "admin", Password: "Admin@123"},
{Username: "root", Password: "Root@456"},
}
// 即使有 Userdict 和 Passwords也应该被忽略
cfg.Credentials.Userdict = map[string][]string{
"mysql": {"mysql", "user1", "user2"},
}
cfg.Credentials.Passwords = []string{"pass1", "pass2", "pass3"}
result := GenerateCredentials("mysql", cfg)
// 验证:只有 2 个凭据(来自 UserPassPairs
if len(result) != 2 {
t.Errorf("Expected 2 credentials from UserPassPairs, got %d", len(result))
}
// 验证:凭据内容正确
expected := map[string]string{
"admin": "Admin@123",
"root": "Root@456",
}
for _, cred := range result {
if expectedPass, exists := expected[cred.Username]; exists {
if cred.Password != expectedPass {
t.Errorf("Username %s: expected password %s, got %s",
cred.Username, expectedPass, cred.Password)
}
} else {
t.Errorf("Unexpected username: %s", cred.Username)
}
}
t.Logf("✓ UserPassPairs 优先: 生成 %d 个精确凭据对", len(result))
}
func TestGenerateCredentials_CartesianProduct(t *testing.T) {
/*
关键测试:笛卡尔积应该正确生成 users × passwords
为什么重要:
- 笛卡尔积是默认的凭据生成方式
- 逻辑错误会导致漏掉有效凭据
Bug 场景:
- 嵌套循环顺序错误
- 重复生成凭据
- 遗漏某些组合
*/
// 保存原始值
cfg := common.GetGlobalConfig()
origUserPassPairs := cfg.Credentials.UserPassPairs
origUserdict := cfg.Credentials.Userdict
origPasswords := cfg.Credentials.Passwords
defer func() {
cfg.Credentials.UserPassPairs = origUserPassPairs
cfg.Credentials.Userdict = origUserdict
cfg.Credentials.Passwords = origPasswords
}()
// 清空 UserPassPairs使用笛卡尔积
cfg.Credentials.UserPassPairs = []config.CredentialPair{}
cfg.Credentials.Userdict = map[string][]string{
"ssh": {"root", "admin"},
}
cfg.Credentials.Passwords = []string{"123456", "password"}
result := GenerateCredentials("ssh", cfg)
// 验证:应该有 2 × 2 = 4 个凭据
expected := 2 * 2
if len(result) != expected {
t.Errorf("Expected %d credentials (2 users × 2 passwords), got %d", expected, len(result))
}
// 验证:所有组合都存在
expectedCombos := map[string]string{
"root:123456": "root",
"root:password": "root",
"admin:123456": "admin",
"admin:password": "admin",
}
found := make(map[string]bool)
for _, cred := range result {
combo := cred.Username + ":" + cred.Password
found[combo] = true
}
for combo := range expectedCombos {
if !found[combo] {
t.Errorf("Missing combination: %s", combo)
}
}
t.Logf("✓ 笛卡尔积正确: 2 users × 2 passwords = %d 凭据", len(result))
}
func TestGenerateCredentials_PlaceholderReplacement(t *testing.T) {
/*
关键测试:{user} 占位符应该被替换为用户名
为什么重要:
- 很多服务的默认密码是用户名(如 mysql:mysql
- {user} 占位符是实现这个需求的关键
Bug 场景:
- {user} 不替换 → 密码字面值是 "{user}"
- 替换错误 → 密码是其他用户名
*/
// 保存原始值
cfg := common.GetGlobalConfig()
origUserPassPairs := cfg.Credentials.UserPassPairs
origUserdict := cfg.Credentials.Userdict
origPasswords := cfg.Credentials.Passwords
defer func() {
cfg.Credentials.UserPassPairs = origUserPassPairs
cfg.Credentials.Userdict = origUserdict
cfg.Credentials.Passwords = origPasswords
}()
cfg.Credentials.UserPassPairs = []config.CredentialPair{}
cfg.Credentials.Userdict = map[string][]string{
"mysql": {"root", "mysql"},
}
cfg.Credentials.Passwords = []string{"{user}", "{user}123"}
result := GenerateCredentials("mysql", cfg)
// 验证:应该有 2 × 2 = 4 个凭据
expected := 2 * 2
if len(result) != expected {
t.Errorf("Expected %d credentials, got %d", expected, len(result))
}
// 验证:{user} 被正确替换
expectedCombos := map[string]string{
"root:root": "root", // {user} → root
"root:root123": "root", // {user}123 → root123
"mysql:mysql": "mysql", // {user} → mysql
"mysql:mysql123": "mysql", // {user}123 → mysql123
}
found := make(map[string]bool)
for _, cred := range result {
combo := cred.Username + ":" + cred.Password
found[combo] = true
// 验证:密码中不应该有字面值 "{user}"
if cred.Password == "{user}" || cred.Password == "{user}123" {
t.Errorf("Placeholder not replaced: %s:%s", cred.Username, cred.Password)
}
}
for combo := range expectedCombos {
if !found[combo] {
t.Errorf("Missing combination: %s", combo)
}
}
t.Logf("✓ {user} 占位符正确替换: 生成 %d 个凭据", len(result))
}
func TestGenerateCredentials_DefaultValues(t *testing.T) {
/*
关键测试:空字典时应该使用默认值
为什么重要:
- 某些服务可能没有预定义字典
- 空字典不应该导致零凭据
Bug 场景:
- 空字典 → 零凭据 → 完全不测试
- 默认值错误 → 浪费时间测试无用凭据
*/
// 保存原始值
cfg := common.GetGlobalConfig()
origUserPassPairs := cfg.Credentials.UserPassPairs
origUserdict := cfg.Credentials.Userdict
origPasswords := cfg.Credentials.Passwords
defer func() {
cfg.Credentials.UserPassPairs = origUserPassPairs
cfg.Credentials.Userdict = origUserdict
cfg.Credentials.Passwords = origPasswords
}()
cfg.Credentials.UserPassPairs = []config.CredentialPair{}
cfg.Credentials.Userdict = map[string][]string{} // 空字典
cfg.Credentials.Passwords = []string{} // 空密码列表
result := GenerateCredentials("unknown_service", cfg)
// 验证:应该有默认凭据
// 默认用户: admin, root, administrator, user, guest, ""6个
// 默认密码: "", admin, root, password, 1234565个
// 预期6 × 5 = 30 个凭据
expectedUsers := []string{"admin", "root", "administrator", "user", "guest", ""}
expectedPasswords := []string{"", "admin", "root", "password", "123456"}
expectedTotal := len(expectedUsers) * len(expectedPasswords)
if len(result) != expectedTotal {
t.Errorf("Expected %d credentials with default values, got %d", expectedTotal, len(result))
}
// 验证:默认用户和密码都被使用
usersFound := make(map[string]bool)
passwordsFound := make(map[string]bool)
for _, cred := range result {
usersFound[cred.Username] = true
passwordsFound[cred.Password] = true
}
for _, user := range expectedUsers {
if !usersFound[user] {
t.Errorf("Default user not found: %s", user)
}
}
for _, pass := range expectedPasswords {
if !passwordsFound[pass] {
t.Errorf("Default password not found: %s", pass)
}
}
t.Logf("✓ 默认值正确: %d users × %d passwords = %d 凭据",
len(expectedUsers), len(expectedPasswords), len(result))
}
func TestGenerateCredentials_EmptyUserPassPairs(t *testing.T) {
/*
关键测试:空的 UserPassPairs 应该回退到笛卡尔积
为什么重要:
- UserPassPairs = [] 和 nil 行为应该一致
- 避免特殊情况
Bug 场景:
- 空数组被当作"有值" → 生成零凭据
*/
// 保存原始值
cfg := common.GetGlobalConfig()
origUserPassPairs := cfg.Credentials.UserPassPairs
origUserdict := cfg.Credentials.Userdict
origPasswords := cfg.Credentials.Passwords
defer func() {
cfg.Credentials.UserPassPairs = origUserPassPairs
cfg.Credentials.Userdict = origUserdict
cfg.Credentials.Passwords = origPasswords
}()
cfg.Credentials.UserPassPairs = []config.CredentialPair{} // 空数组
cfg.Credentials.Userdict = map[string][]string{
"test": {"user1"},
}
cfg.Credentials.Passwords = []string{"pass1"}
result := GenerateCredentials("test", cfg)
// 验证应该回退到笛卡尔积1 × 1 = 1
if len(result) != 1 {
t.Errorf("Expected 1 credential (fallback to cartesian), got %d", len(result))
}
if result[0].Username != "user1" || result[0].Password != "pass1" {
t.Errorf("Expected user1:pass1, got %s:%s", result[0].Username, result[0].Password)
}
t.Logf("✓ 空 UserPassPairs 正确回退到笛卡尔积")
}