Compare commits

..

No commits in common. "da981cdbce2cfa04537f9a1d06b03db40c87ead1" and "c0374a6250cfc1017178a2e7c80a6112cb7fd1b3" have entirely different histories.

181 changed files with 1769 additions and 2026 deletions

5
.gitignore vendored
View File

@ -70,8 +70,3 @@ Todo列表.md
# Claude documentation / Claude文档 # Claude documentation / Claude文档
.claude_docs/ .claude_docs/
# Cleaner plugin artifacts / 清理插件产物
cleanup.bat
cleanup.sh
cleanup_script_*

View File

@ -99,7 +99,28 @@ func (m *ConcurrencyMonitor) FinishConnection(pluginName, target string) {
} }
} }
// 已移除未使用的 GetConnectionStats 方法 // GetConnectionStats 获取所有插件连接统计
func (m *ConcurrencyMonitor) GetConnectionStats() map[string]*PluginConnectionInfo {
stats := make(map[string]*PluginConnectionInfo)
m.pluginConnections.Range(func(key, value interface{}) bool {
keyStr := key.(string)
info := value.(*PluginConnectionInfo)
// 只返回当前活跃的连接
if atomic.LoadInt64(&info.ActiveConnections) > 0 {
stats[keyStr] = &PluginConnectionInfo{
PluginName: info.PluginName,
Target: info.Target,
ActiveConnections: atomic.LoadInt64(&info.ActiveConnections),
TotalConnections: atomic.LoadInt64(&info.TotalConnections),
}
}
return true
})
return stats
}
// GetTotalActiveConnections 获取总活跃连接数 // GetTotalActiveConnections 获取总活跃连接数
func (m *ConcurrencyMonitor) GetTotalActiveConnections() int64 { func (m *ConcurrencyMonitor) GetTotalActiveConnections() int64 {
@ -114,7 +135,16 @@ func (m *ConcurrencyMonitor) GetTotalActiveConnections() int64 {
return total return total
} }
// 已移除未使用的 Reset 方法 // Reset 重置监控器
func (m *ConcurrencyMonitor) Reset() {
atomic.StoreInt64(&m.activePluginTasks, 0)
atomic.StoreInt64(&m.totalPluginTasks, 0)
m.pluginConnections.Range(func(key, value interface{}) bool {
m.pluginConnections.Delete(key)
return true
})
}
// GetConcurrencyStatus 获取并发状态字符串 // GetConcurrencyStatus 获取并发状态字符串
func (m *ConcurrencyMonitor) GetConcurrencyStatus() string { func (m *ConcurrencyMonitor) GetConcurrencyStatus() string {
@ -134,4 +164,28 @@ func (m *ConcurrencyMonitor) GetConcurrencyStatus() string {
i18n.GetText("concurrency_connection"), totalConnections) i18n.GetText("concurrency_connection"), totalConnections)
} }
// 已移除未使用的 GetDetailedStatus 方法 // GetDetailedStatus 获取详细的并发状态
func (m *ConcurrencyMonitor) GetDetailedStatus() string {
activePlugins, _ := m.GetPluginTaskStats()
connectionStats := m.GetConnectionStats()
if activePlugins == 0 && len(connectionStats) == 0 {
return i18n.GetText("concurrency_no_active_tasks")
}
status := fmt.Sprintf("%s: %d", i18n.GetText("concurrency_plugin_tasks"), activePlugins)
if len(connectionStats) > 0 {
status += " | " + i18n.GetText("concurrency_connection_details") + ": "
first := true
for _, info := range connectionStats {
if !first {
status += ", "
}
status += fmt.Sprintf("%s@%s:%d", info.PluginName, info.Target, info.ActiveConnections)
first = false
}
}
return status
}

View File

@ -20,9 +20,6 @@ var (
HostsFile string HostsFile string
PortsFile string PortsFile string
// 本地插件列表(由外部初始化)
LocalPluginsList []string
ModuleThreadNum int ModuleThreadNum int
GlobalTimeout int64 GlobalTimeout int64
EnableFingerprint bool EnableFingerprint bool
@ -51,11 +48,13 @@ var (
RedisFile string RedisFile string
RedisShell string RedisShell string
DisableRedis bool
RedisWritePath string RedisWritePath string
RedisWriteContent string RedisWriteContent string
RedisWriteFile string RedisWriteFile string
DisableBrute bool DisableBrute bool
DisableExploit bool
MaxRetries int MaxRetries int
DisableSave bool DisableSave bool
@ -173,8 +172,9 @@ func Flag(Info *HostInfo) {
flag.Int64Var(&GlobalTimeout, "gt", 180, i18n.GetText("flag_global_timeout")) flag.Int64Var(&GlobalTimeout, "gt", 180, i18n.GetText("flag_global_timeout"))
// LiveTop 参数已移除,改为智能控制 // LiveTop 参数已移除,改为智能控制
flag.BoolVar(&DisablePing, "np", false, i18n.GetText("flag_disable_ping")) flag.BoolVar(&DisablePing, "np", false, i18n.GetText("flag_disable_ping"))
flag.BoolVar(&EnableFingerprint, "fp", false, i18n.GetText("flag_enable_fingerprint")) flag.BoolVar(&EnableFingerprint, "fingerprint", false, i18n.GetText("flag_enable_fingerprint"))
flag.StringVar(&LocalPlugin, "local", "", "指定本地插件名称 (如: cleaner, avdetect, keylogger 等)") flag.BoolVar(&LocalMode, "local", false, i18n.GetText("flag_local_mode"))
flag.StringVar(&LocalPlugin, "localplugin", "", i18n.GetText("flag_local_plugin"))
flag.BoolVar(&AliveOnly, "ao", false, i18n.GetText("flag_alive_only")) flag.BoolVar(&AliveOnly, "ao", false, i18n.GetText("flag_alive_only"))
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
@ -216,6 +216,7 @@ func Flag(Info *HostInfo) {
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
flag.StringVar(&RedisFile, "rf", "", i18n.GetText("flag_redis_file")) flag.StringVar(&RedisFile, "rf", "", i18n.GetText("flag_redis_file"))
flag.StringVar(&RedisShell, "rs", "", i18n.GetText("flag_redis_shell")) flag.StringVar(&RedisShell, "rs", "", i18n.GetText("flag_redis_shell"))
flag.BoolVar(&DisableRedis, "noredis", false, i18n.GetText("flag_disable_redis"))
flag.StringVar(&RedisWritePath, "rwp", "", i18n.GetText("flag_redis_write_path")) flag.StringVar(&RedisWritePath, "rwp", "", i18n.GetText("flag_redis_write_path"))
flag.StringVar(&RedisWriteContent, "rwc", "", i18n.GetText("flag_redis_write_content")) flag.StringVar(&RedisWriteContent, "rwc", "", i18n.GetText("flag_redis_write_content"))
flag.StringVar(&RedisWriteFile, "rwf", "", i18n.GetText("flag_redis_write_file")) flag.StringVar(&RedisWriteFile, "rwf", "", i18n.GetText("flag_redis_write_file"))
@ -224,6 +225,7 @@ func Flag(Info *HostInfo) {
// 暴力破解控制参数 // 暴力破解控制参数
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
flag.BoolVar(&DisableBrute, "nobr", false, i18n.GetText("flag_disable_brute")) flag.BoolVar(&DisableBrute, "nobr", false, i18n.GetText("flag_disable_brute"))
flag.BoolVar(&DisableExploit, "ne", false, i18n.GetText("flag_disable_exploit"))
flag.IntVar(&MaxRetries, "retry", 3, i18n.GetText("flag_max_retries")) flag.IntVar(&MaxRetries, "retry", 3, i18n.GetText("flag_max_retries"))
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
@ -242,7 +244,7 @@ func Flag(Info *HostInfo) {
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
flag.StringVar(&Shellcode, "sc", "", i18n.GetText("flag_shellcode")) flag.StringVar(&Shellcode, "sc", "", i18n.GetText("flag_shellcode"))
flag.StringVar(&ReverseShellTarget, "rsh", "", i18n.GetText("flag_reverse_shell_target")) flag.StringVar(&ReverseShellTarget, "rsh", "", i18n.GetText("flag_reverse_shell_target"))
flag.IntVar(&Socks5ProxyPort, "start-socks5", 0, i18n.GetText("flag_start_socks5_server")) flag.IntVar(&Socks5ProxyPort, "socks5-port", 0, i18n.GetText("flag_socks5_proxy"))
flag.IntVar(&ForwardShellPort, "fsh-port", 4444, i18n.GetText("flag_forward_shell_port")) flag.IntVar(&ForwardShellPort, "fsh-port", 4444, i18n.GetText("flag_forward_shell_port"))
flag.StringVar(&PersistenceTargetFile, "persistence-file", "", i18n.GetText("flag_persistence_file")) flag.StringVar(&PersistenceTargetFile, "persistence-file", "", i18n.GetText("flag_persistence_file"))
flag.StringVar(&WinPEFile, "win-pe", "", i18n.GetText("flag_win_pe_file")) flag.StringVar(&WinPEFile, "win-pe", "", i18n.GetText("flag_win_pe_file"))
@ -393,14 +395,18 @@ func checkParameterConflicts() {
LogBase(i18n.GetText("param_conflict_ao_icmp_both")) LogBase(i18n.GetText("param_conflict_ao_icmp_both"))
} }
// 检查本地插件参数 // 检查本地模式和本地插件参数
if LocalPlugin != "" { if LocalMode {
// 自动启用本地模式 if LocalPlugin == "" {
LocalMode = true fmt.Printf("错误: 使用本地扫描模式 (-local) 时必须指定一个本地插件 (-localplugin)\n")
fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy, forwardshell, ldpreload, shellenv, crontask, systemdservice, winregistry, winstartup, winschtask, winservice, winwmi, keylogger, downloader, cleaner\n")
os.Exit(1)
}
// 验证本地插件名称 // 验证本地插件名称
validPlugins := []string{"avdetect", "fileinfo", "dcinfo", "minidump", "reverseshell", "socks5proxy", "forwardshell", "ldpreload", "shellenv", "crontask", "systemdservice", "winregistry", "winstartup", "winschtask", "winservice", "winwmi", "keylogger", "downloader", "cleaner"} // 已重构的插件
isValid := false isValid := false
for _, valid := range LocalPluginsList { for _, valid := range validPlugins {
if LocalPlugin == valid { if LocalPlugin == valid {
isValid = true isValid = true
break break
@ -409,10 +415,14 @@ func checkParameterConflicts() {
if !isValid { if !isValid {
fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin) fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin)
if len(LocalPluginsList) > 0 { fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy, forwardshell, ldpreload, shellenv, crontask, systemdservice, winregistry, winstartup, winschtask, winservice, winwmi, keylogger, downloader, cleaner\n")
fmt.Printf("可用的本地插件: %s\n", strings.Join(LocalPluginsList, ", "))
}
os.Exit(1) os.Exit(1)
} }
} }
// 如果指定了本地插件但未启用本地模式
if !LocalMode && LocalPlugin != "" {
fmt.Printf("错误: 指定本地插件 (-localplugin) 时必须启用本地模式 (-local)\n")
os.Exit(1)
}
} }

View File

@ -263,19 +263,15 @@ func updateGlobalVariables(config *parsers.ParsedConfig, info *HostInfo) error {
// 更新目标相关全局变量 // 更新目标相关全局变量
if config.Targets != nil { if config.Targets != nil {
if len(config.Targets.Hosts) > 0 { if len(config.Targets.Hosts) > 0 {
// 如果info.Host已经有值说明解析结果来自info.Host不需要重复设置
// 只有当info.Host为空时才设置如从文件读取的情况
if info.Host == "" { if info.Host == "" {
info.Host = joinStrings(config.Targets.Hosts, ",") info.Host = joinStrings(config.Targets.Hosts, ",")
} else {
info.Host += "," + joinStrings(config.Targets.Hosts, ",")
} }
} }
if len(config.Targets.URLs) > 0 { if len(config.Targets.URLs) > 0 {
URLs = config.Targets.URLs URLs = config.Targets.URLs
// 如果info.Url为空且只有一个URL将其设置到info.Url
if info.Url == "" && len(config.Targets.URLs) == 1 {
info.Url = config.Targets.URLs[0]
}
} }
if len(config.Targets.Ports) > 0 { if len(config.Targets.Ports) > 0 {

View File

@ -8,12 +8,8 @@ Ports.go - 端口常量(向后兼容层)
此文件保持向后兼容实际常量定义已迁移到Core/Constants.go 此文件保持向后兼容实际常量定义已迁移到Core/Constants.go
*/ */
// 向后兼容的端口常量 - 引用base包中的定义 // 向后兼容的端口常量 - 引用Core包中的定义
var ( var (
WebPorts = base.WebPorts // Web服务端口组 WebPorts = base.WebPorts // Web服务端口组
MainPorts = base.MainPorts // 主要服务端口组 MainPorts = base.MainPorts // 主要服务端口组
DbPorts = base.DbPorts // 数据库端口组
ServicePorts = base.ServicePorts // 服务端口组
CommonPorts = base.CommonPorts // 常用端口组
AllPorts = base.AllPorts // 全部端口
) )

View File

@ -3,7 +3,6 @@ package common
import ( import (
"fmt" "fmt"
"os" "os"
"runtime"
"sync" "sync"
"time" "time"
@ -32,27 +31,11 @@ type ProgressManager struct {
// 输出缓冲相关 // 输出缓冲相关
outputMutex sync.Mutex outputMutex sync.Mutex
// 活跃指示器相关
spinnerIndex int
lastActivity time.Time
activityTicker *time.Ticker
stopActivityChan chan struct{}
// 内存监控相关
lastMemUpdate time.Time
memStats runtime.MemStats
} }
var ( var (
globalProgressManager *ProgressManager globalProgressManager *ProgressManager
progressMutex sync.Mutex progressMutex sync.Mutex
// 活跃指示器字符序列(旋转动画)
spinnerChars = []string{"|", "/", "-", "\\"}
// 活跃指示器更新间隔
activityUpdateInterval = 500 * time.Millisecond
) )
// GetProgressManager 获取全局进度条管理器 // GetProgressManager 获取全局进度条管理器
@ -86,16 +69,10 @@ func (pm *ProgressManager) InitProgress(total int64, description string) {
pm.startTime = time.Now() pm.startTime = time.Now()
pm.isActive = true pm.isActive = true
pm.enabled = true pm.enabled = true
pm.lastActivity = time.Now()
pm.spinnerIndex = 0
pm.lastMemUpdate = time.Now().Add(-2 * time.Second) // 强制首次更新内存
// 为进度条保留空间 // 为进度条保留空间
pm.setupProgressSpace() pm.setupProgressSpace()
// 启动活跃指示器
pm.startActivityIndicator()
// 初始显示进度条 // 初始显示进度条
pm.renderProgress() pm.renderProgress()
} }
@ -114,9 +91,6 @@ func (pm *ProgressManager) UpdateProgress(increment int64) {
pm.current = pm.total pm.current = pm.total
} }
// 更新活跃时间
pm.lastActivity = time.Now()
pm.renderProgress() pm.renderProgress()
} }
@ -136,9 +110,6 @@ func (pm *ProgressManager) FinishProgress() {
pm.current = pm.total pm.current = pm.total
pm.renderProgress() pm.renderProgress()
// 停止活跃指示器
pm.stopActivityIndicator()
// 显示完成信息 // 显示完成信息
pm.showCompletionInfo() pm.showCompletionInfo()
@ -169,9 +140,7 @@ func (pm *ProgressManager) renderProgress() {
// generateProgressBar 生成进度条字符串 // generateProgressBar 生成进度条字符串
func (pm *ProgressManager) generateProgressBar() string { func (pm *ProgressManager) generateProgressBar() string {
if pm.total == 0 { if pm.total == 0 {
spinner := pm.getActivityIndicator() return fmt.Sprintf("%s: 等待中...", pm.description)
memInfo := pm.getMemoryInfo()
return fmt.Sprintf("%s %s 等待中... %s", pm.description, spinner, memInfo)
} }
percentage := float64(pm.current) / float64(pm.total) * 100 percentage := float64(pm.current) / float64(pm.total) * 100
@ -228,22 +197,16 @@ func (pm *ProgressManager) generateProgressBar() string {
bar += "|" bar += "|"
} }
// 生成活跃指示器
spinner := pm.getActivityIndicator()
// 构建基础进度条 // 构建基础进度条
baseProgress := fmt.Sprintf("%s %s %6.1f%% %s (%d/%d)%s%s", baseProgress := fmt.Sprintf("%s %6.1f%% %s (%d/%d)%s%s",
pm.description, spinner, percentage, bar, pm.current, pm.total, speedStr, eta) pm.description, percentage, bar, pm.current, pm.total, speedStr, eta)
// 添加内存信息
memInfo := pm.getMemoryInfo()
// 添加并发状态 // 添加并发状态
if concurrencyStatus != "" { if concurrencyStatus != "" {
return fmt.Sprintf("%s [%s] %s", baseProgress, concurrencyStatus, memInfo) return fmt.Sprintf("%s [%s]", baseProgress, concurrencyStatus)
} }
return fmt.Sprintf("%s %s", baseProgress, memInfo) return baseProgress
} }
// showCompletionInfo 显示完成信息 // showCompletionInfo 显示完成信息
@ -364,94 +327,3 @@ func (pm *ProgressManager) renderProgressUnsafe() {
// 刷新输出 // 刷新输出
os.Stdout.Sync() os.Stdout.Sync()
} }
// =============================================================================
// 活跃指示器相关方法
// =============================================================================
// startActivityIndicator 启动活跃指示器
func (pm *ProgressManager) startActivityIndicator() {
// 防止重复启动
if pm.activityTicker != nil {
return
}
pm.activityTicker = time.NewTicker(activityUpdateInterval)
pm.stopActivityChan = make(chan struct{})
go func() {
for {
select {
case <-pm.activityTicker.C:
// 只有在活跃状态下才更新指示器
if pm.isActive && pm.enabled {
pm.mu.Lock()
pm.spinnerIndex = (pm.spinnerIndex + 1) % len(spinnerChars)
pm.mu.Unlock()
// 只有在长时间没有进度更新时才重新渲染
// 这样可以避免频繁更新时的性能问题
if time.Since(pm.lastActivity) > 2*time.Second {
pm.renderProgress()
}
}
case <-pm.stopActivityChan:
return
}
}
}()
}
// stopActivityIndicator 停止活跃指示器
func (pm *ProgressManager) stopActivityIndicator() {
if pm.activityTicker != nil {
pm.activityTicker.Stop()
pm.activityTicker = nil
}
if pm.stopActivityChan != nil {
close(pm.stopActivityChan)
pm.stopActivityChan = nil
}
}
// getActivityIndicator 获取当前活跃指示器字符
func (pm *ProgressManager) getActivityIndicator() string {
// 如果最近有活动2秒内显示静态指示器
if time.Since(pm.lastActivity) <= 2*time.Second {
return "●" // 实心圆表示活跃
}
// 如果长时间没有活动,显示旋转指示器表明程序仍在运行
return spinnerChars[pm.spinnerIndex]
}
// getMemoryInfo 获取内存使用信息
func (pm *ProgressManager) getMemoryInfo() string {
// 限制内存统计更新频率以提高性能(每秒最多一次)
now := time.Now()
if now.Sub(pm.lastMemUpdate) >= time.Second {
runtime.ReadMemStats(&pm.memStats)
pm.lastMemUpdate = now
}
// 获取当前使用的内存以MB为单位
memUsedMB := float64(pm.memStats.Alloc) / 1024 / 1024
// 根据内存使用量选择颜色
var colorCode string
if NoColor {
return fmt.Sprintf("内存:%.1fMB", memUsedMB)
}
// 根据内存使用量设置颜色
if memUsedMB < 50 {
colorCode = "\033[32m" // 绿色 - 内存使用较低
} else if memUsedMB < 100 {
colorCode = "\033[33m" // 黄色 - 内存使用中等
} else {
colorCode = "\033[31m" // 红色 - 内存使用较高
}
return fmt.Sprintf("%s内存:%.1fMB\033[0m", colorCode, memUsedMB)
}

View File

@ -13,11 +13,7 @@ Constants.go - 核心常量定义
// 预定义端口组常量 // 预定义端口组常量
var ( var (
WebPorts = "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8005,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10051,10250,12018,12443,14000,15672,15671,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,20880,21000,21501,21502,28018" WebPorts = "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8005,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10051,10250,12018,12443,14000,15672,15671,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,20880,21000,21501,21502,28018"
MainPorts = "21,22,23,25,80,81,110,135,139,143,389,443,445,465,502,587,636,873,993,995,1433,1434,1521,1522,1525,2121,2200,2222,3000,3268,3269,3306,3389,5432,5672,5900,6379,7474,7687,8000,8080,8081,8088,8443,8888,9000,9042,9080,9092,9200,9300,11211,15672,22222,27017,61613,61614" MainPorts = "21,22,23,25,80,81,110,135,139,143,161,389,443,445,465,502,587,636,873,993,995,1433,1434,1521,1522,1525,2121,2200,2222,3000,3268,3269,3306,3389,5432,5672,5900,6379,7474,7687,8000,8080,8081,8088,8443,8888,9000,9042,9080,9092,9200,9300,11211,15672,22222,27017,61613,61614"
DbPorts = "1433,1521,3306,5432,5672,6379,7687,9042,9093,9200,11211,27017,61616"
ServicePorts = "21,22,23,25,110,135,139,143,162,389,445,465,502,587,636,873,993,995,1433,1521,2222,3306,3389,5020,5432,5672,5671,6379,8161,8443,9000,9092,9093,9200,10051,11211,15672,15671,27017,61616,61613"
CommonPorts = "21,22,23,25,53,80,110,135,139,143,443,445,993,995,1723,3389,5060,5985,5986"
AllPorts = "1-65535"
) )
// ============================================================================= // =============================================================================

View File

@ -1,6 +1,7 @@
package base package base
import ( import (
"fmt"
"sync" "sync"
) )
@ -125,7 +126,41 @@ func (p *ScanPlugin) GetInfo() map[string]interface{} {
// 插件管理器方法 // 插件管理器方法
// ============================================================================= // =============================================================================
// 已移除未使用的 RegisterPlugin 方法 // RegisterPlugin 注册插件
func (pm *PluginManager) RegisterPlugin(plugin *ScanPlugin) error {
if plugin == nil {
return fmt.Errorf("plugin cannot be nil")
}
if plugin.Name == "" {
return fmt.Errorf("plugin name cannot be empty")
}
if plugin.ScanFunc == nil {
return fmt.Errorf("plugin scan function cannot be nil")
}
pm.mu.Lock()
defer pm.mu.Unlock()
// 检查插件是否已存在
if _, exists := pm.plugins[plugin.Name]; exists {
return fmt.Errorf("plugin %s already registered", plugin.Name)
}
// 注册插件
pm.plugins[plugin.Name] = plugin
// 按类型索引
for _, pluginType := range plugin.Types {
pm.types[pluginType] = append(pm.types[pluginType], plugin)
}
// 按端口索引
for _, port := range plugin.Ports {
pm.ports[port] = append(pm.ports[port], plugin)
}
return nil
}
// ======================================================================================== // ========================================================================================
// 未使用的插件管理器方法已删除(死代码清理) // 未使用的插件管理器方法已删除(死代码清理)
@ -137,11 +172,89 @@ func (p *ScanPlugin) GetInfo() map[string]interface{} {
// 全局插件管理函数 (保持向后兼容) // 全局插件管理函数 (保持向后兼容)
// ============================================================================= // =============================================================================
// 已移除未使用的 RegisterPlugin 方法 // RegisterPlugin 注册插件到全局管理器
func RegisterPlugin(name string, plugin ScanPlugin) error {
// 转换为新的插件结构
newPlugin := &ScanPlugin{
Name: name,
Ports: plugin.Ports,
Types: plugin.Types,
Enabled: true,
ScanFunc: plugin.ScanFunc,
}
// 已移除未使用的 Clear 方法 // 注册到新的插件管理器
err := globalPluginManager.RegisterPlugin(newPlugin)
if err != nil {
return err
}
// 已移除未使用的 ClearPluginsByType 方法 // 同时更新Legacy管理器以保持向后兼容
LegacyPluginManager[name] = plugin
return nil
}
// Clear 清理所有插件(防止内存泄漏)
func (pm *PluginManager) Clear() {
pm.mu.Lock()
defer pm.mu.Unlock()
// 清理插件实例
for name, plugin := range pm.plugins {
// ScanPlugin结构不包含Cleanup方法直接删除即可
_ = plugin // 避免未使用变量警告
delete(pm.plugins, name)
}
// 清理索引
for typeKey := range pm.types {
delete(pm.types, typeKey)
}
for portKey := range pm.ports {
delete(pm.ports, portKey)
}
// 清理Legacy管理器
for k := range LegacyPluginManager {
delete(LegacyPluginManager, k)
}
}
// ClearPluginsByType 清理指定类型的插件
func (pm *PluginManager) ClearPluginsByType(pluginType string) {
pm.mu.Lock()
defer pm.mu.Unlock()
plugins := pm.types[pluginType]
for _, plugin := range plugins {
// ScanPlugin结构不包含Cleanup方法直接删除即可
delete(pm.plugins, plugin.Name)
}
delete(pm.types, pluginType)
// 同步清理端口索引
for port, portPlugins := range pm.ports {
filtered := make([]*ScanPlugin, 0)
for _, plugin := range portPlugins {
found := false
for _, typePlugin := range plugins {
if plugin == typePlugin {
found = true
break
}
}
if !found {
filtered = append(filtered, plugin)
}
}
if len(filtered) == 0 {
delete(pm.ports, port)
} else {
pm.ports[port] = filtered
}
}
}
// GetGlobalPluginManager 方法已删除(死代码清理) // GetGlobalPluginManager 方法已删除(死代码清理)

View File

@ -18,7 +18,6 @@ import (
"github.com/shadow1ng/fscan/common/base" "github.com/shadow1ng/fscan/common/base"
"github.com/shadow1ng/fscan/common/logging" "github.com/shadow1ng/fscan/common/logging"
"github.com/shadow1ng/fscan/common/output" "github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/common/proxy"
) )
// ============================================================================= // =============================================================================
@ -30,8 +29,12 @@ type ScanPlugin = base.ScanPlugin
// 插件类型常量 // 插件类型常量
const ( const (
PluginTypeService = base.PluginTypeService
PluginTypeWeb = base.PluginTypeWeb PluginTypeWeb = base.PluginTypeWeb
PluginTypeLocal = base.PluginTypeLocal PluginTypeLocal = base.PluginTypeLocal
PluginTypeBrute = base.PluginTypeBrute
PluginTypePoc = base.PluginTypePoc
PluginTypeScan = base.PluginTypeScan
) )
// 全局插件管理器 // 全局插件管理器
@ -41,7 +44,12 @@ var PluginManager = base.LegacyPluginManager
// 核心功能导出 - 直接调用对应模块 // 核心功能导出 - 直接调用对应模块
// ============================================================================= // =============================================================================
// 已移除未使用的 RegisterPlugin 方法 // 插件系统
func RegisterPlugin(name string, plugin ScanPlugin) {
if err := base.RegisterPlugin(name, plugin); err != nil {
LogError("Failed to register plugin " + name + ": " + err.Error())
}
}
// GetGlobalPluginManager 函数已删除(死代码清理) // GetGlobalPluginManager 函数已删除(死代码清理)
@ -169,58 +177,14 @@ func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.
return net.DialTimeout(network, address, timeout) return net.DialTimeout(network, address, timeout)
} }
// WrapperTcpWithContext TCP连接包装器带上下文和代理支持 // WrapperTcpWithContext TCP连接包装器带上下文
func WrapperTcpWithContext(ctx context.Context, network, address string) (net.Conn, error) { func WrapperTcpWithContext(ctx context.Context, network, address string) (net.Conn, error) {
// 检查是否配置了SOCKS5代理
if Socks5Proxy != "" {
proxyConfig := &proxy.ProxyConfig{
Type: proxy.ProxyTypeSOCKS5,
Address: Socks5Proxy,
Timeout: time.Second * 10,
}
proxyManager := proxy.NewProxyManager(proxyConfig)
dialer, err := proxyManager.GetDialer()
if err != nil {
LogDebug(fmt.Sprintf("SOCKS5代理连接失败回退到直连: %v", err))
// 代理失败时回退到直连
var d net.Dialer var d net.Dialer
return d.DialContext(ctx, network, address) return d.DialContext(ctx, network, address)
} }
LogDebug(fmt.Sprintf("使用SOCKS5代理连接: %s -> %s", Socks5Proxy, address)) // WrapperTlsWithContext TLS连接包装器带上下文
return dialer.DialContext(ctx, network, address)
}
// 没有配置代理,使用直连
var d net.Dialer
return d.DialContext(ctx, network, address)
}
// WrapperTlsWithContext TLS连接包装器带上下文和代理支持
func WrapperTlsWithContext(ctx context.Context, network, address string, config *tls.Config) (net.Conn, error) { func WrapperTlsWithContext(ctx context.Context, network, address string, config *tls.Config) (net.Conn, error) {
// 检查是否配置了SOCKS5代理
if Socks5Proxy != "" {
proxyConfig := &proxy.ProxyConfig{
Type: proxy.ProxyTypeSOCKS5,
Address: Socks5Proxy,
Timeout: time.Second * 10,
}
proxyManager := proxy.NewProxyManager(proxyConfig)
tlsDialer, err := proxyManager.GetTLSDialer()
if err != nil {
LogDebug(fmt.Sprintf("SOCKS5代理TLS连接失败回退到直连: %v", err))
// 代理失败时回退到直连
d := &tls.Dialer{Config: config}
return d.DialContext(ctx, network, address)
}
LogDebug(fmt.Sprintf("使用SOCKS5代理TLS连接: %s -> %s", Socks5Proxy, address))
return tlsDialer.DialTLSContext(ctx, network, address, config)
}
// 没有配置代理,使用直连
d := &tls.Dialer{Config: config} d := &tls.Dialer{Config: config}
return d.DialContext(ctx, network, address) return d.DialContext(ctx, network, address)
} }

View File

@ -86,6 +86,7 @@ type VulnExploitConfig struct {
// Redis利用 // Redis利用
RedisFile string `json:"redis_file"` // Redis利用目标文件 RedisFile string `json:"redis_file"` // Redis利用目标文件
RedisShell string `json:"redis_shell"` // Redis反弹Shell命令 RedisShell string `json:"redis_shell"` // Redis反弹Shell命令
DisableRedis bool `json:"disable_redis"` // 是否禁用Redis利用测试
RedisWritePath string `json:"redis_write_path"` // Redis文件写入路径 RedisWritePath string `json:"redis_write_path"` // Redis文件写入路径
RedisWriteContent string `json:"redis_write_content"` // Redis文件写入内容 RedisWriteContent string `json:"redis_write_content"` // Redis文件写入内容
RedisWriteFile string `json:"redis_write_file"` // Redis写入的源文件 RedisWriteFile string `json:"redis_write_file"` // Redis写入的源文件

View File

@ -20,7 +20,7 @@ globals.go - 全局变量定义
// 版本信息 // 版本信息
// ============================================================================= // =============================================================================
var version = "2.2.1" var version = "2.2.0"
// ============================================================================= // =============================================================================
// 简化的全局状态管理(仅保留必要的同步机制) // 简化的全局状态管理(仅保留必要的同步机制)

View File

@ -1,58 +0,0 @@
package common
import (
"fmt"
"strconv"
)
// HostInfoHelper 提供HostInfo的辅助方法
// 使用函数而不是方法,保持向后兼容
// GetPort 获取端口号(转换为整数)
func GetPort(h *HostInfo) (int, error) {
if h.Ports == "" {
return 0, fmt.Errorf("端口未设置")
}
return strconv.Atoi(h.Ports)
}
// IsWebTarget 判断是否为Web目标
func IsWebTarget(h *HostInfo) bool {
return h.Url != ""
}
// HasPort 检查是否设置了端口
func HasPort(h *HostInfo) bool {
return h.Ports != ""
}
// ValidateHostInfo 验证HostInfo的有效性
func ValidateHostInfo(h *HostInfo) error {
if h.Host == "" && h.Url == "" {
return fmt.Errorf("主机地址或URL必须至少指定一个")
}
// 验证端口格式(如果指定了)
if h.Ports != "" {
if _, err := GetPort(h); err != nil {
return fmt.Errorf("端口格式无效: %v", err)
}
}
return nil
}
// HostInfoString 返回HostInfo的字符串表示
func HostInfoString(h *HostInfo) string {
if IsWebTarget(h) {
return h.Url
}
if HasPort(h) {
return fmt.Sprintf("%s:%s", h.Host, h.Ports)
}
return h.Host
}

View File

@ -170,6 +170,10 @@ var FlagMessages = map[string]map[string]string{
LangZH: "Redis Shell", LangZH: "Redis Shell",
LangEN: "Redis Shell", LangEN: "Redis Shell",
}, },
"flag_disable_redis": {
LangZH: "禁用Redis扫描",
LangEN: "Disable Redis scan",
},
"flag_redis_write_path": { "flag_redis_write_path": {
LangZH: "Redis写入路径", LangZH: "Redis写入路径",
LangEN: "Redis write path", LangEN: "Redis write path",
@ -186,6 +190,10 @@ var FlagMessages = map[string]map[string]string{
LangZH: "禁用暴力破解", LangZH: "禁用暴力破解",
LangEN: "Disable brute force", LangEN: "Disable brute force",
}, },
"flag_disable_exploit": {
LangZH: "禁用利用攻击",
LangEN: "Disable exploit attacks",
},
"flag_max_retries": { "flag_max_retries": {
LangZH: "最大重试次数", LangZH: "最大重试次数",
LangEN: "Maximum retries", LangEN: "Maximum retries",
@ -227,10 +235,6 @@ var FlagMessages = map[string]map[string]string{
LangEN: "Reverse shell target address:port (e.g.: 192.168.1.100:4444)", LangEN: "Reverse shell target address:port (e.g.: 192.168.1.100:4444)",
}, },
"flag_socks5_proxy": { "flag_socks5_proxy": {
LangZH: "使用SOCKS5代理 (如: 127.0.0.1:1080)",
LangEN: "Use SOCKS5 proxy (e.g.: 127.0.0.1:1080)",
},
"flag_start_socks5_server": {
LangZH: "启动SOCKS5代理服务器端口 (如: 1080)", LangZH: "启动SOCKS5代理服务器端口 (如: 1080)",
LangEN: "Start SOCKS5 proxy server on port (e.g.: 1080)", LangEN: "Start SOCKS5 proxy server on port (e.g.: 1080)",
}, },

View File

@ -108,7 +108,11 @@ func ParsePort(ports string) []int {
return result return result
} }
// 已移除未使用的 ParsePortsFromString 方法 // ParsePortsFromString 从字符串解析端口列表
// 保持与 ParsePortsFromString 的接口兼容性
func ParsePortsFromString(portsStr string) []int {
return ParsePort(portsStr)
}
// ============================================================================= // =============================================================================
// 辅助函数 // 辅助函数

View File

@ -30,6 +30,7 @@ type TargetParserOptions struct {
ValidateURLs bool `json:"validate_urls"` ValidateURLs bool `json:"validate_urls"`
ResolveDomains bool `json:"resolve_domains"` ResolveDomains bool `json:"resolve_domains"`
EnableStatistics bool `json:"enable_statistics"` EnableStatistics bool `json:"enable_statistics"`
DefaultPorts string `json:"default_ports"`
} }
// DefaultTargetParserOptions 默认目标解析器选项 // DefaultTargetParserOptions 默认目标解析器选项
@ -42,6 +43,7 @@ func DefaultTargetParserOptions() *TargetParserOptions {
ValidateURLs: DefaultValidateURLs, ValidateURLs: DefaultValidateURLs,
ResolveDomains: DefaultResolveDomains, ResolveDomains: DefaultResolveDomains,
EnableStatistics: DefaultTargetEnableStatistics, EnableStatistics: DefaultTargetEnableStatistics,
DefaultPorts: DefaultPorts,
} }
} }
@ -197,31 +199,11 @@ func (tp *TargetParser) parseHosts(input *TargetInput) ([]string, []error, []str
} }
} }
// 去重和验证同时分离host:port格式 // 去重和验证
hosts = tp.removeDuplicateStrings(hosts) hosts = tp.removeDuplicateStrings(hosts)
validHosts := make([]string, 0, len(hosts)) validHosts := make([]string, 0, len(hosts))
hostPorts := make([]string, 0)
for _, host := range hosts { for _, host := range hosts {
// 检查是否为host:port格式
if strings.Contains(host, ":") {
if h, portStr, err := net.SplitHostPort(host); err == nil {
// 验证端口号
if port, portErr := strconv.Atoi(portStr); portErr == nil && port >= MinPort && port <= MaxPort {
// 验证主机部分
if valid, hostErr := tp.validateHost(h); valid {
// 这是有效的host:port组合添加到hostPorts
hostPorts = append(hostPorts, host)
continue
} else if hostErr != nil {
warnings = append(warnings, fmt.Sprintf("无效主机端口组合: %s - %s", host, hostErr.Error()))
continue
}
}
}
}
// 作为普通主机验证
if valid, err := tp.validateHost(host); valid { if valid, err := tp.validateHost(host); valid {
validHosts = append(validHosts, host) validHosts = append(validHosts, host)
} else if err != nil { } else if err != nil {
@ -229,11 +211,6 @@ func (tp *TargetParser) parseHosts(input *TargetInput) ([]string, []error, []str
} }
} }
// 将找到的hostPorts合并到输入结果中通过修改input结构
if len(hostPorts) > 0 {
input.HostPort = append(input.HostPort, hostPorts...)
}
// 检查目标数量限制 // 检查目标数量限制
if len(validHosts) > tp.options.MaxTargets { if len(validHosts) > tp.options.MaxTargets {
warnings = append(warnings, fmt.Sprintf("主机数量超过限制,截取前%d个", tp.options.MaxTargets)) warnings = append(warnings, fmt.Sprintf("主机数量超过限制,截取前%d个", tp.options.MaxTargets))
@ -429,28 +406,10 @@ func (tp *TargetParser) parseHostList(hostStr string) ([]string, error) {
} }
hosts = append(hosts, rangeHosts...) hosts = append(hosts, rangeHosts...)
default: default:
// 检查是否为host:port格式
if strings.Contains(item, ":") {
if _, portStr, err := net.SplitHostPort(item); err == nil {
// 验证端口号
if port, portErr := strconv.Atoi(portStr); portErr == nil && port >= MinPort && port <= MaxPort {
// 这是有效的host:port格式但在这里仍然作为主机处理
// 在后续的processHostPorts函数中会被正确处理
hosts = append(hosts, item)
} else {
// 端口无效,作为普通主机处理
hosts = append(hosts, item)
}
} else {
// 不是有效的host:port格式作为普通主机处理
hosts = append(hosts, item)
}
} else {
// 单个IP或域名 // 单个IP或域名
hosts = append(hosts, item) hosts = append(hosts, item)
} }
} }
}
return hosts, nil return hosts, nil
} }
@ -738,19 +697,6 @@ func (tp *TargetParser) validateHost(host string) (bool, error) {
return false, fmt.Errorf("主机地址为空") return false, fmt.Errorf("主机地址为空")
} }
// 检查是否为host:port格式
if strings.Contains(host, ":") {
// 可能是host:port格式尝试分离
if h, portStr, err := net.SplitHostPort(host); err == nil {
// 验证端口号
if port, portErr := strconv.Atoi(portStr); portErr == nil && port >= MinPort && port <= MaxPort {
// 递归验证主机部分(不包含端口)
return tp.validateHost(h)
}
}
// 如果不是有效的host:port格式继续按普通主机地址处理
}
// 检查是否为IP地址 // 检查是否为IP地址
if ip := net.ParseIP(host); ip != nil { if ip := net.ParseIP(host); ip != nil {
return tp.validateIP(ip) return tp.validateIP(ip)

View File

@ -3,8 +3,6 @@ package parsers
import ( import (
"regexp" "regexp"
"time" "time"
"github.com/shadow1ng/fscan/common/base"
) )
/* /*
@ -136,11 +134,12 @@ const (
DefaultValidateURLs = true DefaultValidateURLs = true
DefaultResolveDomains = false DefaultResolveDomains = false
DefaultTargetEnableStatistics = true DefaultTargetEnableStatistics = true
DefaultPorts = "21,22,23,80,81,110,135,139,143,389,443,445,502,873,993,995,1433,1521,3306,5432,5672,6379,7001,7687,8000,8005,8009,8080,8089,8443,9000,9042,9092,9200,10051,11211,15672,27017,61616"
// 正则表达式模式 // 正则表达式模式
IPv4RegexPattern = `^(\d{1,3}\.){3}\d{1,3}$` IPv4RegexPattern = `^(\d{1,3}\.){3}\d{1,3}$`
PortRangeRegexPattern = `^(\d+)(-(\d+))?$` PortRangeRegexPattern = `^(\d+)(-(\d+))?$`
URLValidationRegexPattern = `^https?://[^\s]+$` URLValidationRegexPattern = `^https?://[^\s/$.?#].[^\s]*$`
DomainRegexPattern = `^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$` DomainRegexPattern = `^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$`
CookieRegexPattern = `^[^=;\s]+(=[^;\s]*)?(\s*;\s*[^=;\s]+(=[^;\s]*)?)*$` CookieRegexPattern = `^[^=;\s]+(=[^;\s]*)?(\s*;\s*[^=;\s]+(=[^;\s]*)?)*$`
@ -193,21 +192,25 @@ const (
SamplingMaxHost = 253 SamplingMaxHost = 253
) )
// GetPortGroups 获取预定义端口组映射(统一的端口组定义) // GetPortGroups 获取预定义端口组映射
func GetPortGroups() map[string]string { func GetPortGroups() map[string]string {
return map[string]string{ return map[string]string{
"web": base.WebPorts, // 使用实际的WebPorts常量 "web": "80,81,82,83,84,85,86,87,88,89,90,443,8000,8001,8002,8003,8004,8005,8006,8007,8008,8009,8010,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8443,9000,9001,9002,9080,9090",
"main": base.MainPorts, // 使用实际的MainPorts常量 "main": "21,22,23,80,81,110,135,139,143,389,443,445,502,873,993,995,1433,1521,3306,5432,5672,6379,7001,7687,8000,8005,8009,8080,8089,8443,9000,9042,9092,9200,10051,11211,15672,27017,61616",
"db": base.DbPorts, // 使用实际的DbPorts常量 "database": "1433,1521,3306,5432,6379,11211,27017",
"service": base.ServicePorts, // 使用实际的ServicePorts常量 "common": "21,22,23,25,53,80,110,135,139,143,443,445,993,995,1723,3389,5060,5985,5986",
"common": base.CommonPorts, // 使用实际的CommonPorts常量
"all": base.AllPorts, // 使用实际的AllPorts常量
} }
} }
// GetTargetPortGroups 获取目标解析器端口组映射(向后兼容,调用统一函数) // GetTargetPortGroups 获取目标解析器端口组映射
func GetTargetPortGroups() map[string]string { func GetTargetPortGroups() map[string]string {
return GetPortGroups() return map[string]string{
"service": "21,22,23,25,110,135,139,143,162,389,445,465,502,587,636,873,993,995,1433,1521,2222,3306,3389,5020,5432,5672,5671,6379,8161,8443,9000,9092,9093,9200,10051,11211,15672,15671,27017,61616,61613",
"db": "1433,1521,3306,5432,5672,6379,7687,9042,9093,9200,11211,27017,61616",
"web": "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8005,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10051,10250,12018,12443,14000,15672,15671,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,20880,21000,21501,21502,28018",
"all": "1-65535",
"main": "21,22,23,80,81,110,135,139,143,389,443,445,502,873,993,995,1433,1521,3306,5432,5672,6379,7001,7687,8000,8005,8009,8080,8089,8443,9000,9042,9092,9200,10051,11211,15672,27017,61616",
}
} }
// ============================================================================= // =============================================================================

View File

@ -1,62 +0,0 @@
package common
import (
"context"
)
// TargetInfo 包装HostInfo提供更丰富的功能
type TargetInfo struct {
*HostInfo // 嵌入HostInfo保持向后兼容
context context.Context
metadata map[string]interface{}
}
// NewTargetInfo 创建新的目标信息
func NewTargetInfo(hostInfo HostInfo) *TargetInfo {
return &TargetInfo{
HostInfo: &hostInfo,
context: context.Background(),
metadata: make(map[string]interface{}),
}
}
// WithContext 设置上下文
func (t *TargetInfo) WithContext(ctx context.Context) *TargetInfo {
t.context = ctx
return t
}
// SetMetadata 设置元数据
func (t *TargetInfo) SetMetadata(key string, value interface{}) *TargetInfo {
if t.metadata == nil {
t.metadata = make(map[string]interface{})
}
t.metadata[key] = value
return t
}
// GetMetadata 获取元数据
func (t *TargetInfo) GetMetadata(key string) (interface{}, bool) {
if t.metadata == nil {
return nil, false
}
value, exists := t.metadata[key]
return value, exists
}
// String 返回字符串表示
func (t *TargetInfo) String() string {
return HostInfoString(t.HostInfo)
}
// HasMetadata 检查是否有指定的元数据
func (t *TargetInfo) HasMetadata(key string) bool {
_, exists := t.GetMetadata(key)
return exists
}

View File

@ -1,6 +1,8 @@
package utils package utils
import ( import (
"log"
"runtime"
"time" "time"
) )
@ -23,19 +25,102 @@ func NewMemoryMonitor(maxHeapMB uint64, maxGoroutines int, checkInterval time.Du
} }
} }
// 已移除未使用的 Start 方法 // Start 启动内存监控
func (mm *MemoryMonitor) Start() {
if mm.running {
return
}
// 已移除未使用的 Stop 方法 mm.running = true
go mm.monitor()
}
// 已移除未使用的 monitor 方法 // Stop 停止内存监控
func (mm *MemoryMonitor) Stop() {
if !mm.running {
return
}
// 已移除未使用的 checkMemory 方法 mm.running = false
select {
case mm.stopChan <- true:
default:
}
}
// 已移除未使用的 GetMemoryStats 方法 // monitor 监控循环
func (mm *MemoryMonitor) monitor() {
ticker := time.NewTicker(mm.checkInterval)
defer ticker.Stop()
// 已移除未使用的 ForceGC 方法 for {
select {
case <-ticker.C:
mm.checkMemory()
case <-mm.stopChan:
return
}
}
}
// 已移除未使用的 getHeapSize 方法 // checkMemory 检查内存使用情况
func (mm *MemoryMonitor) checkMemory() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
heapMB := m.HeapInuse / 1024 / 1024
goroutineCount := runtime.NumGoroutine()
// 检查堆内存使用
if heapMB > mm.maxHeapMB {
log.Printf("[WARN] 内存使用警告: 堆内存使用过高 %d MB (阈值: %d MB)", heapMB, mm.maxHeapMB)
// 尝试触发GC
runtime.GC()
// 再次检查
runtime.ReadMemStats(&m)
heapMBAfterGC := m.HeapInuse / 1024 / 1024
log.Printf("[INFO] GC后堆内存: %d MB", heapMBAfterGC)
}
// 检查goroutine数量
if goroutineCount > mm.maxGoroutines {
log.Printf("[WARN] Goroutine数量警告: 当前数量 %d (阈值: %d)", goroutineCount, mm.maxGoroutines)
}
}
// GetMemoryStats 获取当前内存统计信息
func (mm *MemoryMonitor) GetMemoryStats() map[string]interface{} {
var m runtime.MemStats
runtime.ReadMemStats(&m)
return map[string]interface{}{
"heap_inuse_mb": m.HeapInuse / 1024 / 1024,
"heap_alloc_mb": m.HeapAlloc / 1024 / 1024,
"sys_mb": m.Sys / 1024 / 1024,
"num_gc": m.NumGC,
"num_goroutines": runtime.NumGoroutine(),
"last_gc_time": time.Unix(0, int64(m.LastGC)),
}
}
// ForceGC 强制执行垃圾回收
func (mm *MemoryMonitor) ForceGC() {
before := mm.getHeapSize()
runtime.GC()
after := mm.getHeapSize()
// 使用log包直接输出避免undefined common错误
log.Printf("[INFO] 强制GC: 释放内存 %d MB", (before-after)/1024/1024)
}
// getHeapSize 获取当前堆大小
func (mm *MemoryMonitor) getHeapSize() uint64 {
var m runtime.MemStats
runtime.ReadMemStats(&m)
return m.HeapInuse
}
// 默认内存监控器实例 // 默认内存监控器实例
var DefaultMemMonitor = NewMemoryMonitor( var DefaultMemMonitor = NewMemoryMonitor(
@ -44,6 +129,12 @@ var DefaultMemMonitor = NewMemoryMonitor(
30*time.Second, // 30秒检查一次 30*time.Second, // 30秒检查一次
) )
// 已移除未使用的 StartDefaultMonitor 方法 // StartDefaultMonitor 启动默认内存监控器
func StartDefaultMonitor() {
DefaultMemMonitor.Start()
}
// 已移除未使用的 StopDefaultMonitor 方法 // StopDefaultMonitor 停止默认内存监控器
func StopDefaultMonitor() {
DefaultMemMonitor.Stop()
}

View File

@ -2,7 +2,25 @@ package utils
// 已移除未使用的 DeduplicateStrings 方法 // DeduplicateStrings 高效字符串去重 - 简化版本
func DeduplicateStrings(slice []string) []string {
if len(slice) <= 1 {
return slice
}
// 使用最简单高效的实现
seen := make(map[string]struct{}, len(slice))
result := make([]string, 0, len(slice))
for _, item := range slice {
if _, exists := seen[item]; !exists {
seen[item] = struct{}{}
result = append(result, item)
}
}
return result
}

View File

@ -108,8 +108,7 @@ func (b *BaseScanStrategy) isPluginTypeMatchedByName(pluginName string) bool {
case FilterLocal: case FilterLocal:
return metadata.Category == "local" return metadata.Category == "local"
case FilterService: case FilterService:
// 服务扫描允许service类型以及通过智能检测的web类型在上层逻辑中处理 return metadata.Category == "service"
return metadata.Category == "service" || metadata.Category == "web"
case FilterWeb: case FilterWeb:
return metadata.Category == "web" return metadata.Category == "web"
default: default:
@ -123,7 +122,6 @@ func (b *BaseScanStrategy) isPluginTypeMatched(plugin common.ScanPlugin) bool {
case FilterLocal: case FilterLocal:
return plugin.HasType(common.PluginTypeLocal) return plugin.HasType(common.PluginTypeLocal)
case FilterService: case FilterService:
// 服务扫描排除本地插件允许服务和Web插件
return !plugin.HasType(common.PluginTypeLocal) return !plugin.HasType(common.PluginTypeLocal)
case FilterWeb: case FilterWeb:
return plugin.HasType(common.PluginTypeWeb) return plugin.HasType(common.PluginTypeWeb)
@ -133,7 +131,7 @@ func (b *BaseScanStrategy) isPluginTypeMatched(plugin common.ScanPlugin) bool {
} }
// IsPluginApplicableByName 根据插件名称判断是否适用(新方法) // IsPluginApplicableByName 根据插件名称判断是否适用(新方法)
func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetHost string, targetPort int, isCustomMode bool) bool { func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件 // 自定义模式下运行所有明确指定的插件
if isCustomMode { if isCustomMode {
return true return true
@ -145,7 +143,7 @@ func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetHos
} }
// 智能Web插件检测如果是Web插件且检测到Web服务则包含Web插件 // 智能Web插件检测如果是Web插件且检测到Web服务则包含Web插件
if b.shouldIncludeWebPlugin(metadata, targetHost, targetPort) { if b.shouldIncludeWebPlugin(metadata, targetPort) {
return true return true
} }
@ -164,21 +162,11 @@ func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetHos
return false return false
} }
// 对于Web插件的特殊处理
if metadata.Category == "web" {
// Web扫描策略下直接允许Web插件执行用户明确指定了Web目标
if b.filterType == FilterWeb {
return true
}
// 其他策略下必须通过智能检测才能执行
return false
}
return true return true
} }
// shouldIncludeWebPlugin 判断是否应该包含Web插件智能检测 // shouldIncludeWebPlugin 判断是否应该包含Web插件智能检测
func (b *BaseScanStrategy) shouldIncludeWebPlugin(metadata *base.PluginMetadata, targetHost string, targetPort int) bool { func (b *BaseScanStrategy) shouldIncludeWebPlugin(metadata *base.PluginMetadata, targetPort int) bool {
// 只对服务扫描策略启用Web插件智能检测 // 只对服务扫描策略启用Web插件智能检测
if b.filterType != FilterService { if b.filterType != FilterService {
return false return false
@ -199,14 +187,14 @@ func (b *BaseScanStrategy) shouldIncludeWebPlugin(metadata *base.PluginMetadata,
globalWebDetector = NewWebPortDetector() globalWebDetector = NewWebPortDetector()
} }
// 检测是否为Web服务使用完整的智能检测包括HTTP协议探测) // 检测是否为Web服务这里暂时只检查常见端口避免每次都进行HTTP探测)
return globalWebDetector.IsWebService(targetHost, targetPort) return globalWebDetector.IsCommonWebPort(targetPort)
} }
// globalWebDetector Web检测器全局实例 // globalWebDetector Web检测器全局实例
var globalWebDetector *WebPortDetector var globalWebDetector *WebPortDetector
// IsPluginApplicable 判断插件是否适用(通用实现,传统插件系统 // IsPluginApplicable 判断插件是否适用(通用实现
func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool { func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件 // 自定义模式下运行所有明确指定的插件
if isCustomMode { if isCustomMode {
@ -218,21 +206,6 @@ func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPo
return false return false
} }
// 对于服务扫描中的Web插件需要进行智能检测传统插件系统
if b.filterType == FilterService && plugin.HasType(common.PluginTypeWeb) {
// 获取Web检测器实例延迟初始化
if globalWebDetector == nil {
globalWebDetector = NewWebPortDetector()
}
// 注意传统插件系统无法传递host参数使用空字符串
// 这是传统插件系统的限制,新插件系统已经解决了这个问题
if targetPort > 0 {
return globalWebDetector.IsWebService("", targetPort)
}
return false
}
// 对于服务扫描,还需检查端口匹配 // 对于服务扫描,还需检查端口匹配
if b.filterType == FilterService { if b.filterType == FilterService {
// 无端口限制的插件适用于所有端口 // 无端口限制的插件适用于所有端口

140
Core/PluginAdapter.go Normal file
View File

@ -0,0 +1,140 @@
package core
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// PluginAdapter 插件适配器
// 提供从新插件系统到旧扫描接口的适配
type PluginAdapter struct {
registry *base.PluginRegistry
}
// NewPluginAdapter 创建插件适配器
func NewPluginAdapter() *PluginAdapter {
return &PluginAdapter{
registry: base.GlobalPluginRegistry,
}
}
// 全局插件适配器实例
var GlobalPluginAdapter = NewPluginAdapter()
// GetAllPluginNames 获取所有插件名称
func (pa *PluginAdapter) GetAllPluginNames() []string {
return pa.registry.GetAll()
}
// PluginExists 检查插件是否存在
func (pa *PluginAdapter) PluginExists(name string) bool {
metadata := pa.registry.GetMetadata(name)
return metadata != nil
}
// GetPluginPorts 获取插件支持的端口
func (pa *PluginAdapter) GetPluginPorts(name string) []int {
metadata := pa.registry.GetMetadata(name)
if metadata != nil {
return metadata.Ports
}
return []int{}
}
// GetPluginsByPort 根据端口获取支持的插件
func (pa *PluginAdapter) GetPluginsByPort(port int) []string {
var plugins []string
for _, name := range pa.registry.GetAll() {
metadata := pa.registry.GetMetadata(name)
if metadata != nil {
for _, p := range metadata.Ports {
if p == port {
plugins = append(plugins, name)
break
}
}
}
}
return plugins
}
// GetPluginsByType 根据类型获取插件
func (pa *PluginAdapter) GetPluginsByType(pluginType string) []string {
var plugins []string
for _, name := range pa.registry.GetAll() {
metadata := pa.registry.GetMetadata(name)
if metadata != nil {
if metadata.Category == pluginType {
plugins = append(plugins, name)
}
}
}
return plugins
}
// ScanWithPlugin 使用插件进行扫描
func (pa *PluginAdapter) ScanWithPlugin(pluginName string, info *common.HostInfo) error {
common.LogDebug(fmt.Sprintf("使用新插件架构扫描: %s", pluginName))
// 创建插件实例
plugin, err := pa.registry.Create(pluginName)
if err != nil {
return fmt.Errorf("创建插件 %s 失败: %v", pluginName, err)
}
// 执行扫描
result, err := plugin.Scan(context.Background(), info)
if err != nil {
return fmt.Errorf("插件 %s 扫描失败: %v", pluginName, err)
}
// 处理扫描结果
if result == nil {
common.LogDebug(fmt.Sprintf("插件 %s 返回了空结果", pluginName))
} else if result.Success {
common.LogDebug(fmt.Sprintf("插件 %s 扫描成功", pluginName))
// TODO: 输出扫描结果
} else {
common.LogDebug(fmt.Sprintf("插件 %s 扫描失败: %v", pluginName, result.Error))
}
return nil
}
// FilterPluginsByType 按类型过滤插件名称
func FilterPluginsByType(pluginType string) func(name string) bool {
return func(name string) bool {
metadata := GlobalPluginAdapter.registry.GetMetadata(name)
if metadata == nil {
return false
}
switch pluginType {
case common.PluginTypeService:
return metadata.Category == "service"
case common.PluginTypeWeb:
return metadata.Category == "web"
case common.PluginTypeLocal:
return metadata.Category == "local"
default:
return true
}
}
}
// GetServicePlugins 获取所有服务插件
func GetServicePlugins() []string {
return GlobalPluginAdapter.GetPluginsByType("service")
}
// GetWebPlugins 获取所有Web插件
func GetWebPlugins() []string {
return GlobalPluginAdapter.GetPluginsByType("web")
}
// GetLocalPlugins 获取所有本地插件
func GetLocalPlugins() []string {
return GlobalPluginAdapter.GetPluginsByType("local")
}

View File

@ -60,15 +60,6 @@ func (p *PortDiscoveryService) shouldPerformLivenessCheck(hosts []string) bool {
func (p *PortDiscoveryService) discoverAlivePorts(hosts []string) []string { func (p *PortDiscoveryService) discoverAlivePorts(hosts []string) []string {
var alivePorts []string var alivePorts []string
// 如果已经有明确指定的host:port则优先使用并跳过常规端口扫描
if len(common.HostPort) > 0 {
alivePorts = append(alivePorts, common.HostPort...)
alivePorts = common.RemoveDuplicate(alivePorts)
common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts)))
common.HostPort = nil
return alivePorts
}
// 根据扫描模式选择端口扫描方式 // 根据扫描模式选择端口扫描方式
if len(hosts) > 0 { if len(hosts) > 0 {
alivePorts = EnhancedPortScan(hosts, common.Ports, common.Timeout) alivePorts = EnhancedPortScan(hosts, common.Ports, common.Timeout)
@ -82,6 +73,14 @@ func (p *PortDiscoveryService) discoverAlivePorts(hosts []string) []string {
common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts))) common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts)))
} }
// 合并额外指定的端口
if len(common.HostPort) > 0 {
alivePorts = append(alivePorts, common.HostPort...)
alivePorts = common.RemoveDuplicate(alivePorts)
common.HostPort = nil
common.LogBase(i18n.GetText("scan_alive_ports_count", len(alivePorts)))
}
return alivePorts return alivePorts
} }

View File

@ -5,7 +5,7 @@ import (
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base" "github.com/shadow1ng/fscan/plugins/base"
// 导入跨平台服务插件(可在所有平台上运行) // 导入新架构插件,触发自动注册
_ "github.com/shadow1ng/fscan/plugins/services/activemq" _ "github.com/shadow1ng/fscan/plugins/services/activemq"
_ "github.com/shadow1ng/fscan/plugins/services/cassandra" _ "github.com/shadow1ng/fscan/plugins/services/cassandra"
_ "github.com/shadow1ng/fscan/plugins/services/ftp" _ "github.com/shadow1ng/fscan/plugins/services/ftp"
@ -28,30 +28,20 @@ import (
_ "github.com/shadow1ng/fscan/plugins/services/snmp" _ "github.com/shadow1ng/fscan/plugins/services/snmp"
_ "github.com/shadow1ng/fscan/plugins/services/ssh" _ "github.com/shadow1ng/fscan/plugins/services/ssh"
_ "github.com/shadow1ng/fscan/plugins/services/telnet" _ "github.com/shadow1ng/fscan/plugins/services/telnet"
_ "github.com/shadow1ng/fscan/plugins/services/vnc"
// 导入跨平台Legacy插件 // 导入Legacy插件适配器
_ "github.com/shadow1ng/fscan/plugins/legacy/elasticsearch" // 跨平台服务 _ "github.com/shadow1ng/fscan/plugins/legacy/netbios"
_ "github.com/shadow1ng/fscan/plugins/legacy/findnet" // 网络发现 _ "github.com/shadow1ng/fscan/plugins/legacy/ms17010"
_ "github.com/shadow1ng/fscan/plugins/legacy/ms17010" // Windows SMB漏洞但扫描器可跨平台 _ "github.com/shadow1ng/fscan/plugins/legacy/smb"
_ "github.com/shadow1ng/fscan/plugins/legacy/netbios" // NetBIOS协议主要Windows但可跨平台扫描 _ "github.com/shadow1ng/fscan/plugins/legacy/smb2"
_ "github.com/shadow1ng/fscan/plugins/legacy/rdp" // RDP协议扫描可跨平台 _ "github.com/shadow1ng/fscan/plugins/legacy/smbghost"
_ "github.com/shadow1ng/fscan/plugins/legacy/smb" // SMB协议扫描可跨平台 _ "github.com/shadow1ng/fscan/plugins/legacy/rdp"
_ "github.com/shadow1ng/fscan/plugins/legacy/smb2" // SMBv2协议扫描可跨平台 _ "github.com/shadow1ng/fscan/plugins/legacy/elasticsearch"
_ "github.com/shadow1ng/fscan/plugins/legacy/smbghost" // SMBGhost漏洞扫描可跨平台 _ "github.com/shadow1ng/fscan/plugins/legacy/findnet"
// 导入Web插件适配器 // 导入Web插件适配器
_ "github.com/shadow1ng/fscan/plugins/legacy/webtitle" _ "github.com/shadow1ng/fscan/plugins/legacy/webtitle"
_ "github.com/shadow1ng/fscan/plugins/legacy/webpoc" _ "github.com/shadow1ng/fscan/plugins/legacy/webpoc"
// 导入跨平台本地插件(可在所有平台上运行)
_ "github.com/shadow1ng/fscan/plugins/local/cleaner" // 系统痕迹清理
_ "github.com/shadow1ng/fscan/plugins/local/downloader" // 文件下载
_ "github.com/shadow1ng/fscan/plugins/local/fileinfo" // 文件信息收集
_ "github.com/shadow1ng/fscan/plugins/local/forwardshell" // 正向Shell
_ "github.com/shadow1ng/fscan/plugins/local/keylogger" // 键盘记录主要Windows但支持跨平台
_ "github.com/shadow1ng/fscan/plugins/local/reverseshell" // 反弹Shell
_ "github.com/shadow1ng/fscan/plugins/local/socks5proxy" // SOCKS5代理
) )
// ============================================================================= // =============================================================================
@ -83,15 +73,49 @@ func InitializePluginSystem() error {
return nil return nil
} }
// 已移除未使用的 GetAllPlugins 方法 // GetAllPlugins 获取所有已注册插件名称
func GetAllPlugins() []string {
return base.GlobalPluginRegistry.GetAll()
}
// 已移除未使用的 GetPluginMetadata 方法 // GetPluginMetadata 获取插件元数据
func GetPluginMetadata(name string) *base.PluginMetadata {
return base.GlobalPluginRegistry.GetMetadata(name)
}
// 已移除未使用的 CreatePlugin 方法 // CreatePlugin 创建插件实例
func CreatePlugin(name string) (base.Plugin, error) {
return base.GlobalPluginRegistry.Create(name)
}
// 已移除未使用的 GetPluginsByCategory 方法 // GetPluginsByCategory 按类别获取插件
func GetPluginsByCategory(category string) []string {
var plugins []string
for _, name := range base.GlobalPluginRegistry.GetAll() {
if metadata := base.GlobalPluginRegistry.GetMetadata(name); metadata != nil {
if metadata.Category == category {
plugins = append(plugins, name)
}
}
}
return plugins
}
// 已移除未使用的 GetPluginsByPort 方法 // GetPluginsByPort 按端口获取插件
func GetPluginsByPort(port int) []string {
var plugins []string
for _, name := range base.GlobalPluginRegistry.GetAll() {
if metadata := base.GlobalPluginRegistry.GetMetadata(name); metadata != nil {
for _, p := range metadata.Ports {
if p == port {
plugins = append(plugins, name)
break
}
}
}
}
return plugins
}
// init 自动初始化插件系统 // init 自动初始化插件系统
func init() { func init() {

View File

@ -19,7 +19,7 @@ type ScanStrategy interface {
// 插件管理方法 // 插件管理方法
GetPlugins() ([]string, bool) GetPlugins() ([]string, bool)
IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool
IsPluginApplicableByName(pluginName string, targetHost string, targetPort int, isCustomMode bool) bool IsPluginApplicableByName(pluginName string, targetPort int, isCustomMode bool) bool
} }
// selectStrategy 根据扫描配置选择适当的扫描策略 // selectStrategy 根据扫描配置选择适当的扫描策略
@ -125,7 +125,7 @@ func ExecuteScanTasks(targets []common.HostInfo, strategy ScanStrategy, ch *chan
} }
// 检查插件是否适用于当前目标 // 检查插件是否适用于当前目标
if strategy.IsPluginApplicableByName(pluginName, target.Host, targetPort, isCustomMode) { if strategy.IsPluginApplicableByName(pluginName, targetPort, isCustomMode) {
executeScanTask(pluginName, target, ch, wg) executeScanTask(pluginName, target, ch, wg)
} }
} }
@ -143,7 +143,7 @@ func countApplicableTasks(targets []common.HostInfo, pluginsToRun []string, isCu
for _, pluginName := range pluginsToRun { for _, pluginName := range pluginsToRun {
if GlobalPluginAdapter.PluginExists(pluginName) && if GlobalPluginAdapter.PluginExists(pluginName) &&
strategy.IsPluginApplicableByName(pluginName, target.Host, targetPort, isCustomMode) { strategy.IsPluginApplicableByName(pluginName, targetPort, isCustomMode) {
count++ count++
} }
} }
@ -187,4 +187,7 @@ func executeScanTask(pluginName string, target common.HostInfo, ch *chan struct{
} }
// 已移除未使用的 Scan 方法 // Scan 入口函数,向后兼容旧的调用方式
func Scan(info common.HostInfo) {
RunScan(info)
}

View File

@ -144,7 +144,7 @@ func (s *ServiceScanStrategy) isPluginApplicableToAnyPort(plugin common.ScanPlug
return true return true
} }
// 服务扫描排除本地插件但保留Web插件有智能检测 // 非自定义模式下,排除本地插件
if plugin.HasType(common.PluginTypeLocal) { if plugin.HasType(common.PluginTypeLocal) {
return false return false
} }
@ -166,11 +166,6 @@ func (s *ServiceScanStrategy) isPluginApplicableToAnyPort(plugin common.ScanPlug
// isNewPluginApplicableToAnyPort 检查新插件架构的插件是否对任何端口适用 // isNewPluginApplicableToAnyPort 检查新插件架构的插件是否对任何端口适用
func (s *ServiceScanStrategy) isNewPluginApplicableToAnyPort(metadata *base.PluginMetadata, portSet map[int]bool, isCustomMode bool) bool { func (s *ServiceScanStrategy) isNewPluginApplicableToAnyPort(metadata *base.PluginMetadata, portSet map[int]bool, isCustomMode bool) bool {
// 服务扫描排除本地插件但保留service和web类型web有智能检测
if metadata.Category == "local" {
return false
}
// 自定义模式下运行所有明确指定的插件 // 自定义模式下运行所有明确指定的插件
if isCustomMode { if isCustomMode {
return true return true

145
Core/WebDetection.go Normal file
View File

@ -0,0 +1,145 @@
package core
import (
"context"
"net"
"net/http"
"strings"
"time"
)
// WebPortDetector Web端口检测器
type WebPortDetector struct {
// 常见Web端口列表
commonWebPorts map[int]bool
// HTTP检测超时时间
httpTimeout time.Duration
}
// NewWebPortDetector 创建Web端口检测器
func NewWebPortDetector() *WebPortDetector {
// 定义常见Web端口
commonPorts := map[int]bool{
80: true, // HTTP
443: true, // HTTPS
8080: true, // HTTP alternate
8443: true, // HTTPS alternate
8000: true, // Development server
8888: true, // Common dev port
9000: true, // Common dev port
9090: true, // Common dev port
3000: true, // Node.js dev server
4000: true, // Ruby dev server
5000: true, // Python dev server
8081: true, // HTTP alternate
8082: true, // HTTP alternate
8083: true, // HTTP alternate
8084: true, // HTTP alternate
8085: true, // HTTP alternate
8086: true, // HTTP alternate
8087: true, // HTTP alternate
8088: true, // HTTP alternate
8089: true, // HTTP alternate
}
return &WebPortDetector{
commonWebPorts: commonPorts,
httpTimeout: 2 * time.Second, // 2秒超时快速检测
}
}
// IsWebService 检测指定主机端口是否为Web服务
func (w *WebPortDetector) IsWebService(host string, port int) bool {
// 1. 首先检查是否为常见Web端口
if w.commonWebPorts[port] {
return true
}
// 2. 对于非常见端口进行HTTP协议检测
return w.detectHTTPService(host, port)
}
// IsCommonWebPort 检查是否为常见Web端口
func (w *WebPortDetector) IsCommonWebPort(port int) bool {
return w.commonWebPorts[port]
}
// detectHTTPService 检测HTTP服务轻量级探测
func (w *WebPortDetector) detectHTTPService(host string, port int) bool {
// 创建检测上下文,避免长时间阻塞
ctx, cancel := context.WithTimeout(context.Background(), w.httpTimeout)
defer cancel()
// 尝试HTTP连接
if w.tryHTTPConnection(ctx, host, port, "http") {
return true
}
// 尝试HTTPS连接对于高端口常见
if w.tryHTTPConnection(ctx, host, port, "https") {
return true
}
return false
}
// tryHTTPConnection 尝试HTTP连接
func (w *WebPortDetector) tryHTTPConnection(ctx context.Context, host string, port int, protocol string) bool {
url := ""
if port == 80 && protocol == "http" {
url = "http://" + host
} else if port == 443 && protocol == "https" {
url = "https://" + host
} else {
url = protocol + "://" + host + ":" + string(rune(port))
}
// 创建HTTP客户端快速检测
client := &http.Client{
Timeout: w.httpTimeout,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: w.httpTimeout,
}).DialContext,
TLSHandshakeTimeout: w.httpTimeout,
ResponseHeaderTimeout: w.httpTimeout,
DisableKeepAlives: true,
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // 不跟随重定向,加快检测速度
},
}
// 发送HEAD请求最小化网络开销
req, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
if err != nil {
return false
}
req.Header.Set("User-Agent", "fscan-web-detector/2.0")
resp, err := client.Do(req)
if err != nil {
// 检查错误类型某些错误也表明是HTTP服务
errStr := strings.ToLower(err.Error())
if strings.Contains(errStr, "http") ||
strings.Contains(errStr, "malformed http") ||
strings.Contains(errStr, "server closed") {
return true
}
return false
}
defer resp.Body.Close()
// 任何HTTP响应都表明这是Web服务
return resp.StatusCode > 0
}
// GetCommonWebPorts 获取常见Web端口列表
func (w *WebPortDetector) GetCommonWebPorts() []int {
ports := make([]int, 0, len(w.commonWebPorts))
for port := range w.commonWebPorts {
ports = append(ports, port)
}
return ports
}

View File

@ -1,8 +1,6 @@
package core package core
import ( import (
"fmt"
"net/url"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/common/i18n"
"strings" "strings"
@ -56,55 +54,16 @@ func (s *WebScanStrategy) Execute(info common.HostInfo, ch *chan struct{}, wg *s
func (s *WebScanStrategy) PrepareTargets(baseInfo common.HostInfo) []common.HostInfo { func (s *WebScanStrategy) PrepareTargets(baseInfo common.HostInfo) []common.HostInfo {
var targetInfos []common.HostInfo var targetInfos []common.HostInfo
// 首先从common.URLs获取目标 for _, url := range common.URLs {
for _, urlStr := range common.URLs { urlInfo := baseInfo
urlInfo := s.createTargetFromURL(baseInfo, urlStr) // 确保URL包含协议头
if urlInfo != nil { if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
targetInfos = append(targetInfos, *urlInfo) url = "http://" + url
}
}
// 如果common.URLs为空但baseInfo.Url有值使用baseInfo.Url
if len(targetInfos) == 0 && baseInfo.Url != "" {
urlInfo := s.createTargetFromURL(baseInfo, baseInfo.Url)
if urlInfo != nil {
targetInfos = append(targetInfos, *urlInfo)
} }
urlInfo.Url = url
targetInfos = append(targetInfos, urlInfo)
} }
return targetInfos return targetInfos
} }
// createTargetFromURL 从URL创建目标信息
func (s *WebScanStrategy) createTargetFromURL(baseInfo common.HostInfo, urlStr string) *common.HostInfo {
// 确保URL包含协议头
if !strings.HasPrefix(urlStr, "http://") && !strings.HasPrefix(urlStr, "https://") {
urlStr = "http://" + urlStr
}
// 解析URL获取Host和Port信息
parsedURL, err := url.Parse(urlStr)
if err != nil {
common.LogError(fmt.Sprintf("解析URL失败: %s - %v", urlStr, err))
return nil
}
urlInfo := baseInfo
urlInfo.Url = urlStr
urlInfo.Host = parsedURL.Hostname()
// 设置端口
port := parsedURL.Port()
if port == "" {
// 根据协议设置默认端口
if parsedURL.Scheme == "https" {
port = "443"
} else {
port = "80"
}
}
urlInfo.Ports = port
return &urlInfo
}

View File

@ -1,262 +0,0 @@
# Fscan 参数说明文档
**版本**: v2.2.1
**分支**: v2.2.1
## 基础扫描参数
### 目标指定
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-h` | 目标主机: IP, IP段, IP段文件, 域名, **支持host:port格式** | - | `-h 192.168.1.1/24``-h 127.0.0.1:135` |
| `-hf` | 主机文件 | - | `-hf hosts.txt` |
| `-u` | 目标URL | - | `-u http://example.com` |
| `-uf` | URL文件 | - | `-uf urls.txt` |
| `-domain` | 域名 | - | `-domain example.com` |
### 端口扫描
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-p` | 端口扫描范围 | 1000个常用端口 | `-p 1-65535` |
| `-pf` | 端口文件 | - | `-pf ports.txt` |
| `-ep` | 排除端口 | - | `-ep 80,443` |
| `-t` | 端口扫描线程数 | 600 | `-t 1000` |
| `-time` | 端口扫描超时时间 | 3秒 | `-time 5` |
### 扫描模式
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-m` | 扫描模式: all(全部), icmp(存活探测), 或指定插件名称 | all | `-m icmp` |
| `-ao` | 仅进行存活探测 | - | `-ao` |
| `-full` | 全量POC扫描 | - | `-full` |
| `-np` | 禁用ping探测 | - | `-np` |
## 认证参数
### 用户凭据
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-user` | 用户名 | - | `-user admin` |
| `-usera` | 额外用户名 | - | `-usera root,administrator` |
| `-userf` | 用户名字典文件 | - | `-userf users.txt` |
| `-pwd` | 密码 | - | `-pwd 123456` |
| `-pwda` | 额外密码 | - | `-pwda password,admin` |
| `-pwdf` | 密码字典文件 | - | `-pwdf pass.txt` |
| `-hash` | 哈希值 | - | `-hash ntlm_hash` |
| `-hashf` | 哈希文件 | - | `-hashf hashes.txt` |
| `-sshkey` | SSH私钥文件 | - | `-sshkey id_rsa` |
## 网络配置
### HTTP设置
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-cookie` | HTTP Cookie | - | `-cookie "session=abc123"` |
| `-proxy` | HTTP代理 | - | `-proxy http://127.0.0.1:8080` |
| `-socks5` | SOCKS5代理支持所有服务扫描 | - | `-socks5 127.0.0.1:1080` |
| `-start-socks5` | 启动SOCKS5代理服务器端口 | - | `-start-socks5 1080` |
| `-wt` | Web超时时间 | 5秒 | `-wt 10` |
## POC扫描
### POC配置
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-pocname` | POC名称 | - | `-pocname ms17-010` |
| `-pocpath` | POC脚本路径 | - | `-pocpath ./pocs/` |
| `-num` | POC并发数 | 20 | `-num 50` |
| `-nopoc` | 禁用POC扫描 | - | `-nopoc` |
## 高级功能
### Redis操作
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-rf` | Redis文件 | - | `-rf redis.txt` |
| `-rs` | Redis Shell | - | `-rs "echo test"` |
| `-rwf` | Redis写入文件 | - | `-rwf /tmp/test.txt` |
| `-rwp` | Redis写入路径 | - | `-rwp /var/www/html/` |
| `-rwc` | Redis写入内容 | - | `-rwc "<?php phpinfo(); ?>"` |
### Shell功能
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-rsh` | 反弹Shell目标地址:端口 | - | `-rsh 192.168.1.100:4444` |
| `-fsh-port` | 启动正向Shell服务器端口 | 4444 | `-fsh-port 8888` |
| `-sc` | Shellcode | - | `-sc payload` |
### 新增功能改进
- **智能目标识别**: `-h`参数现在支持直接指定`host:port`格式,如`-h 192.168.1.1:3306`
- **优化扫描逻辑**: 当使用`host:port`格式时,自动跳过不必要的端口扫描,直接进行服务检测
- **SOCKS5全面支持**: SOCKS5代理现已支持所有服务扫描类型包括数据库和SSH等服务
**代理支持范围:**
- **HTTP代理** (`-proxy`): 仅支持Web扫描
- **SOCKS5代理** (`-socks5`): 支持所有服务扫描MySQL、PostgreSQL、Redis、SSH等和Web扫描
- **代理失败处理**: 当代理连接失败时,自动回退到直连模式
## 本地插件系统
### 本地模式
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-local` | 指定本地插件名称 | - | `-local cleaner` |
### 可用本地插件
| 插件名 | 功能说明 | 平台支持 |
|--------|----------|----------|
| `avdetect` | 杀毒软件检测 | Windows/Linux/macOS |
| `fileinfo` | 文件信息收集 | Windows/Linux/macOS |
| `dcinfo` | 域控信息收集 | Windows/Linux/macOS |
| `minidump` | 内存转储 | Windows |
| `reverseshell` | 反弹Shell | Windows/Linux/macOS |
| `socks5proxy` | SOCKS5代理 | Windows/Linux/macOS |
| `forwardshell` | 正向Shell | Windows/Linux/macOS |
| `ldpreload` | Linux LD_PRELOAD持久化 | Linux |
| `shellenv` | Shell环境变量持久化 | Linux |
| `crontask` | Cron计划任务持久化 | Linux |
| `systemdservice` | Systemd服务持久化 | Linux |
| `winregistry` | Windows注册表持久化 | Windows |
| `winstartup` | Windows启动文件夹持久化 | Windows |
| `winschtask` | Windows计划任务持久化 | Windows |
| `winservice` | Windows服务持久化 | Windows |
| `winwmi` | Windows WMI事件订阅持久化 | Windows |
| `keylogger` | 跨平台键盘记录 | Windows/Linux/macOS |
| `downloader` | 跨平台文件下载 | Windows/Linux/macOS |
| `cleaner` | 跨平台系统痕迹清理 | Windows/Linux/macOS |
### 插件特定参数
| 参数 | 说明 | 适用插件 | 示例 |
|------|------|----------|------|
| `-keylog-output` | 键盘记录输出文件路径 | keylogger | `-keylog-output keylog.txt` |
| `-download-url` | 要下载的文件URL | downloader | `-download-url http://example.com/file.exe` |
| `-download-path` | 下载文件保存路径 | downloader | `-download-path /tmp/` |
| `-persistence-file` | Linux持久化目标文件路径 | ldpreload, shellenv 等 | `-persistence-file target.elf` |
| `-win-pe` | Windows持久化目标PE文件路径 | Windows持久化插件 | `-win-pe target.exe` |
## 输出控制
### 输出格式
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-o` | 输出文件 | result.txt | `-o scan_result.txt` |
| `-f` | 输出格式: txt, json, csv | txt | `-f json` |
### 显示控制
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-silent` | 静默模式 | - | `-silent` |
| `-nocolor` | 禁用颜色输出 | - | `-nocolor` |
| `-nopg` | 禁用进度条 | - | `-nopg` |
| `-no` | 禁用结果保存 | - | `-no` |
## 系统配置
### 线程与超时
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-mt` | 模块线程数 | 50 | `-mt 100` |
| `-gt` | 全局超时时间 | 180秒 | `-gt 300` |
| `-retry` | 最大重试次数 | 3 | `-retry 5` |
### 功能开关
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-nobr` | 禁用暴力破解 | - | `-nobr` |
| `-fp` | 启用指纹识别 | - | `-fp` |
| `-dns` | DNS日志记录 | - | `-dns` |
### 排除设置
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-eh` | 排除主机 | - | `-eh 192.168.1.1,192.168.1.2` |
### 语言与帮助
| 参数 | 说明 | 默认值 | 示例 |
|------|------|--------|------|
| `-lang` | 语言: zh, en | zh | `-lang en` |
| `-log` | 日志级别 | BASE_INFO_SUCCESS | `-log DEBUG` |
| `-help` | 显示帮助信息 | - | `-help` |
## 使用示例
### 基础扫描
```bash
# 扫描单个IP
go run main.go -h 192.168.1.1
# 扫描IP段
go run main.go -h 192.168.1.1/24
# 直接扫描指定主机和端口 (新功能)
go run main.go -h 127.0.0.1:135
# 从文件读取目标
go run main.go -hf targets.txt
# 仅存活探测
go run main.go -h 192.168.1.1/24 -ao
```
### 本地插件使用
```bash
# 系统痕迹清理
go run main.go -local cleaner
# 杀毒软件检测
go run main.go -local avdetect
# 键盘记录
go run main.go -local keylogger -keylog-output my_keylog.txt
# 文件下载
go run main.go -local downloader -download-url http://example.com/file.exe -download-path /tmp/
# Windows注册表持久化
go run main.go -local winregistry -win-pe target.exe
```
### 高级扫描
```bash
# 全量POC扫描
go run main.go -h 192.168.1.1/24 -full
# 使用HTTP代理
go run main.go -h 192.168.1.1 -proxy http://127.0.0.1:8080
# 使用SOCKS5代理进行服务扫描
go run main.go -h 192.168.1.1 -socks5 127.0.0.1:1080
# 直接扫描MySQL服务 (新功能 - 优化扫描流程)
go run main.go -h 192.168.1.1:3306 -socks5 127.0.0.1:1080
# 启动SOCKS5代理服务器
go run main.go -start-socks5 1080
# Web应用扫描
go run main.go -u http://127.0.0.1:8080
# 暴力破解
go run main.go -h 192.168.1.1 -user admin -pwd password
# 输出JSON格式
go run main.go -h 192.168.1.1 -f json -o result.json
```
---
## 版本更新说明 (v2.2.1)
### 新增功能
- **智能目标解析**: `-h`参数现在支持`host:port`格式,如`-h 127.0.0.1:135`
- **优化扫描流程**: 对于明确指定的host:port目标跳过常规端口扫描直接进行服务检测
- **统一结果显示**: 修复了端口扫描结果显示不一致的问题
### 改进项目
- 提升用户体验,支持更灵活的目标指定方式
- 优化扫描性能,减少不必要的网络请求
- 增强目标地址验证逻辑,支持更多输入格式
**总计参数数量**: 70个命令行参数 + 19个本地插件
**更新时间**: 2025-08-12
**版本**: v2.2.1

View File

@ -0,0 +1,165 @@
package adapters
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/plugins/base"
"time"
// 导入新插件以触发注册
_ "github.com/shadow1ng/fscan/plugins/services/mysql"
_ "github.com/shadow1ng/fscan/plugins/services/redis"
_ "github.com/shadow1ng/fscan/plugins/services/ssh"
)
// PluginAdapter 插件适配器,将新插件架构与旧系统集成
type PluginAdapter struct {
registry *base.PluginRegistry
}
// NewPluginAdapter 创建插件适配器
func NewPluginAdapter() *PluginAdapter {
return &PluginAdapter{
registry: base.GlobalPluginRegistry,
}
}
// AdaptPluginScan 适配插件扫描调用
// 将传统的插件扫描函数调用转换为新架构的插件调用
func (a *PluginAdapter) AdaptPluginScan(pluginName string, info *common.HostInfo) error {
// 创建插件实例
plugin, err := a.registry.Create(pluginName)
if err != nil {
// 如果新架构中没有该插件,返回错误让旧系统处理
return fmt.Errorf("plugin %s not found in new architecture: %v", pluginName, err)
}
// 初始化插件
if err := plugin.Initialize(); err != nil {
return fmt.Errorf("plugin %s initialization failed: %v", pluginName, err)
}
// 设置全局超时上下文
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(common.GlobalTimeout)*time.Second)
defer cancel()
// 执行扫描
result, err := plugin.Scan(ctx, info)
if err != nil {
common.LogError(fmt.Sprintf("Plugin %s scan failed: %v", pluginName, err))
return err
}
// 如果扫描成功,记录结果
if result != nil && result.Success {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 适配器层不输出扫描结果,由插件层负责输出
// 这避免了重复输出的问题
common.LogDebug(fmt.Sprintf("插件 %s 适配成功: %s", pluginName, target))
// 保存结果到文件
a.saveResult(info, result, pluginName)
// 如果有漏洞信息,也记录下来
for _, vuln := range result.Vulnerabilities {
common.LogError(fmt.Sprintf("%s vulnerability found: %s - %s",
pluginName, vuln.ID, vuln.Description))
}
}
return nil
}
// saveResult 保存扫描结果
func (a *PluginAdapter) saveResult(info *common.HostInfo, result *base.ScanResult, pluginName string) {
// 使用原有的结果保存机制
vulnResult := &output.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"plugin": pluginName,
"port": info.Ports,
"host": info.Host,
},
}
if len(result.Credentials) > 0 {
cred := result.Credentials[0]
if cred.Username != "" {
vulnResult.Details["username"] = cred.Username
vulnResult.Details["password"] = cred.Password
vulnResult.Details["credentials"] = fmt.Sprintf("%s:%s", cred.Username, cred.Password)
} else {
vulnResult.Details["password"] = cred.Password
vulnResult.Details["credentials"] = cred.Password
}
} else {
// 未授权访问
vulnResult.Details["type"] = "unauthorized"
}
// 保存结果
common.SaveResult(vulnResult)
}
// IsPluginSupported 检查插件是否在新架构中支持
func (a *PluginAdapter) IsPluginSupported(pluginName string) bool {
plugins := a.registry.GetAll()
for _, name := range plugins {
if name == pluginName {
return true
}
}
return false
}
// GetSupportedPlugins 获取新架构支持的插件列表
func (a *PluginAdapter) GetSupportedPlugins() []string {
return a.registry.GetAll()
}
// GetPluginMetadata 获取插件元数据
func (a *PluginAdapter) GetPluginMetadata(pluginName string) (*base.PluginMetadata, error) {
metadata := a.registry.GetMetadata(pluginName)
if metadata == nil {
return nil, fmt.Errorf("plugin %s not found", pluginName)
}
return metadata, nil
}
// =============================================================================
// 全局适配器实例
// =============================================================================
// GlobalAdapter 全局插件适配器实例
var GlobalAdapter = NewPluginAdapter()
// =============================================================================
// 便捷函数
// =============================================================================
// TryNewArchitecture 尝试使用新架构执行插件扫描
// 如果新架构支持该插件则使用新架构否则返回false让调用方使用旧插件
func TryNewArchitecture(pluginName string, info *common.HostInfo) bool {
if !GlobalAdapter.IsPluginSupported(pluginName) {
common.LogDebug(i18n.GetText("plugin_legacy_using", pluginName))
return false
}
common.LogDebug(i18n.GetText("plugin_new_arch_trying", pluginName))
err := GlobalAdapter.AdaptPluginScan(pluginName, info)
if err != nil {
common.LogError(i18n.GetText("plugin_new_arch_fallback", pluginName, err))
return false
}
common.LogDebug(i18n.GetText("plugin_new_arch_success", pluginName))
return true
}

View File

@ -264,4 +264,46 @@ func GenerateCredentials(usernames []string, passwords []string) []*Credential {
return credentials return credentials
} }
// 已移除未使用的 GeneratePasswordOnlyCredentials 方法 // GeneratePasswordOnlyCredentials 生成仅密码的凭据列表如Redis
func GeneratePasswordOnlyCredentials(passwords []string) []*Credential {
var credentials []*Credential
for _, password := range passwords {
credentials = append(credentials, &Credential{
Password: password,
Extra: make(map[string]string),
})
}
return credentials
}
// =============================================================================
// 结果处理工具
// =============================================================================
// SaveScanResult 保存扫描结果到通用输出系统
func SaveScanResult(info *common.HostInfo, result *ScanResult, pluginName string) {
if result == nil || !result.Success {
return
}
target := fmt.Sprintf("%s:%d", info.Host, info.Ports)
// 保存成功的凭据
for _, cred := range result.Credentials {
var message string
if cred.Username != "" && cred.Password != "" {
message = fmt.Sprintf("%s %s %s %s", pluginName, target, cred.Username, cred.Password)
} else if cred.Password != "" {
message = fmt.Sprintf("%s %s [密码] %s", pluginName, target, cred.Password)
} else {
message = fmt.Sprintf("%s %s 未授权访问", pluginName, target)
}
common.LogSuccess(message)
// 保存到输出系统的详细实现...
// 这里可以调用common.SaveResult等函数
}
}

View File

@ -1,6 +1,7 @@
package Plugins package Plugins
import ( import (
"bytes"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"encoding/base64" "encoding/base64"
@ -43,6 +44,32 @@ func ReadBytes(conn net.Conn) ([]byte, error) {
// 默认AES加密密钥 // 默认AES加密密钥
var key = "0123456789abcdef" var key = "0123456789abcdef"
// AesEncrypt 使用AES-CBC模式加密字符串
func AesEncrypt(orig string, key string) (string, error) {
// 转为字节数组
origData := []byte(orig)
keyBytes := []byte(key)
// 创建加密块,要求密钥长度必须为16/24/32字节
block, err := aes.NewCipher(keyBytes)
if err != nil {
return "", fmt.Errorf("创建加密块失败: %v", err)
}
// 获取块大小并填充数据
blockSize := block.BlockSize()
origData = PKCS7Padding(origData, blockSize)
// 创建CBC加密模式
blockMode := cipher.NewCBCEncrypter(block, keyBytes[:blockSize])
// 加密数据
encrypted := make([]byte, len(origData))
blockMode.CryptBlocks(encrypted, origData)
// base64编码
return base64.StdEncoding.EncodeToString(encrypted), nil
}
// AesDecrypt 使用AES-CBC模式解密字符串 // AesDecrypt 使用AES-CBC模式解密字符串
func AesDecrypt(crypted string, key string) (string, error) { func AesDecrypt(crypted string, key string) (string, error) {
@ -77,6 +104,12 @@ func AesDecrypt(crypted string, key string) (string, error) {
return string(origData), nil return string(origData), nil
} }
// PKCS7Padding 对数据进行PKCS7填充
func PKCS7Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padtext...)
}
// PKCS7UnPadding 去除PKCS7填充 // PKCS7UnPadding 去除PKCS7填充
func PKCS7UnPadding(data []byte) ([]byte, error) { func PKCS7UnPadding(data []byte) ([]byte, error) {

View File

@ -1,5 +1,3 @@
//go:build linux
package crontask package crontask
import ( import (

View File

@ -1,5 +1,3 @@
//go:build linux
package ldpreload package ldpreload
import ( import (

View File

@ -1,5 +1,3 @@
//go:build linux
package shellenv package shellenv
import ( import (

View File

@ -1,5 +1,3 @@
//go:build linux
package systemdservice package systemdservice
import ( import (

View File

@ -1,5 +1,3 @@
//go:build windows
package winregistry package winregistry
import ( import (

View File

@ -1,5 +1,3 @@
//go:build windows
package winschtask package winschtask
import ( import (

View File

@ -1,5 +1,3 @@
//go:build windows
package winservice package winservice
import ( import (

View File

@ -1,5 +1,3 @@
//go:build windows
package winstartup package winstartup
import ( import (

Some files were not shown because too many files have changed in this diff Show More