From adb3ac5b746d784425cb06382b6683a3013dff77 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Mon, 18 May 2026 16:19:34 +0800 Subject: [PATCH] add session-aware scan logging --- common/session.go | 45 +++++++++++++++++++++++++++++++--- common/session_test.go | 32 ++++++++++++++++++++++++ core/icmp.go | 14 +++-------- core/port_scan.go | 54 ++++++++++++++++++++--------------------- core/scanner.go | 27 ++++++++++++--------- plugins/services/ftp.go | 18 ++++++++------ 6 files changed, 131 insertions(+), 59 deletions(-) create mode 100644 common/session_test.go diff --git a/common/session.go b/common/session.go index 97193cb..3295514 100644 --- a/common/session.go +++ b/common/session.go @@ -48,18 +48,57 @@ func (s *ScanSession) SaveResult(result *output.ScanResult) error { return SaveResult(result) } +func (s *ScanSession) loggingEnabled() bool { + return s == nil || s.Config == nil || !s.Config.Output.Silent +} + +// LogDebug writes through the session's logging policy. +func (s *ScanSession) LogDebug(msg string) { + if s.loggingEnabled() { + LogDebug(msg) + } +} + +// LogInfo writes through the session's logging policy. +func (s *ScanSession) LogInfo(msg string) { + if s.loggingEnabled() { + LogInfo(msg) + } +} + +// LogSuccess writes through the session's logging policy. +func (s *ScanSession) LogSuccess(result string) { + if s.loggingEnabled() { + LogSuccess(result) + } +} + +// LogVuln writes through the session's logging policy. +func (s *ScanSession) LogVuln(result string) { + if s.loggingEnabled() { + LogVuln(result) + } +} + +// LogError writes through the session's logging policy. +func (s *ScanSession) LogError(errMsg string) { + if s.loggingEnabled() { + LogError(errMsg) + } +} + // DialTCP 创建 TCP 连接,内含限速检查、代理、计数 func (s *ScanSession) DialTCP(ctx context.Context, network, address string, timeout time.Duration) (net.Conn, error) { // 检查发包限制 if ok, err := CanSendPacketWith(s.Config, s.State); !ok { - LogError(fmt.Sprintf("TCP连接 %s 受限: %s", address, err.Error())) + s.LogError(fmt.Sprintf("TCP连接 %s 受限: %s", address, err.Error())) return nil, fmt.Errorf("%s", i18n.Tr("network_rate_limited", err.Error())) } // 获取 dialer dialer, err := s.getDialer() if err != nil { - LogError(fmt.Sprintf("获取代理拨号器失败: %v", err)) + s.LogError(fmt.Sprintf("获取代理拨号器失败: %v", err)) s.State.IncrementTCPFailedPacketCount() return nil, err } @@ -67,7 +106,7 @@ func (s *ScanSession) DialTCP(ctx context.Context, network, address string, time conn, err := dialer.DialContext(ctx, network, address) if err != nil { s.State.IncrementTCPFailedPacketCount() - LogDebug(fmt.Sprintf("连接 %s 失败: %v", address, err)) + s.LogDebug(fmt.Sprintf("连接 %s 失败: %v", address, err)) return nil, err } diff --git a/common/session_test.go b/common/session_test.go new file mode 100644 index 0000000..895ed56 --- /dev/null +++ b/common/session_test.go @@ -0,0 +1,32 @@ +package common + +import "testing" + +func TestScanSessionLogMethodsHonorSilentConfig(t *testing.T) { + loggerMu.Lock() + silentLoggerRefs = 0 + resetLoggerLocked() + loggerMu.Unlock() + t.Cleanup(func() { + loggerMu.Lock() + silentLoggerRefs = 0 + resetLoggerLocked() + loggerMu.Unlock() + }) + + cfg := NewConfig() + cfg.Output.Silent = true + session := NewScanSession(cfg, NewState(), &FlagVars{}) + + session.LogDebug("debug") + session.LogInfo("info") + session.LogSuccess("success") + session.LogVuln("vuln") + session.LogError("error") + + loggerMu.Lock() + defer loggerMu.Unlock() + if globalLogger != nil { + t.Fatal("silent session log methods initialized global logger") + } +} diff --git a/core/icmp.go b/core/icmp.go index a800fba..2ac1966 100644 --- a/core/icmp.go +++ b/core/icmp.go @@ -106,7 +106,7 @@ func tcpSupplementaryProbe(ctx context.Context, allHosts []string, aliveHosts [] } // 提示用户正在进行 TCP 补充探测 - common.LogInfo(i18n.Tr("tcp_probe_low_icmp_rate", fmt.Sprintf("%.1f%%", responseRate*100), len(unrespondedHosts))) + session.LogInfo(i18n.Tr("tcp_probe_low_icmp_rate", fmt.Sprintf("%.1f%%", responseRate*100), len(unrespondedHosts))) // 执行 TCP 补充探测 tcpAliveHosts := runTcpProbeForHosts(ctx, unrespondedHosts, session) @@ -114,7 +114,7 @@ func tcpSupplementaryProbe(ctx context.Context, allHosts []string, aliveHosts [] // 合并结果 if len(tcpAliveHosts) > 0 { aliveHosts = append(aliveHosts, tcpAliveHosts...) - common.LogInfo(i18n.Tr("tcp_probe_found", len(tcpAliveHosts))) + session.LogInfo(i18n.Tr("tcp_probe_found", len(tcpAliveHosts))) } return aliveHosts @@ -157,10 +157,7 @@ func handleAliveHosts(chanHosts chan string, hostslist []string, isPing bool, al } _ = session.SaveResult(result) - // 保留原有的控制台输出 - if !config.Output.Silent { - common.LogInfo(i18n.Tr("host_alive", ip, protocol)) - } + session.LogInfo(i18n.Tr("host_alive", ip, protocol)) } livewg.Done() } @@ -730,7 +727,6 @@ func tcpProbeAlive(ctx context.Context, session *common.ScanSession, host string // runTcpProbeForHosts 对指定主机列表进行 TCP 补充探测 // 返回存活的主机列表 func runTcpProbeForHosts(ctx context.Context, hosts []string, session *common.ScanSession) []string { - config := session.Config if len(hosts) == 0 { return nil } @@ -773,9 +769,7 @@ func runTcpProbeForHosts(ctx context.Context, hosts []string, session *common.Sc } _ = session.SaveResult(result) - if !config.Output.Silent { - common.LogInfo(i18n.Tr("host_alive", h, "TCP")) - } + session.LogInfo(i18n.Tr("host_alive", h, "TCP")) } }(host) } diff --git a/core/port_scan.go b/core/port_scan.go index 30f815f..a2ec510 100644 --- a/core/port_scan.go +++ b/core/port_scan.go @@ -143,13 +143,13 @@ func (f *failedPortCollector) Count() int { func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout int64, session *common.ScanSession, stream chan<- string) []string { config := session.Config state := session.State - common.LogDebug(fmt.Sprintf("[PortScan] 开始: %d个主机, 线程数=%d", len(hosts), config.ThreadNum)) + session.LogDebug(fmt.Sprintf("[PortScan] 开始: %d个主机, 线程数=%d", len(hosts), config.ThreadNum)) // 大规模扫描预筛:跨多个 /24 时先做网段探活,跳过空网段 if len(hosts) > subnetProbeThreshold { hosts = probeSubnets(ctx, hosts, time.Duration(timeout)*time.Second, session) if len(hosts) == 0 { - common.LogInfo(i18n.GetText("port_scan_no_alive_subnet")) + session.LogInfo(i18n.GetText("port_scan_no_alive_subnet")) if stream != nil { close(stream) } @@ -160,13 +160,13 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout // 解析端口和排除端口 portList := parsers.ParsePort(ports) if len(portList) == 0 { - common.LogError(i18n.Tr("invalid_port", ports)) + session.LogError(i18n.Tr("invalid_port", ports)) if stream != nil { close(stream) } return nil } - common.LogDebug(fmt.Sprintf("[PortScan] 端口解析完成: %d个端口", len(portList))) + session.LogDebug(fmt.Sprintf("[PortScan] 端口解析完成: %d个端口", len(portList))) // 使用config中的排除端口配置 excludePorts := parsers.ParsePort(config.Target.ExcludePorts) @@ -177,25 +177,25 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout // 检查代理可靠性,如果存在全回显问题则警告 if common.IsProxyEnabled() && !common.IsProxyReliable() { - common.LogError("检测到代理存在全回显问题,端口扫描结果可能不准确") + session.LogError("检测到代理存在全回显问题,端口扫描结果可能不准确") } // 创建流式迭代器(O(1) 内存,端口喷洒策略) iter := NewSocketIterator(hosts, portList, exclude) totalTasks := iter.Total() - common.LogDebug(fmt.Sprintf("[PortScan] 总任务数: %d", totalTasks)) + session.LogDebug(fmt.Sprintf("[PortScan] 总任务数: %d", totalTasks)) // 使用传入的配置 threadNum := config.ThreadNum // 大规模扫描警告和线程数自动调整 if totalTasks > 100000 { - common.LogInfo(fmt.Sprintf("大规模扫描: %d 个目标 (%d主机 × %d端口)", totalTasks, len(hosts), len(portList))) + session.LogInfo(fmt.Sprintf("大规模扫描: %d 个目标 (%d主机 × %d端口)", totalTasks, len(hosts), len(portList))) // 如果任务数超过100万且线程数大于300,自动降低线程数 if totalTasks > 1000000 && threadNum > 300 { oldThreadNum := threadNum threadNum = 300 - common.LogInfo(fmt.Sprintf("自动调整线程数: %d -> %d (大规模扫描优化)", oldThreadNum, threadNum)) + session.LogInfo(fmt.Sprintf("自动调整线程数: %d -> %d (大规模扫描优化)", oldThreadNum, threadNum)) } } @@ -204,7 +204,7 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout description := fmt.Sprintf("端口扫描中(%d线程)", threadNum) common.InitProgressBar(int64(totalTasks), description) } - common.LogDebug("[PortScan] 进度条初始化完成") + session.LogDebug("[PortScan] 进度条初始化完成") // 初始化并发控制 to := time.Duration(timeout) * time.Second @@ -214,7 +214,7 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout failedCollector := &failedPortCollector{} var wg sync.WaitGroup - common.LogDebug(fmt.Sprintf("[PortScan] 开始创建线程池, size=%d", threadNum)) + session.LogDebug(fmt.Sprintf("[PortScan] 开始创建线程池, size=%d", threadNum)) // 创建自适应线程池(支持动态调整) pool, err := NewAdaptivePool(threadNum, func(task interface{}) { taskInfo, ok := task.(portScanTask) @@ -230,19 +230,19 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout common.UpdateProgressBar(1) }, state) if err != nil { - common.LogError(i18n.Tr("thread_pool_create_failed", err)) + session.LogError(i18n.Tr("thread_pool_create_failed", err)) if stream != nil { close(stream) } return nil } - common.LogDebug("[PortScan] 线程池创建成功") + session.LogDebug("[PortScan] 线程池创建成功") defer pool.Release() - common.LogDebug("[PortScan] 开始滑动窗口调度") + session.LogDebug("[PortScan] 开始滑动窗口调度") // 滑动窗口调度:维护固定数量的"飞行中"任务 slidingWindowSchedule(iter, pool, &wg, threadNum) - common.LogDebug("[PortScan] 滑动窗口调度完成") + session.LogDebug("[PortScan] 滑动窗口调度完成") // 收集结果 aliveAddrs := collector.GetAll() @@ -257,7 +257,7 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout common.FinishProgressBar() } - common.LogInfo(i18n.Tr("port_scan_complete", count)) + session.LogInfo(i18n.Tr("port_scan_complete", count)) // 检查扫描失败率,如果过高则警告用户 resourceErrors := state.GetResourceExhaustedCount() @@ -268,18 +268,18 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout if failureRate > 20 { // 失败率超过20%,严重警告 - common.LogError(i18n.Tr("scan_failure_rate_high", fmt.Sprintf("%.1f%%", failureRate), failedCount, totalTasks)) - common.LogError(i18n.GetText("scan_failure_reason")) - common.LogError(i18n.Tr("scan_reduce_threads_suggestion", threadNum)) + session.LogError(i18n.Tr("scan_failure_rate_high", fmt.Sprintf("%.1f%%", failureRate), failedCount, totalTasks)) + session.LogError(i18n.GetText("scan_failure_reason")) + session.LogError(i18n.Tr("scan_reduce_threads_suggestion", threadNum)) } else if failureRate > 5 { // 失败率5-20%,一般警告 - common.LogInfo(i18n.Tr("scan_partial_failure", fmt.Sprintf("%.1f%%", failureRate), failedCount, totalTasks)) - common.LogInfo(i18n.Tr("scan_reduce_threads_accuracy", threadNum)) + session.LogInfo(i18n.Tr("scan_partial_failure", fmt.Sprintf("%.1f%%", failureRate), failedCount, totalTasks)) + session.LogInfo(i18n.Tr("scan_reduce_threads_accuracy", threadNum)) } } if resourceErrors > 0 { - common.LogError(i18n.Tr("resource_exhausted_warning", resourceErrors)) + session.LogError(i18n.Tr("resource_exhausted_warning", resourceErrors)) } return aliveAddrs @@ -467,7 +467,7 @@ func scanSinglePort(ctx context.Context, host string, port int, addr string, ada // 步骤1.5:代理连接深度验证(防止透明代理/全回显代理的假连接问题) valid, verifyMethod := verifyProxyConnectionDeep(conn, addr) if !valid { - common.LogDebug(fmt.Sprintf("代理验证失败 %s: %s", addr, verifyMethod)) + session.LogDebug(fmt.Sprintf("代理验证失败 %s: %s", addr, verifyMethod)) _ = conn.Close() return } @@ -655,7 +655,7 @@ func processServiceResult(host string, port int, addr string, serviceInfo *Servi if serviceInfo == nil { // 服务识别失败,尝试 HTTP 回退探测 if !tryHTTPFallbackDetection(host, port, addr, config, session) { - common.LogInfo(i18n.Tr("port_open", addr)) + session.LogInfo(i18n.Tr("port_open", addr)) } return } @@ -677,7 +677,7 @@ func processServiceResult(host string, port int, addr string, serviceInfo *Servi Details: details, }) - common.LogInfo(buildServiceLogMessage(addr, serviceInfo, isWeb)) + session.LogInfo(buildServiceLogMessage(addr, serviceInfo, isWeb)) } // buildServiceDetails 构建服务详情 map @@ -745,7 +745,7 @@ func tryHTTPFallbackDetection(host string, port int, addr string, config *common Details: details, }) - common.LogInfo(i18n.Tr("port_open_http", addr)) + session.LogInfo(i18n.Tr("port_open_http", addr)) return true } @@ -790,7 +790,7 @@ func probeSubnets(ctx context.Context, hosts []string, timeout time.Duration, se return hosts } - common.LogInfo(fmt.Sprintf("网段预筛: %d 个 /24 子网, %d 个主机", len(subnets), len(hosts))) + session.LogInfo(fmt.Sprintf("网段预筛: %d 个 /24 子网, %d 个主机", len(subnets), len(hosts))) aliveSubnets := sync.Map{} var wg sync.WaitGroup @@ -872,7 +872,7 @@ done: } skipped := len(subnets) - aliveCount - common.LogInfo(fmt.Sprintf("网段预筛完成: %d 个存活 (网关命中 %d), %d 个跳过, 剩余 %d 主机", + session.LogInfo(fmt.Sprintf("网段预筛完成: %d 个存活 (网关命中 %d), %d 个跳过, 剩余 %d 主机", aliveCount, gwHits, skipped, len(result))) return result } diff --git a/core/scanner.go b/core/scanner.go index 5ef4032..3fc5148 100644 --- a/core/scanner.go +++ b/core/scanner.go @@ -87,7 +87,7 @@ func RunScan(ctx context.Context, info common.HostInfo, session *common.ScanSess // 初始化HTTP客户端(静默,无需日志) if err := lib.Inithttp(config); err != nil { - common.LogError(i18n.Tr("http_client_init_failed", err)) + session.LogError(i18n.Tr("http_client_init_failed", err)) return } @@ -107,22 +107,22 @@ func RunScan(ctx context.Context, info common.HostInfo, session *common.ScanSess // 检查是否有活跃的连接需要维持 if state.IsReverseShellActive() || state.IsSocks5ProxyActive() || state.IsForwardShellActive() { if state.IsReverseShellActive() { - common.LogInfo(i18n.GetText("active_reverse_shell")) + session.LogInfo(i18n.GetText("active_reverse_shell")) } if state.IsSocks5ProxyActive() { - common.LogInfo(i18n.GetText("active_socks5_proxy")) + session.LogInfo(i18n.GetText("active_socks5_proxy")) } if state.IsForwardShellActive() { - common.LogInfo(i18n.GetText("active_forward_shell")) + session.LogInfo(i18n.GetText("active_forward_shell")) } - common.LogInfo(i18n.GetText("press_ctrl_c_exit")) + session.LogInfo(i18n.GetText("press_ctrl_c_exit")) // 优雅等待信号或 context 取消(Web Stop) sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) select { case <-sigChan: - common.LogInfo(i18n.GetText("received_exit_signal")) + session.LogInfo(i18n.GetText("received_exit_signal")) case <-ctx.Done(): } cancel() @@ -130,18 +130,21 @@ func RunScan(ctx context.Context, info common.HostInfo, session *common.ScanSess } // 完成扫描 - finishScan(config, state) + finishScan(session) } // finishScan 完成扫描并输出结果 -func finishScan(config *common.Config, state *common.State) { +func finishScan(session *common.ScanSession) { + config := session.Config + state := session.State + // 确保进度条正确完成 if common.IsProgressActive() { common.FinishProgressBar() } // 输出扫描完成信息 - common.LogInfo(i18n.Tr("scan_task_complete", time.Since(state.GetStartTime()).Round(time.Millisecond), state.GetNum())) + session.LogInfo(i18n.Tr("scan_task_complete", time.Since(state.GetStartTime()).Round(time.Millisecond), state.GetNum())) // 输出性能统计 JSON(如果启用) if config.Output.PerfStats { @@ -262,7 +265,7 @@ func executeScanTask(ctx context.Context, session *common.ScanSession, pluginNam defer func() { // 捕获并记录任何可能的panic if r := recover(); r != nil { - common.LogError(i18n.Tr("plugin_panic", pluginName, target.Host, target.Port, r)) + session.LogError(i18n.Tr("plugin_panic", pluginName, target.Host, target.Port, r)) } // 更新统计和进度(任务真正完成时才更新) @@ -284,10 +287,10 @@ func executeScanTask(ctx context.Context, session *common.ScanSession, pluginNam savePluginResult(session, &target, pluginName, result) } else if result.Type == plugins.ResultTypeCredential { // 凭据测试完成但未发现弱密码,在error级别输出提示 - common.LogError(i18n.Tr("brute_no_weak_pass", target.Host, target.Port, pluginName)) + session.LogError(i18n.Tr("brute_no_weak_pass", target.Host, target.Port, pluginName)) } else if result.Error != nil { // 其他类型的错误 - common.LogError(i18n.Tr("plugin_scan_error", target.Host, target.Port, result.Error)) + session.LogError(i18n.Tr("plugin_scan_error", target.Host, target.Port, result.Error)) } } } diff --git a/plugins/services/ftp.go b/plugins/services/ftp.go index 55deb79..0bce563 100644 --- a/plugins/services/ftp.go +++ b/plugins/services/ftp.go @@ -28,13 +28,13 @@ func (p *FTPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co config := session.Config state := session.State if config.DisableBrute { - return p.identifyService(info, config, state) + return p.identifyService(info, session) } target := info.Target() // 优先检测匿名访问 - if result := p.testAnonymousAccess(ctx, info, config, state); result != nil && result.Success { + if result := p.testAnonymousAccess(ctx, info, session); result != nil && result.Success { return result } @@ -63,7 +63,7 @@ func (p *FTPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co output.WriteString(fmt.Sprintf("\n [->] %s", file)) } } - common.LogVuln(output.String()) + session.LogVuln(output.String()) } return result @@ -144,7 +144,9 @@ func classifyFTPErrorType(err error) ErrorType { return ClassifyError(err, ftpAuthErrors, ftpNetworkErrors) } -func (p *FTPPlugin) identifyService(info *common.HostInfo, config *common.Config, state *common.State) *ScanResult { +func (p *FTPPlugin) identifyService(info *common.HostInfo, session *common.ScanSession) *ScanResult { + config := session.Config + state := session.State target := info.Target() conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.Timeout)) @@ -160,7 +162,7 @@ func (p *FTPPlugin) identifyService(info *common.HostInfo, config *common.Config defer func() { _ = conn.Quit() }() banner := "FTP" - common.LogSuccess(i18n.Tr("ftp_service", target, banner)) + session.LogSuccess(i18n.Tr("ftp_service", target, banner)) return &ScanResult{ Type: plugins.ResultTypeService, Success: true, @@ -170,7 +172,9 @@ func (p *FTPPlugin) identifyService(info *common.HostInfo, config *common.Config } // testAnonymousAccess 测试FTP匿名访问 -func (p *FTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult { +func (p *FTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { + config := session.Config + state := session.State target := info.Target() anonymousCreds := []Credential{ @@ -204,7 +208,7 @@ func (p *FTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostIn output.WriteString(fmt.Sprintf("\n [->] %s", file)) } } - common.LogVuln(output.String()) + session.LogVuln(output.String()) return &ScanResult{ Type: plugins.ResultTypeCredential,