mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00
Compare commits
29 Commits
c0374a6250
...
da981cdbce
Author | SHA1 | Date | |
---|---|---|---|
![]() |
da981cdbce | ||
![]() |
75a056809c | ||
![]() |
4473d1a28f | ||
![]() |
074aebfeec | ||
![]() |
1cb1e8b2aa | ||
![]() |
2703ddd9ed | ||
![]() |
cc4f55374a | ||
![]() |
0eb0a8f14a | ||
![]() |
245e3d0a12 | ||
![]() |
701306ee5f | ||
![]() |
defe5b0733 | ||
![]() |
792a075172 | ||
![]() |
c6bfb5f064 | ||
![]() |
b706fb46bb | ||
![]() |
a206e2c16e | ||
![]() |
b463984e78 | ||
![]() |
338dd60c3e | ||
![]() |
0cc29afbeb | ||
![]() |
7e4e3dbd1b | ||
![]() |
4a3f281b6b | ||
![]() |
cddbb9e7e4 | ||
![]() |
3e4cd2466e | ||
![]() |
f097d2812a | ||
![]() |
b89bf4b0da | ||
![]() |
7ca20cbebe | ||
![]() |
60eb65766a | ||
![]() |
3b6f2083de | ||
![]() |
285358772f | ||
![]() |
c85370a7d9 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -70,3 +70,8 @@ Todo列表.md
|
|||||||
|
|
||||||
# Claude documentation / Claude文档
|
# Claude documentation / Claude文档
|
||||||
.claude_docs/
|
.claude_docs/
|
||||||
|
|
||||||
|
# Cleaner plugin artifacts / 清理插件产物
|
||||||
|
cleanup.bat
|
||||||
|
cleanup.sh
|
||||||
|
cleanup_script_*
|
||||||
|
@ -99,28 +99,7 @@ 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 {
|
||||||
@ -135,16 +114,7 @@ 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 {
|
||||||
@ -164,28 +134,4 @@ 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
|
|
||||||
}
|
|
@ -20,6 +20,9 @@ var (
|
|||||||
HostsFile string
|
HostsFile string
|
||||||
PortsFile string
|
PortsFile string
|
||||||
|
|
||||||
|
// 本地插件列表(由外部初始化)
|
||||||
|
LocalPluginsList []string
|
||||||
|
|
||||||
ModuleThreadNum int
|
ModuleThreadNum int
|
||||||
GlobalTimeout int64
|
GlobalTimeout int64
|
||||||
EnableFingerprint bool
|
EnableFingerprint bool
|
||||||
@ -48,13 +51,11 @@ 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
|
||||||
@ -172,9 +173,8 @@ 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, "fingerprint", false, i18n.GetText("flag_enable_fingerprint"))
|
flag.BoolVar(&EnableFingerprint, "fp", false, i18n.GetText("flag_enable_fingerprint"))
|
||||||
flag.BoolVar(&LocalMode, "local", false, i18n.GetText("flag_local_mode"))
|
flag.StringVar(&LocalPlugin, "local", "", "指定本地插件名称 (如: cleaner, avdetect, keylogger 等)")
|
||||||
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,7 +216,6 @@ 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"))
|
||||||
@ -225,7 +224,6 @@ 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"))
|
||||||
|
|
||||||
// ═════════════════════════════════════════════════
|
// ═════════════════════════════════════════════════
|
||||||
@ -244,7 +242,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, "socks5-port", 0, i18n.GetText("flag_socks5_proxy"))
|
flag.IntVar(&Socks5ProxyPort, "start-socks5", 0, i18n.GetText("flag_start_socks5_server"))
|
||||||
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"))
|
||||||
@ -395,18 +393,14 @@ func checkParameterConflicts() {
|
|||||||
LogBase(i18n.GetText("param_conflict_ao_icmp_both"))
|
LogBase(i18n.GetText("param_conflict_ao_icmp_both"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查本地模式和本地插件参数
|
// 检查本地插件参数
|
||||||
if LocalMode {
|
if LocalPlugin != "" {
|
||||||
if LocalPlugin == "" {
|
// 自动启用本地模式
|
||||||
fmt.Printf("错误: 使用本地扫描模式 (-local) 时必须指定一个本地插件 (-localplugin)\n")
|
LocalMode = true
|
||||||
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 validPlugins {
|
for _, valid := range LocalPluginsList {
|
||||||
if LocalPlugin == valid {
|
if LocalPlugin == valid {
|
||||||
isValid = true
|
isValid = true
|
||||||
break
|
break
|
||||||
@ -415,14 +409,10 @@ func checkParameterConflicts() {
|
|||||||
|
|
||||||
if !isValid {
|
if !isValid {
|
||||||
fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin)
|
fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin)
|
||||||
fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy, forwardshell, ldpreload, shellenv, crontask, systemdservice, winregistry, winstartup, winschtask, winservice, winwmi, keylogger, downloader, cleaner\n")
|
if len(LocalPluginsList) > 0 {
|
||||||
|
fmt.Printf("可用的本地插件: %s\n", strings.Join(LocalPluginsList, ", "))
|
||||||
|
}
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果指定了本地插件但未启用本地模式
|
|
||||||
if !LocalMode && LocalPlugin != "" {
|
|
||||||
fmt.Printf("错误: 指定本地插件 (-localplugin) 时必须启用本地模式 (-local)\n")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -263,15 +263,19 @@ 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 {
|
||||||
|
@ -8,8 +8,12 @@ Ports.go - 端口常量(向后兼容层)
|
|||||||
此文件保持向后兼容,实际常量定义已迁移到Core/Constants.go
|
此文件保持向后兼容,实际常量定义已迁移到Core/Constants.go
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 向后兼容的端口常量 - 引用Core包中的定义
|
// 向后兼容的端口常量 - 引用base包中的定义
|
||||||
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 // 全部端口
|
||||||
)
|
)
|
||||||
|
@ -3,6 +3,7 @@ package common
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -31,11 +32,27 @@ 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 获取全局进度条管理器
|
||||||
@ -69,10 +86,16 @@ 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()
|
||||||
}
|
}
|
||||||
@ -91,6 +114,9 @@ func (pm *ProgressManager) UpdateProgress(increment int64) {
|
|||||||
pm.current = pm.total
|
pm.current = pm.total
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新活跃时间
|
||||||
|
pm.lastActivity = time.Now()
|
||||||
|
|
||||||
pm.renderProgress()
|
pm.renderProgress()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,6 +136,9 @@ func (pm *ProgressManager) FinishProgress() {
|
|||||||
pm.current = pm.total
|
pm.current = pm.total
|
||||||
pm.renderProgress()
|
pm.renderProgress()
|
||||||
|
|
||||||
|
// 停止活跃指示器
|
||||||
|
pm.stopActivityIndicator()
|
||||||
|
|
||||||
// 显示完成信息
|
// 显示完成信息
|
||||||
pm.showCompletionInfo()
|
pm.showCompletionInfo()
|
||||||
|
|
||||||
@ -140,7 +169,9 @@ func (pm *ProgressManager) renderProgress() {
|
|||||||
// generateProgressBar 生成进度条字符串
|
// generateProgressBar 生成进度条字符串
|
||||||
func (pm *ProgressManager) generateProgressBar() string {
|
func (pm *ProgressManager) generateProgressBar() string {
|
||||||
if pm.total == 0 {
|
if pm.total == 0 {
|
||||||
return fmt.Sprintf("%s: 等待中...", pm.description)
|
spinner := pm.getActivityIndicator()
|
||||||
|
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
|
||||||
@ -197,16 +228,22 @@ func (pm *ProgressManager) generateProgressBar() string {
|
|||||||
bar += "|"
|
bar += "|"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成活跃指示器
|
||||||
|
spinner := pm.getActivityIndicator()
|
||||||
|
|
||||||
// 构建基础进度条
|
// 构建基础进度条
|
||||||
baseProgress := fmt.Sprintf("%s %6.1f%% %s (%d/%d)%s%s",
|
baseProgress := fmt.Sprintf("%s %s %6.1f%% %s (%d/%d)%s%s",
|
||||||
pm.description, percentage, bar, pm.current, pm.total, speedStr, eta)
|
pm.description, spinner, percentage, bar, pm.current, pm.total, speedStr, eta)
|
||||||
|
|
||||||
|
// 添加内存信息
|
||||||
|
memInfo := pm.getMemoryInfo()
|
||||||
|
|
||||||
// 添加并发状态
|
// 添加并发状态
|
||||||
if concurrencyStatus != "" {
|
if concurrencyStatus != "" {
|
||||||
return fmt.Sprintf("%s [%s]", baseProgress, concurrencyStatus)
|
return fmt.Sprintf("%s [%s] %s", baseProgress, concurrencyStatus, memInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseProgress
|
return fmt.Sprintf("%s %s", baseProgress, memInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// showCompletionInfo 显示完成信息
|
// showCompletionInfo 显示完成信息
|
||||||
@ -326,4 +363,95 @@ 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)
|
||||||
}
|
}
|
@ -12,8 +12,12 @@ 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,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"
|
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package base
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -126,41 +125,7 @@ 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================================================================
|
// ========================================================================================
|
||||||
// 未使用的插件管理器方法已删除(死代码清理)
|
// 未使用的插件管理器方法已删除(死代码清理)
|
||||||
@ -172,89 +137,11 @@ func (pm *PluginManager) RegisterPlugin(plugin *ScanPlugin) error {
|
|||||||
// 全局插件管理函数 (保持向后兼容)
|
// 全局插件管理函数 (保持向后兼容)
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
// RegisterPlugin 注册插件到全局管理器
|
// 已移除未使用的 RegisterPlugin 方法
|
||||||
func RegisterPlugin(name string, plugin ScanPlugin) error {
|
|
||||||
// 转换为新的插件结构
|
|
||||||
newPlugin := &ScanPlugin{
|
|
||||||
Name: name,
|
|
||||||
Ports: plugin.Ports,
|
|
||||||
Types: plugin.Types,
|
|
||||||
Enabled: true,
|
|
||||||
ScanFunc: plugin.ScanFunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册到新的插件管理器
|
|
||||||
err := globalPluginManager.RegisterPlugin(newPlugin)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 同时更新Legacy管理器以保持向后兼容
|
|
||||||
LegacyPluginManager[name] = plugin
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear 清理所有插件(防止内存泄漏)
|
// 已移除未使用的 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 清理指定类型的插件
|
// 已移除未使用的 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 方法已删除(死代码清理)
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@ -29,12 +30,8 @@ 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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 全局插件管理器
|
// 全局插件管理器
|
||||||
@ -44,12 +41,7 @@ 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 函数已删除(死代码清理)
|
||||||
|
|
||||||
@ -63,22 +55,22 @@ var loggerMutex sync.Mutex
|
|||||||
func getGlobalLogger() *logging.Logger {
|
func getGlobalLogger() *logging.Logger {
|
||||||
loggerMutex.Lock()
|
loggerMutex.Lock()
|
||||||
defer loggerMutex.Unlock()
|
defer loggerMutex.Unlock()
|
||||||
|
|
||||||
if globalLogger == nil {
|
if globalLogger == nil {
|
||||||
level := getLogLevelFromString(LogLevel)
|
level := getLogLevelFromString(LogLevel)
|
||||||
config := &logging.LoggerConfig{
|
config := &logging.LoggerConfig{
|
||||||
Level: level,
|
Level: level,
|
||||||
EnableColor: !NoColor,
|
EnableColor: !NoColor,
|
||||||
SlowOutput: false,
|
SlowOutput: false,
|
||||||
ShowProgress: ShowProgress,
|
ShowProgress: ShowProgress,
|
||||||
StartTime: StartTime,
|
StartTime: StartTime,
|
||||||
}
|
}
|
||||||
globalLogger = logging.NewLogger(config)
|
globalLogger = logging.NewLogger(config)
|
||||||
if ProgressBar != nil {
|
if ProgressBar != nil {
|
||||||
globalLogger.SetProgressBar(ProgressBar)
|
globalLogger.SetProgressBar(ProgressBar)
|
||||||
}
|
}
|
||||||
globalLogger.SetOutputMutex(&OutputMutex)
|
globalLogger.SetOutputMutex(&OutputMutex)
|
||||||
|
|
||||||
// 设置协调输出函数,使用LogWithProgress
|
// 设置协调输出函数,使用LogWithProgress
|
||||||
globalLogger.SetCoordinatedOutput(LogWithProgress)
|
globalLogger.SetCoordinatedOutput(LogWithProgress)
|
||||||
}
|
}
|
||||||
@ -109,18 +101,18 @@ func getLogLevelFromString(levelStr string) logging.LogLevel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 日志函数
|
// 日志函数
|
||||||
func InitLogger() {
|
func InitLogger() {
|
||||||
loggerMutex.Lock()
|
loggerMutex.Lock()
|
||||||
globalLogger = nil
|
globalLogger = nil
|
||||||
loggerMutex.Unlock()
|
loggerMutex.Unlock()
|
||||||
getGlobalLogger().Initialize()
|
getGlobalLogger().Initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
func LogDebug(msg string) { getGlobalLogger().Debug(msg) }
|
func LogDebug(msg string) { getGlobalLogger().Debug(msg) }
|
||||||
func LogBase(msg string) { getGlobalLogger().Base(msg) }
|
func LogBase(msg string) { getGlobalLogger().Base(msg) }
|
||||||
func LogInfo(msg string) { getGlobalLogger().Info(msg) }
|
func LogInfo(msg string) { getGlobalLogger().Info(msg) }
|
||||||
func LogSuccess(result string) { getGlobalLogger().Success(result) }
|
func LogSuccess(result string) { getGlobalLogger().Success(result) }
|
||||||
func LogError(errMsg string) { getGlobalLogger().Error(errMsg) }
|
func LogError(errMsg string) { getGlobalLogger().Error(errMsg) }
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 输出系统简化接口
|
// 输出系统简化接口
|
||||||
@ -132,7 +124,7 @@ func InitOutput() error {
|
|||||||
if Outputfile == "" {
|
if Outputfile == "" {
|
||||||
return fmt.Errorf("output file not specified")
|
return fmt.Errorf("output file not specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
var format output.OutputFormat
|
var format output.OutputFormat
|
||||||
switch OutputFormat {
|
switch OutputFormat {
|
||||||
case "txt":
|
case "txt":
|
||||||
@ -177,14 +169,58 @@ 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
|
||||||
|
return d.DialContext(ctx, network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
LogDebug(fmt.Sprintf("使用SOCKS5代理连接: %s -> %s", Socks5Proxy, address))
|
||||||
|
return dialer.DialContext(ctx, network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有配置代理,使用直连
|
||||||
var d net.Dialer
|
var d net.Dialer
|
||||||
return d.DialContext(ctx, network, address)
|
return d.DialContext(ctx, network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WrapperTlsWithContext TLS连接包装器,带上下文
|
// 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)
|
||||||
}
|
}
|
||||||
@ -196,4 +232,4 @@ func WrapperTlsWithContext(ctx context.Context, network, address string, config
|
|||||||
// CheckErrs 检查单个错误 - 简化版本
|
// CheckErrs 检查单个错误 - 简化版本
|
||||||
func CheckErrs(err error) error {
|
func CheckErrs(err error) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,6 @@ 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写入的源文件
|
||||||
|
@ -20,7 +20,7 @@ globals.go - 全局变量定义
|
|||||||
// 版本信息
|
// 版本信息
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
var version = "2.2.0"
|
var version = "2.2.1"
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 简化的全局状态管理(仅保留必要的同步机制)
|
// 简化的全局状态管理(仅保留必要的同步机制)
|
||||||
|
58
Common/hostinfo_ext.go
Normal file
58
Common/hostinfo_ext.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
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
|
||||||
|
}
|
@ -170,10 +170,6 @@ 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",
|
||||||
@ -190,10 +186,6 @@ 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",
|
||||||
@ -235,6 +227,10 @@ 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)",
|
||||||
},
|
},
|
||||||
|
@ -108,11 +108,7 @@ func ParsePort(ports string) []int {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParsePortsFromString 从字符串解析端口列表
|
// 已移除未使用的 ParsePortsFromString 方法
|
||||||
// 保持与 ParsePortsFromString 的接口兼容性
|
|
||||||
func ParsePortsFromString(portsStr string) []int {
|
|
||||||
return ParsePort(portsStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 辅助函数
|
// 辅助函数
|
||||||
|
@ -30,7 +30,6 @@ 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 默认目标解析器选项
|
||||||
@ -43,7 +42,6 @@ func DefaultTargetParserOptions() *TargetParserOptions {
|
|||||||
ValidateURLs: DefaultValidateURLs,
|
ValidateURLs: DefaultValidateURLs,
|
||||||
ResolveDomains: DefaultResolveDomains,
|
ResolveDomains: DefaultResolveDomains,
|
||||||
EnableStatistics: DefaultTargetEnableStatistics,
|
EnableStatistics: DefaultTargetEnableStatistics,
|
||||||
DefaultPorts: DefaultPorts,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,17 +197,42 @@ 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 {
|
||||||
warnings = append(warnings, fmt.Sprintf("无效主机: %s - %s", host, err.Error()))
|
warnings = append(warnings, fmt.Sprintf("无效主机: %s - %s", host, err.Error()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将找到的hostPorts合并到输入结果中(通过修改input结构)
|
||||||
|
if len(hostPorts) > 0 {
|
||||||
|
input.HostPort = append(input.HostPort, hostPorts...)
|
||||||
|
}
|
||||||
|
|
||||||
// 检查目标数量限制
|
// 检查目标数量限制
|
||||||
if len(validHosts) > tp.options.MaxTargets {
|
if len(validHosts) > tp.options.MaxTargets {
|
||||||
@ -406,8 +429,26 @@ func (tp *TargetParser) parseHostList(hostStr string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
hosts = append(hosts, rangeHosts...)
|
hosts = append(hosts, rangeHosts...)
|
||||||
default:
|
default:
|
||||||
// 单个IP或域名
|
// 检查是否为host:port格式
|
||||||
hosts = append(hosts, item)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -697,6 +738,19 @@ 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)
|
||||||
|
@ -3,6 +3,8 @@ package parsers
|
|||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/common/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -134,12 +136,11 @@ 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/$.?#].[^\s]*$`
|
URLValidationRegexPattern = `^https?://[^\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]*)?)*$`
|
||||||
|
|
||||||
@ -192,25 +193,21 @@ 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": "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",
|
"web": base.WebPorts, // 使用实际的WebPorts常量
|
||||||
"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",
|
"main": base.MainPorts, // 使用实际的MainPorts常量
|
||||||
"database": "1433,1521,3306,5432,6379,11211,27017",
|
"db": base.DbPorts, // 使用实际的DbPorts常量
|
||||||
"common": "21,22,23,25,53,80,110,135,139,143,443,445,993,995,1723,3389,5060,5985,5986",
|
"service": base.ServicePorts, // 使用实际的ServicePorts常量
|
||||||
|
"common": base.CommonPorts, // 使用实际的CommonPorts常量
|
||||||
|
"all": base.AllPorts, // 使用实际的AllPorts常量
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTargetPortGroups 获取目标解析器端口组映射
|
// GetTargetPortGroups 获取目标解析器端口组映射(向后兼容,调用统一函数)
|
||||||
func GetTargetPortGroups() map[string]string {
|
func GetTargetPortGroups() map[string]string {
|
||||||
return map[string]string{
|
return GetPortGroups()
|
||||||
"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",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
62
Common/target.go
Normal file
62
Common/target.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
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
|
||||||
|
}
|
@ -1,8 +1,6 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,102 +23,19 @@ func NewMemoryMonitor(maxHeapMB uint64, maxGoroutines int, checkInterval time.Du
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start 启动内存监控
|
// 已移除未使用的 Start 方法
|
||||||
func (mm *MemoryMonitor) Start() {
|
|
||||||
if mm.running {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mm.running = true
|
|
||||||
go mm.monitor()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop 停止内存监控
|
// 已移除未使用的 Stop 方法
|
||||||
func (mm *MemoryMonitor) Stop() {
|
|
||||||
if !mm.running {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mm.running = false
|
|
||||||
select {
|
|
||||||
case mm.stopChan <- true:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// monitor 监控循环
|
// 已移除未使用的 monitor 方法
|
||||||
func (mm *MemoryMonitor) monitor() {
|
|
||||||
ticker := time.NewTicker(mm.checkInterval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
mm.checkMemory()
|
|
||||||
case <-mm.stopChan:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkMemory 检查内存使用情况
|
// 已移除未使用的 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 获取当前内存统计信息
|
// 已移除未使用的 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 强制执行垃圾回收
|
// 已移除未使用的 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 获取当前堆大小
|
// 已移除未使用的 getHeapSize 方法
|
||||||
func (mm *MemoryMonitor) getHeapSize() uint64 {
|
|
||||||
var m runtime.MemStats
|
|
||||||
runtime.ReadMemStats(&m)
|
|
||||||
return m.HeapInuse
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认内存监控器实例
|
// 默认内存监控器实例
|
||||||
var DefaultMemMonitor = NewMemoryMonitor(
|
var DefaultMemMonitor = NewMemoryMonitor(
|
||||||
@ -129,12 +44,6 @@ var DefaultMemMonitor = NewMemoryMonitor(
|
|||||||
30*time.Second, // 30秒检查一次
|
30*time.Second, // 30秒检查一次
|
||||||
)
|
)
|
||||||
|
|
||||||
// StartDefaultMonitor 启动默认内存监控器
|
// 已移除未使用的 StartDefaultMonitor 方法
|
||||||
func StartDefaultMonitor() {
|
|
||||||
DefaultMemMonitor.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopDefaultMonitor 停止默认内存监控器
|
// 已移除未使用的 StopDefaultMonitor 方法
|
||||||
func StopDefaultMonitor() {
|
|
||||||
DefaultMemMonitor.Stop()
|
|
||||||
}
|
|
@ -2,25 +2,7 @@ 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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,140 +0,0 @@
|
|||||||
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")
|
|
||||||
}
|
|
@ -1,145 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
262
PARAMETERS.md
Normal file
262
PARAMETERS.md
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
# 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
|
@ -1,165 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,111 +0,0 @@
|
|||||||
package vnc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mitchellh/go-vnc"
|
|
||||||
"github.com/shadow1ng/fscan/common"
|
|
||||||
"github.com/shadow1ng/fscan/common/i18n"
|
|
||||||
"github.com/shadow1ng/fscan/plugins/base"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VNCConnector VNC服务连接器
|
|
||||||
type VNCConnector struct{}
|
|
||||||
|
|
||||||
// NewVNCConnector 创建新的VNC连接器
|
|
||||||
func NewVNCConnector() *VNCConnector {
|
|
||||||
return &VNCConnector{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect 连接到VNC服务
|
|
||||||
func (c *VNCConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
|
|
||||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
|
||||||
timeout := time.Duration(common.Timeout) * time.Second
|
|
||||||
|
|
||||||
// 使用带上下文的TCP连接
|
|
||||||
conn, err := common.WrapperTcpWithTimeout("tcp", target, timeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(i18n.GetText("vnc_connection_failed"), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置读写超时
|
|
||||||
if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, fmt.Errorf("failed to set connection deadline: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authenticate 认证VNC服务
|
|
||||||
func (c *VNCConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
|
|
||||||
netConn, ok := conn.(net.Conn)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid connection type")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查上下文是否已取消
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// VNC只使用密码认证,忽略用户名
|
|
||||||
password := cred.Password
|
|
||||||
if password == "" && cred.Username != "" {
|
|
||||||
// 如果密码为空但用户名不为空,尝试使用用户名作为密码
|
|
||||||
password = cred.Username
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建完成通道
|
|
||||||
doneChan := make(chan error, 1)
|
|
||||||
|
|
||||||
// 在协程中处理VNC认证
|
|
||||||
go func() {
|
|
||||||
// 配置VNC客户端
|
|
||||||
config := &vnc.ClientConfig{
|
|
||||||
Auth: []vnc.ClientAuth{
|
|
||||||
&vnc.PasswordAuth{
|
|
||||||
Password: password,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试VNC认证
|
|
||||||
client, err := vnc.Client(netConn, config)
|
|
||||||
if err != nil {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
case doneChan <- err:
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 认证成功,立即关闭客户端
|
|
||||||
client.Close()
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
case doneChan <- nil:
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// 等待认证结果或上下文取消
|
|
||||||
select {
|
|
||||||
case err := <-doneChan:
|
|
||||||
return err
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close 关闭连接
|
|
||||||
func (c *VNCConnector) Close(conn interface{}) error {
|
|
||||||
if closer, ok := conn.(interface{ Close() error }); ok && closer != nil {
|
|
||||||
return closer.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package vnc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/shadow1ng/fscan/common"
|
|
||||||
"github.com/shadow1ng/fscan/plugins/base"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VNCExploiter VNC服务利用器
|
|
||||||
// 遵循新架构设计模式,当前为空实现
|
|
||||||
type VNCExploiter struct{}
|
|
||||||
|
|
||||||
// NewVNCExploiter 创建新的VNC利用器
|
|
||||||
func NewVNCExploiter() *VNCExploiter {
|
|
||||||
return &VNCExploiter{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exploit 执行VNC服务利用
|
|
||||||
// 当前为空实现,遵循其他插件的一致性设计
|
|
||||||
func (e *VNCExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
|
|
||||||
// 空实现 - 遵循新架构中其他服务插件的模式
|
|
||||||
// 主要功能集中在连接器和插件主体中实现
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
@ -1,187 +0,0 @@
|
|||||||
package vnc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/shadow1ng/fscan/common"
|
|
||||||
"github.com/shadow1ng/fscan/common/i18n"
|
|
||||||
"github.com/shadow1ng/fscan/plugins/base"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VNCPlugin VNC服务插件
|
|
||||||
type VNCPlugin struct {
|
|
||||||
*base.ServicePlugin
|
|
||||||
exploiter *VNCExploiter
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewVNCPlugin 创建VNC插件
|
|
||||||
func NewVNCPlugin() *VNCPlugin {
|
|
||||||
// 插件元数据
|
|
||||||
metadata := &base.PluginMetadata{
|
|
||||||
Name: "vnc",
|
|
||||||
Version: "2.0.0",
|
|
||||||
Author: "fscan-team",
|
|
||||||
Description: "VNC远程桌面协议服务检测和弱口令扫描",
|
|
||||||
Category: "service",
|
|
||||||
Ports: []int{5900, 5901, 5902, 5903}, // VNC常用端口
|
|
||||||
Protocols: []string{"tcp"},
|
|
||||||
Tags: []string{"vnc", "remote-desktop", "weak-password"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建连接器和服务插件
|
|
||||||
connector := NewVNCConnector()
|
|
||||||
servicePlugin := base.NewServicePlugin(metadata, connector)
|
|
||||||
|
|
||||||
// 创建VNC插件
|
|
||||||
plugin := &VNCPlugin{
|
|
||||||
ServicePlugin: servicePlugin,
|
|
||||||
exploiter: NewVNCExploiter(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置能力
|
|
||||||
plugin.SetCapabilities([]base.Capability{
|
|
||||||
base.CapWeakPassword,
|
|
||||||
})
|
|
||||||
|
|
||||||
return plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
// init 自动注册VNC插件
|
|
||||||
func init() {
|
|
||||||
// 创建插件工厂
|
|
||||||
metadata := &base.PluginMetadata{
|
|
||||||
Name: "vnc",
|
|
||||||
Version: "2.0.0",
|
|
||||||
Author: "fscan-team",
|
|
||||||
Description: "VNC远程桌面协议服务检测和弱口令扫描",
|
|
||||||
Category: "service",
|
|
||||||
Ports: []int{5900, 5901, 5902, 5903},
|
|
||||||
Protocols: []string{"tcp"},
|
|
||||||
Tags: []string{"vnc", "remote-desktop", "weak-password"},
|
|
||||||
}
|
|
||||||
|
|
||||||
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
|
|
||||||
return NewVNCPlugin()
|
|
||||||
})
|
|
||||||
|
|
||||||
base.GlobalPluginRegistry.Register("vnc", factory)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan 重写扫描方法,进行VNC服务扫描
|
|
||||||
func (p *VNCPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
|
|
||||||
// 如果禁用了暴力破解,只进行服务识别
|
|
||||||
if common.DisableBrute {
|
|
||||||
return p.performServiceIdentification(ctx, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
|
||||||
|
|
||||||
// 生成凭据进行暴力破解
|
|
||||||
credentials := p.generateCredentials()
|
|
||||||
if len(credentials) == 0 {
|
|
||||||
return &base.ScanResult{
|
|
||||||
Success: false,
|
|
||||||
Error: fmt.Errorf("no credentials available"),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 遍历凭据进行测试
|
|
||||||
for _, cred := range credentials {
|
|
||||||
result, err := p.ScanCredential(ctx, info, cred)
|
|
||||||
if err == nil && result.Success {
|
|
||||||
// 认证成功
|
|
||||||
common.LogSuccess(i18n.GetText("vnc_weak_password_success", target, cred.Password))
|
|
||||||
|
|
||||||
return &base.ScanResult{
|
|
||||||
Success: true,
|
|
||||||
Service: "VNC",
|
|
||||||
Credentials: []*base.Credential{cred},
|
|
||||||
Banner: result.Banner,
|
|
||||||
Extra: map[string]interface{}{
|
|
||||||
"service": "VNC",
|
|
||||||
"port": info.Ports,
|
|
||||||
"password": cred.Password,
|
|
||||||
"type": "weak-password",
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 没有找到有效凭据
|
|
||||||
return &base.ScanResult{
|
|
||||||
Success: false,
|
|
||||||
Service: "VNC",
|
|
||||||
Error: fmt.Errorf("authentication failed for all credentials"),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// performServiceIdentification 执行服务识别
|
|
||||||
func (p *VNCPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
|
|
||||||
// 尝试连接到服务进行基本识别
|
|
||||||
conn, err := p.GetServiceConnector().Connect(ctx, info)
|
|
||||||
if err != nil {
|
|
||||||
return &base.ScanResult{
|
|
||||||
Success: false,
|
|
||||||
Error: err,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
defer p.GetServiceConnector().Close(conn)
|
|
||||||
|
|
||||||
// 服务识别成功
|
|
||||||
return &base.ScanResult{
|
|
||||||
Success: true,
|
|
||||||
Service: "VNC",
|
|
||||||
Banner: "VNC service detected",
|
|
||||||
Extra: map[string]interface{}{
|
|
||||||
"service": "VNC",
|
|
||||||
"port": info.Ports,
|
|
||||||
"type": "service-identification",
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateCredentials 生成VNC认证凭据
|
|
||||||
func (p *VNCPlugin) generateCredentials() []*base.Credential {
|
|
||||||
var credentials []*base.Credential
|
|
||||||
|
|
||||||
// VNC只使用密码认证,不需要用户名
|
|
||||||
passwords := common.Passwords
|
|
||||||
if len(passwords) == 0 {
|
|
||||||
// 使用默认VNC密码
|
|
||||||
passwords = []string{"", "123456", "password", "vnc", "admin", "root", "888888", "123123"}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成密码凭据(VNC不使用用户名)
|
|
||||||
for _, password := range passwords {
|
|
||||||
credentials = append(credentials, &base.Credential{
|
|
||||||
Username: "", // VNC不需要用户名
|
|
||||||
Password: password,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 额外尝试常见的VNC密码组合
|
|
||||||
commonVNCPasswords := []string{
|
|
||||||
"vnc", "password", "123456", "admin", "root", "guest",
|
|
||||||
"1234", "12345", "qwerty", "abc123", "888888", "000000",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, password := range commonVNCPasswords {
|
|
||||||
// 避免重复添加
|
|
||||||
found := false
|
|
||||||
for _, existing := range credentials {
|
|
||||||
if existing.Password == password {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
credentials = append(credentials, &base.Credential{
|
|
||||||
Username: "",
|
|
||||||
Password: password,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return credentials
|
|
||||||
}
|
|
107
app/container.go
Normal file
107
app/container.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
"github.com/shadow1ng/fscan/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Container 依赖注入容器
|
||||||
|
type Container struct {
|
||||||
|
services map[string]interface{}
|
||||||
|
initializers []Initializer
|
||||||
|
mu sync.RWMutex
|
||||||
|
initialized bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContainer 创建新的容器
|
||||||
|
func NewContainer() *Container {
|
||||||
|
container := &Container{
|
||||||
|
services: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册默认初始化器
|
||||||
|
container.AddInitializer(&PluginInitializer{})
|
||||||
|
|
||||||
|
return container
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddInitializer 添加初始化器
|
||||||
|
func (c *Container) AddInitializer(init Initializer) {
|
||||||
|
c.initializers = append(c.initializers, init)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register 注册服务
|
||||||
|
func (c *Container) Register(name string, service interface{}) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.services[name] = service
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 获取服务
|
||||||
|
func (c *Container) Get(name string) (interface{}, bool) {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
service, exists := c.services[name]
|
||||||
|
return service, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize 初始化容器和所有服务
|
||||||
|
func (c *Container) Initialize() error {
|
||||||
|
if c.initialized {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行所有初始化器
|
||||||
|
for _, initializer := range c.initializers {
|
||||||
|
if err := initializer.Initialize(); err != nil {
|
||||||
|
return WrapError(ErrInitFailed, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.initialized = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunScan 执行扫描(包装现有的core.RunScan)
|
||||||
|
func (c *Container) RunScan(ctx context.Context, info common.HostInfo) error {
|
||||||
|
// 使用新的验证函数
|
||||||
|
if err := common.ValidateHostInfo(&info); err != nil {
|
||||||
|
return WrapError(ErrScanFailed, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建目标信息(展示新功能,但保持兼容)
|
||||||
|
target := common.NewTargetInfo(info)
|
||||||
|
target.WithContext(ctx)
|
||||||
|
target.SetMetadata("container_managed", true)
|
||||||
|
target.SetMetadata("validation_passed", true)
|
||||||
|
|
||||||
|
// 记录扫描信息
|
||||||
|
c.logScanInfo(target)
|
||||||
|
|
||||||
|
// 调用现有的扫描逻辑
|
||||||
|
core.RunScan(info)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// logScanInfo 记录扫描信息
|
||||||
|
func (c *Container) logScanInfo(target *common.TargetInfo) {
|
||||||
|
targetStr := target.String()
|
||||||
|
if targetStr != "" {
|
||||||
|
common.LogDebug(fmt.Sprintf("容器管理的扫描目标: %s", targetStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.HasMetadata("validation_passed") {
|
||||||
|
common.LogDebug("目标验证通过")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup 清理资源
|
||||||
|
func (c *Container) Cleanup() {
|
||||||
|
// 清理输出资源
|
||||||
|
common.CloseOutput()
|
||||||
|
}
|
43
app/errors.go
Normal file
43
app/errors.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// AppError 应用程序错误类型
|
||||||
|
type AppError struct {
|
||||||
|
Code int
|
||||||
|
Message string
|
||||||
|
Cause error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AppError) Error() string {
|
||||||
|
if e.Cause != nil {
|
||||||
|
return fmt.Sprintf("[%d] %s: %v", e.Code, e.Message, e.Cause)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("[%d] %s", e.Code, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预定义错误类型
|
||||||
|
var (
|
||||||
|
ErrInitFailed = &AppError{Code: 1, Message: "初始化失败"}
|
||||||
|
ErrParseFailed = &AppError{Code: 2, Message: "参数解析失败"}
|
||||||
|
ErrOutputFailed = &AppError{Code: 3, Message: "输出初始化失败"}
|
||||||
|
ErrScanFailed = &AppError{Code: 4, Message: "扫描执行失败"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewAppError 创建新的应用程序错误
|
||||||
|
func NewAppError(code int, message string, cause error) *AppError {
|
||||||
|
return &AppError{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
Cause: cause,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapError 包装错误为应用程序错误
|
||||||
|
func WrapError(baseErr *AppError, cause error) *AppError {
|
||||||
|
return &AppError{
|
||||||
|
Code: baseErr.Code,
|
||||||
|
Message: baseErr.Message,
|
||||||
|
Cause: cause,
|
||||||
|
}
|
||||||
|
}
|
66
app/initializer.go
Normal file
66
app/initializer.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
"github.com/shadow1ng/fscan/plugins/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initializer 初始化器接口
|
||||||
|
type Initializer interface {
|
||||||
|
Initialize() error
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// PluginInitializer 插件初始化器
|
||||||
|
type PluginInitializer struct{}
|
||||||
|
|
||||||
|
func (p *PluginInitializer) Name() string {
|
||||||
|
return "PluginInitializer"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PluginInitializer) Initialize() error {
|
||||||
|
var localPlugins []string
|
||||||
|
|
||||||
|
// 获取所有注册的插件
|
||||||
|
allPlugins := base.GlobalPluginRegistry.GetAll()
|
||||||
|
|
||||||
|
for _, pluginName := range allPlugins {
|
||||||
|
metadata := base.GlobalPluginRegistry.GetMetadata(pluginName)
|
||||||
|
if metadata != nil && metadata.Category == "local" {
|
||||||
|
localPlugins = append(localPlugins, pluginName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序以保持一致性
|
||||||
|
sort.Strings(localPlugins)
|
||||||
|
|
||||||
|
// 设置全局变量
|
||||||
|
common.LocalPluginsList = localPlugins
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerInitializer 日志初始化器
|
||||||
|
type LoggerInitializer struct{}
|
||||||
|
|
||||||
|
func (l *LoggerInitializer) Name() string {
|
||||||
|
return "LoggerInitializer"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LoggerInitializer) Initialize() error {
|
||||||
|
common.InitLogger()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutputInitializer 输出初始化器
|
||||||
|
type OutputInitializer struct{}
|
||||||
|
|
||||||
|
func (o *OutputInitializer) Name() string {
|
||||||
|
return "OutputInitializer"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OutputInitializer) Initialize() error {
|
||||||
|
return common.InitOutput()
|
||||||
|
}
|
@ -108,7 +108,8 @@ func (b *BaseScanStrategy) isPluginTypeMatchedByName(pluginName string) bool {
|
|||||||
case FilterLocal:
|
case FilterLocal:
|
||||||
return metadata.Category == "local"
|
return metadata.Category == "local"
|
||||||
case FilterService:
|
case FilterService:
|
||||||
return metadata.Category == "service"
|
// 服务扫描允许service类型,以及通过智能检测的web类型(在上层逻辑中处理)
|
||||||
|
return metadata.Category == "service" || metadata.Category == "web"
|
||||||
case FilterWeb:
|
case FilterWeb:
|
||||||
return metadata.Category == "web"
|
return metadata.Category == "web"
|
||||||
default:
|
default:
|
||||||
@ -122,6 +123,7 @@ 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)
|
||||||
@ -131,7 +133,7 @@ func (b *BaseScanStrategy) isPluginTypeMatched(plugin common.ScanPlugin) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsPluginApplicableByName 根据插件名称判断是否适用(新方法)
|
// IsPluginApplicableByName 根据插件名称判断是否适用(新方法)
|
||||||
func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetPort int, isCustomMode bool) bool {
|
func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetHost string, targetPort int, isCustomMode bool) bool {
|
||||||
// 自定义模式下运行所有明确指定的插件
|
// 自定义模式下运行所有明确指定的插件
|
||||||
if isCustomMode {
|
if isCustomMode {
|
||||||
return true
|
return true
|
||||||
@ -143,7 +145,7 @@ func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetPor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 智能Web插件检测:如果是Web插件且检测到Web服务,则包含Web插件
|
// 智能Web插件检测:如果是Web插件且检测到Web服务,则包含Web插件
|
||||||
if b.shouldIncludeWebPlugin(metadata, targetPort) {
|
if b.shouldIncludeWebPlugin(metadata, targetHost, targetPort) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,11 +164,21 @@ func (b *BaseScanStrategy) IsPluginApplicableByName(pluginName string, targetPor
|
|||||||
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, targetPort int) bool {
|
func (b *BaseScanStrategy) shouldIncludeWebPlugin(metadata *base.PluginMetadata, targetHost string, targetPort int) bool {
|
||||||
// 只对服务扫描策略启用Web插件智能检测
|
// 只对服务扫描策略启用Web插件智能检测
|
||||||
if b.filterType != FilterService {
|
if b.filterType != FilterService {
|
||||||
return false
|
return false
|
||||||
@ -187,14 +199,14 @@ func (b *BaseScanStrategy) shouldIncludeWebPlugin(metadata *base.PluginMetadata,
|
|||||||
globalWebDetector = NewWebPortDetector()
|
globalWebDetector = NewWebPortDetector()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检测是否为Web服务(这里暂时只检查常见端口,避免每次都进行HTTP探测)
|
// 检测是否为Web服务(使用完整的智能检测,包括HTTP协议探测)
|
||||||
return globalWebDetector.IsCommonWebPort(targetPort)
|
return globalWebDetector.IsWebService(targetHost, 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 {
|
||||||
@ -206,6 +218,21 @@ 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 {
|
||||||
// 无端口限制的插件适用于所有端口
|
// 无端口限制的插件适用于所有端口
|
78
core/PluginAdapter.go
Normal file
78
core/PluginAdapter.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
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 方法
|
||||||
|
|
||||||
|
// 已移除未使用的 GetPluginsByPort 方法
|
||||||
|
|
||||||
|
// 已移除未使用的 GetPluginsByType 方法
|
||||||
|
|
||||||
|
// 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 方法
|
||||||
|
|
||||||
|
// 已移除未使用的 GetServicePlugins 方法
|
||||||
|
|
||||||
|
// 已移除未使用的 GetWebPlugins 方法
|
||||||
|
|
||||||
|
// 已移除未使用的 GetLocalPlugins 方法
|
@ -60,6 +60,15 @@ 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)
|
||||||
@ -73,14 +82,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|
@ -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,20 +28,30 @@ 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/netbios"
|
_ "github.com/shadow1ng/fscan/plugins/legacy/elasticsearch" // 跨平台服务
|
||||||
_ "github.com/shadow1ng/fscan/plugins/legacy/ms17010"
|
_ "github.com/shadow1ng/fscan/plugins/legacy/findnet" // 网络发现
|
||||||
_ "github.com/shadow1ng/fscan/plugins/legacy/smb"
|
_ "github.com/shadow1ng/fscan/plugins/legacy/ms17010" // Windows SMB漏洞(但扫描器可跨平台)
|
||||||
_ "github.com/shadow1ng/fscan/plugins/legacy/smb2"
|
_ "github.com/shadow1ng/fscan/plugins/legacy/netbios" // NetBIOS协议(主要Windows但可跨平台扫描)
|
||||||
_ "github.com/shadow1ng/fscan/plugins/legacy/smbghost"
|
_ "github.com/shadow1ng/fscan/plugins/legacy/rdp" // RDP协议扫描(可跨平台)
|
||||||
_ "github.com/shadow1ng/fscan/plugins/legacy/rdp"
|
_ "github.com/shadow1ng/fscan/plugins/legacy/smb" // SMB协议扫描(可跨平台)
|
||||||
_ "github.com/shadow1ng/fscan/plugins/legacy/elasticsearch"
|
_ "github.com/shadow1ng/fscan/plugins/legacy/smb2" // SMBv2协议扫描(可跨平台)
|
||||||
_ "github.com/shadow1ng/fscan/plugins/legacy/findnet"
|
_ "github.com/shadow1ng/fscan/plugins/legacy/smbghost" // SMBGhost漏洞扫描(可跨平台)
|
||||||
|
|
||||||
// 导入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代理
|
||||||
)
|
)
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@ -73,49 +83,15 @@ 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() {
|
@ -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, targetPort int, isCustomMode bool) bool
|
IsPluginApplicableByName(pluginName string, targetHost 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, targetPort, isCustomMode) {
|
if strategy.IsPluginApplicableByName(pluginName, target.Host, 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, targetPort, isCustomMode) {
|
strategy.IsPluginApplicableByName(pluginName, target.Host, targetPort, isCustomMode) {
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,7 +187,4 @@ func executeScanTask(pluginName string, target common.HostInfo, ch *chan struct{
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Scan 入口函数,向后兼容旧的调用方式
|
// 已移除未使用的 Scan 方法
|
||||||
func Scan(info common.HostInfo) {
|
|
||||||
RunScan(info)
|
|
||||||
}
|
|
@ -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,6 +166,11 @@ 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
|
377
core/WebDetection.go
Normal file
377
core/WebDetection.go
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WebPortDetector Web端口检测器
|
||||||
|
type WebPortDetector struct {
|
||||||
|
// 常见Web端口列表
|
||||||
|
commonWebPorts map[int]bool
|
||||||
|
// HTTP检测超时时间
|
||||||
|
httpTimeout time.Duration
|
||||||
|
// HTTP客户端
|
||||||
|
httpClient *http.Client
|
||||||
|
// HTTPS客户端
|
||||||
|
httpsClient *http.Client
|
||||||
|
// 检测结果缓存(避免重复检测)
|
||||||
|
detectionCache map[string]bool
|
||||||
|
// 缓存互斥锁
|
||||||
|
cacheMutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWebPortDetector 创建Web端口检测器
|
||||||
|
func NewWebPortDetector() *WebPortDetector {
|
||||||
|
timeout := 3 * time.Second
|
||||||
|
|
||||||
|
// 定义常见Web端口(扩展列表)
|
||||||
|
commonPorts := map[int]bool{
|
||||||
|
// 标准Web端口
|
||||||
|
80: true, // HTTP
|
||||||
|
443: true, // HTTPS
|
||||||
|
8080: true, // HTTP alternate
|
||||||
|
8443: true, // HTTPS alternate
|
||||||
|
|
||||||
|
// 开发服务器端口
|
||||||
|
3000: true, // Node.js dev server
|
||||||
|
4000: true, // Ruby dev server
|
||||||
|
5000: true, // Python dev server
|
||||||
|
8000: true, // Development server
|
||||||
|
8888: true, // Common dev port
|
||||||
|
9000: true, // Common dev port
|
||||||
|
9090: true, // Common dev port
|
||||||
|
|
||||||
|
// Web服务器alternate端口
|
||||||
|
8081: true, 8082: true, 8083: true, 8084: true, 8085: true,
|
||||||
|
8086: true, 8087: true, 8088: true, 8089: true,
|
||||||
|
|
||||||
|
// 企业级Web端口
|
||||||
|
9080: true, // WebSphere
|
||||||
|
9443: true, // WebSphere SSL
|
||||||
|
7001: true, // WebLogic
|
||||||
|
7002: true, // WebLogic SSL
|
||||||
|
|
||||||
|
// 应用服务器端口
|
||||||
|
8180: true, // Tomcat alternate
|
||||||
|
8181: true, // Tomcat alternate
|
||||||
|
8282: true, // Common alternate
|
||||||
|
8383: true, // Common alternate
|
||||||
|
8484: true, // Common alternate
|
||||||
|
8585: true, // Common alternate
|
||||||
|
|
||||||
|
// 代理和负载均衡端口
|
||||||
|
3128: true, // Squid proxy
|
||||||
|
8008: true, // HTTP proxy
|
||||||
|
8118: true, // Privoxy
|
||||||
|
|
||||||
|
// 监控和管理界面
|
||||||
|
9200: true, // Elasticsearch
|
||||||
|
5601: true, // Kibana
|
||||||
|
3001: true, // Grafana alternate
|
||||||
|
9091: true, // Prometheus
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建HTTP客户端
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Timeout: timeout,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: timeout,
|
||||||
|
}).DialContext,
|
||||||
|
MaxIdleConns: 10,
|
||||||
|
IdleConnTimeout: 30 * time.Second,
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
MaxConnsPerHost: 5,
|
||||||
|
},
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse // 不跟随重定向,减少检测时间
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建HTTPS客户端(跳过证书验证)
|
||||||
|
httpsClient := &http.Client{
|
||||||
|
Timeout: timeout,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: timeout,
|
||||||
|
}).DialContext,
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
MaxIdleConns: 10,
|
||||||
|
IdleConnTimeout: 30 * time.Second,
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
MaxConnsPerHost: 5,
|
||||||
|
},
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &WebPortDetector{
|
||||||
|
commonWebPorts: commonPorts,
|
||||||
|
httpTimeout: timeout,
|
||||||
|
httpClient: httpClient,
|
||||||
|
httpsClient: httpsClient,
|
||||||
|
detectionCache: make(map[string]bool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWebService 智能检测端口是否运行Web服务
|
||||||
|
func (w *WebPortDetector) IsWebService(host string, port int) bool {
|
||||||
|
// 1. 快速路径:常见Web端口直接返回true
|
||||||
|
if w.IsCommonWebPort(port) {
|
||||||
|
common.LogDebug(fmt.Sprintf("端口 %d 是常见Web端口,启用Web插件", port))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 缓存检查:避免重复检测同一端口
|
||||||
|
cacheKey := fmt.Sprintf("%s:%d", host, port)
|
||||||
|
w.cacheMutex.RLock()
|
||||||
|
if cached, exists := w.detectionCache[cacheKey]; exists {
|
||||||
|
w.cacheMutex.RUnlock()
|
||||||
|
common.LogDebug(fmt.Sprintf("端口 %d 使用缓存结果: %v", port, cached))
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
w.cacheMutex.RUnlock()
|
||||||
|
|
||||||
|
// 3. 智能路径:对非常见端口进行HTTP协议探测
|
||||||
|
common.LogDebug(fmt.Sprintf("对端口 %d 进行智能Web检测", port))
|
||||||
|
result := w.detectHTTPService(host, port)
|
||||||
|
|
||||||
|
// 4. 缓存结果
|
||||||
|
w.cacheMutex.Lock()
|
||||||
|
w.detectionCache[cacheKey] = result
|
||||||
|
w.cacheMutex.Unlock()
|
||||||
|
|
||||||
|
common.LogDebug(fmt.Sprintf("端口 %d 智能检测结果: %v", port, result))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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和HTTPS
|
||||||
|
httpChan := make(chan bool, 1)
|
||||||
|
httpsChan := make(chan bool, 1)
|
||||||
|
|
||||||
|
// 检测HTTP
|
||||||
|
go func() {
|
||||||
|
httpChan <- w.tryHTTPConnection(ctx, host, port, "http")
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 检测HTTPS(对于高端口常见)
|
||||||
|
go func() {
|
||||||
|
httpsChan <- w.tryHTTPConnection(ctx, host, port, "https")
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 等待任一协议检测成功
|
||||||
|
select {
|
||||||
|
case result := <-httpChan:
|
||||||
|
if result {
|
||||||
|
common.LogDebug(fmt.Sprintf("端口 %d 检测到HTTP服务", port))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case result := <-httpsChan:
|
||||||
|
if result {
|
||||||
|
common.LogDebug(fmt.Sprintf("端口 %d 检测到HTTPS服务", port))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryHTTPConnection 尝试HTTP连接
|
||||||
|
func (w *WebPortDetector) tryHTTPConnection(ctx context.Context, host string, port int, protocol string) bool {
|
||||||
|
// 构造URL
|
||||||
|
var url string
|
||||||
|
if (port == 80 && protocol == "http") || (port == 443 && protocol == "https") {
|
||||||
|
url = fmt.Sprintf("%s://%s", protocol, host)
|
||||||
|
} else {
|
||||||
|
url = fmt.Sprintf("%s://%s:%d", protocol, host, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择客户端
|
||||||
|
var client *http.Client
|
||||||
|
if protocol == "https" {
|
||||||
|
client = w.httpsClient
|
||||||
|
} else {
|
||||||
|
client = w.httpClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送HEAD请求,最小化网络开销
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("User-Agent", "fscan-web-detector/2.2")
|
||||||
|
req.Header.Set("Accept", "*/*")
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
// 检查错误类型,某些错误也表明是HTTP服务
|
||||||
|
return w.analyzeHTTPError(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// 分析HTTP响应
|
||||||
|
return w.analyzeHTTPResponse(resp, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// analyzeHTTPError 分析HTTP错误,判断是否表明存在HTTP服务
|
||||||
|
func (w *WebPortDetector) analyzeHTTPError(err error) bool {
|
||||||
|
errStr := strings.ToLower(err.Error())
|
||||||
|
|
||||||
|
// 先检查明确的非Web服务错误
|
||||||
|
nonWebErrors := []string{
|
||||||
|
"connection refused",
|
||||||
|
"no such host",
|
||||||
|
"network unreachable",
|
||||||
|
"timeout",
|
||||||
|
"deadline exceeded",
|
||||||
|
"connection reset",
|
||||||
|
"eof",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, nonWebErr := range nonWebErrors {
|
||||||
|
if strings.Contains(errStr, nonWebErr) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这些错误表明连接到了HTTP服务器,只是协议或其他问题
|
||||||
|
webIndicators := []string{
|
||||||
|
"malformed http response",
|
||||||
|
"unexpected http response",
|
||||||
|
"ssl handshake",
|
||||||
|
"certificate",
|
||||||
|
"tls",
|
||||||
|
"bad request",
|
||||||
|
"method not allowed",
|
||||||
|
"server gave http response",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, indicator := range webIndicators {
|
||||||
|
if strings.Contains(errStr, indicator) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// analyzeHTTPResponse 分析HTTP响应,判断是否为Web服务
|
||||||
|
func (w *WebPortDetector) analyzeHTTPResponse(resp *http.Response, url string) bool {
|
||||||
|
// 任何HTTP状态码都表明这是Web服务
|
||||||
|
if resp.StatusCode > 0 {
|
||||||
|
// 分析响应头获取更多信息
|
||||||
|
serverHeader := resp.Header.Get("Server")
|
||||||
|
contentType := resp.Header.Get("Content-Type")
|
||||||
|
|
||||||
|
// 记录检测到的Web服务器信息
|
||||||
|
if serverHeader != "" {
|
||||||
|
common.LogDebug(fmt.Sprintf("检测到Web服务器: %s (Server: %s)", url, serverHeader))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 特殊处理:某些非Web服务也会返回HTTP响应
|
||||||
|
if w.isNonWebHTTPService(serverHeader, contentType) {
|
||||||
|
common.LogDebug(fmt.Sprintf("端口返回HTTP响应但可能不是Web服务: %s", url))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isNonWebHTTPService 判断是否为非Web的HTTP服务
|
||||||
|
func (w *WebPortDetector) isNonWebHTTPService(serverHeader, contentType string) bool {
|
||||||
|
// 某些服务使用HTTP协议但不是传统Web服务
|
||||||
|
nonWebServices := []string{
|
||||||
|
"docker",
|
||||||
|
"kubernetes",
|
||||||
|
"consul",
|
||||||
|
"etcd",
|
||||||
|
"vault",
|
||||||
|
"nomad",
|
||||||
|
}
|
||||||
|
|
||||||
|
serverLower := strings.ToLower(serverHeader)
|
||||||
|
for _, service := range nonWebServices {
|
||||||
|
if strings.Contains(serverLower, service) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWebPortRange 获取可能的Web端口范围(用于高级扫描)
|
||||||
|
func (w *WebPortDetector) GetWebPortRange() []int {
|
||||||
|
// 返回扩展的Web端口列表,用于目标发现
|
||||||
|
var ports []int
|
||||||
|
for port := range w.commonWebPorts {
|
||||||
|
ports = append(ports, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加一些常见的自定义端口范围
|
||||||
|
customRanges := []int{
|
||||||
|
8090, 8091, 8092, 8093, 8094, 8095, 8096, 8097, 8098, 8099,
|
||||||
|
9001, 9002, 9003, 9004, 9005, 9006, 9007, 9008, 9009,
|
||||||
|
10000, 10001, 10002, 10003, 10004, 10005,
|
||||||
|
}
|
||||||
|
|
||||||
|
ports = append(ports, customRanges...)
|
||||||
|
return ports
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWebPortByPattern 基于端口模式判断是否可能是Web端口
|
||||||
|
func (w *WebPortDetector) IsWebPortByPattern(port int) bool {
|
||||||
|
portStr := strconv.Itoa(port)
|
||||||
|
|
||||||
|
// Web端口的常见模式
|
||||||
|
webPatterns := []*regexp.Regexp{
|
||||||
|
regexp.MustCompile(`^80\d{2}$`), // 80xx
|
||||||
|
regexp.MustCompile(`^90\d{2}$`), // 90xx
|
||||||
|
regexp.MustCompile(`^300\d$`), // 300x
|
||||||
|
regexp.MustCompile(`^[3-9]000$`), // x000
|
||||||
|
regexp.MustCompile(`^1[0-9]000$`), // 1x000
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pattern := range webPatterns {
|
||||||
|
if pattern.MatchString(portStr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
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"
|
||||||
@ -54,16 +56,55 @@ 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
|
||||||
|
|
||||||
for _, url := range common.URLs {
|
// 首先从common.URLs获取目标
|
||||||
urlInfo := baseInfo
|
for _, urlStr := range common.URLs {
|
||||||
// 确保URL包含协议头
|
urlInfo := s.createTargetFromURL(baseInfo, urlStr)
|
||||||
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
|
if urlInfo != nil {
|
||||||
url = "http://" + url
|
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)
|
||||||
}
|
}
|
||||||
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
|
||||||
|
}
|
||||||
|
|
11
core/registry_linux.go
Normal file
11
core/registry_linux.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
// Linux持久化插件
|
||||||
|
_ "github.com/shadow1ng/fscan/plugins/local/ldpreload" // Linux LD_PRELOAD持久化
|
||||||
|
_ "github.com/shadow1ng/fscan/plugins/local/shellenv" // Linux Shell环境变量持久化
|
||||||
|
_ "github.com/shadow1ng/fscan/plugins/local/crontask" // Linux Cron计划任务持久化
|
||||||
|
_ "github.com/shadow1ng/fscan/plugins/local/systemdservice" // Linux Systemd服务持久化
|
||||||
|
)
|
17
core/registry_windows.go
Normal file
17
core/registry_windows.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
// Windows特有系统功能插件
|
||||||
|
_ "github.com/shadow1ng/fscan/plugins/local/avdetect" // Windows 杀毒软件检测
|
||||||
|
_ "github.com/shadow1ng/fscan/plugins/local/dcinfo" // Windows 域控信息收集
|
||||||
|
_ "github.com/shadow1ng/fscan/plugins/local/minidump" // Windows 内存转储
|
||||||
|
|
||||||
|
// Windows持久化插件
|
||||||
|
_ "github.com/shadow1ng/fscan/plugins/local/winregistry" // Windows 注册表持久化
|
||||||
|
_ "github.com/shadow1ng/fscan/plugins/local/winstartup" // Windows 启动文件夹持久化
|
||||||
|
_ "github.com/shadow1ng/fscan/plugins/local/winschtask" // Windows 计划任务持久化
|
||||||
|
_ "github.com/shadow1ng/fscan/plugins/local/winservice" // Windows 服务持久化
|
||||||
|
_ "github.com/shadow1ng/fscan/plugins/local/winwmi" // Windows WMI事件订阅持久化
|
||||||
|
)
|
97
main.go
97
main.go
@ -1,61 +1,62 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/app"
|
||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
"github.com/shadow1ng/fscan/core"
|
|
||||||
|
|
||||||
// 引入本地插件以触发注册
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/fileinfo" // 已重构,可用
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/dcinfo" // 已重构,可用
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/minidump" // 已重构,可用
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/reverseshell" // 已重构,可用
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/socks5proxy" // 已重构,可用
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/avdetect" // 已重构,可用
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/forwardshell" // 新增,可用
|
|
||||||
|
|
||||||
// Linux持久化插件
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/ldpreload" // Linux LD_PRELOAD持久化
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/shellenv" // Linux Shell环境变量持久化
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/crontask" // Linux Cron计划任务持久化
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/systemdservice" // Linux Systemd服务持久化
|
|
||||||
|
|
||||||
// Windows持久化插件
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/winregistry" // Windows 注册表持久化
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/winstartup" // Windows 启动文件夹持久化
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/winschtask" // Windows 计划任务持久化
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/winservice" // Windows 服务持久化
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/winwmi" // Windows WMI事件订阅持久化
|
|
||||||
|
|
||||||
// 监控插件
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/keylogger" // 跨平台键盘记录
|
|
||||||
|
|
||||||
// 实用工具插件
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/downloader" // 跨平台文件下载
|
|
||||||
_ "github.com/shadow1ng/fscan/plugins/local/cleaner" // 跨平台系统痕迹清理
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var Info common.HostInfo
|
// 创建应用容器
|
||||||
common.Flag(&Info)
|
container := app.NewContainer()
|
||||||
|
|
||||||
// 在flag解析后初始化logger,确保LogLevel参数生效
|
// 第一阶段:基础初始化(插件系统)
|
||||||
common.InitLogger()
|
if err := container.Initialize(); err != nil {
|
||||||
|
handleError("基础初始化失败", err)
|
||||||
// 解析 CLI 参数
|
|
||||||
if err := common.Parse(&Info); err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
defer container.Cleanup()
|
||||||
// 初始化输出系统,如果失败则直接退出
|
|
||||||
if err := common.InitOutput(); err != nil {
|
// 第二阶段:解析配置
|
||||||
common.LogError(fmt.Sprintf("初始化输出系统失败: %v", err))
|
var info common.HostInfo
|
||||||
os.Exit(1)
|
common.Flag(&info)
|
||||||
|
|
||||||
|
// 第三阶段:日志初始化(依赖于flag解析)
|
||||||
|
logInit := &app.LoggerInitializer{}
|
||||||
|
if err := logInit.Initialize(); err != nil {
|
||||||
|
handleError("日志初始化失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第四阶段:参数解析和验证
|
||||||
|
if err := common.Parse(&info); err != nil {
|
||||||
|
handleError("参数解析失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第五阶段:输出系统初始化
|
||||||
|
outputInit := &app.OutputInitializer{}
|
||||||
|
if err := outputInit.Initialize(); err != nil {
|
||||||
|
handleError("输出初始化失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第六阶段:执行扫描
|
||||||
|
ctx := context.Background()
|
||||||
|
if err := container.RunScan(ctx, info); err != nil {
|
||||||
|
handleError("扫描失败", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleError(msg string, err error) {
|
||||||
|
// 检查是否是应用程序错误
|
||||||
|
if appErr, ok := err.(*app.AppError); ok {
|
||||||
|
common.LogError(fmt.Sprintf("%s: %s", msg, appErr.Message))
|
||||||
|
if appErr.Cause != nil {
|
||||||
|
common.LogError(fmt.Sprintf("详细错误: %v", appErr.Cause))
|
||||||
|
}
|
||||||
|
os.Exit(appErr.Code)
|
||||||
|
} else {
|
||||||
|
common.LogError(fmt.Sprintf("%s: %v", msg, err))
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer common.CloseOutput()
|
|
||||||
|
|
||||||
// 执行 CLI 扫描逻辑
|
|
||||||
core.RunScan(Info)
|
|
||||||
}
|
}
|
||||||
|
7
plugins/adapters/plugin_adapter.go
Normal file
7
plugins/adapters/plugin_adapter.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Package adapters provides plugin compatibility layers.
|
||||||
|
// This package contains legacy adapter code that was part of a transition architecture.
|
||||||
|
// The adapter functions were not being used in the current codebase.
|
||||||
|
package adapters
|
||||||
|
|
||||||
|
// Legacy plugin adapter functionality has been removed as it was unused.
|
||||||
|
// This file is preserved for future compatibility needs if required.
|
@ -264,46 +264,4 @@ func GenerateCredentials(usernames []string, passwords []string) []*Credential {
|
|||||||
return credentials
|
return credentials
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeneratePasswordOnlyCredentials 生成仅密码的凭据列表(如Redis)
|
// 已移除未使用的 GeneratePasswordOnlyCredentials 方法
|
||||||
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等函数
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
package Plugins
|
package Plugins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
@ -44,32 +43,6 @@ 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) {
|
||||||
@ -104,12 +77,6 @@ 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) {
|
@ -1,3 +1,5 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
package crontask
|
package crontask
|
||||||
|
|
||||||
import (
|
import (
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user