diff --git a/.github/release-notes/v2.2.0-rc.1.md b/.github/release-notes/v2.2.0-rc.1.md new file mode 100644 index 0000000..222efe9 --- /dev/null +++ b/.github/release-notes/v2.2.0-rc.1.md @@ -0,0 +1,47 @@ +# fscan v2.2.0-rc.1 + +> ⚠️ **这是预发布版本 (Release Candidate)**,可能存在未发现的问题。 +> 如果你在使用中遇到任何异常,请积极通过 [Issue](https://github.com/shadow1ng/fscan/issues/new/choose) 反馈,帮助我们尽快稳定正式版。 +> 生产环境建议继续使用 [v2.1.3](https://github.com/shadow1ng/fscan/releases/tag/v2.1.3)。 + +--- + +## 与 v2.2.0-rc 的变更 + +本版本聚焦**实机测试发现的可靠性问题修复**,无新功能。 + +### 🐛 Bug 修复 + +- **UDP 插件在 `-p` 指定端口时被跳过** — 用户指定 `-p 53,161` 等包含 UDP 端口时,DNS/SNMP 等 UDP 插件不会执行。现在按用户指定的端口过滤并正确调度 +- **Redis exploit 操作无超时保护** — exploit 阶段移除了全部 deadline,服务端卡滞时 goroutine 永久阻塞。现在设置 30s 操作超时 +- **Redis readReply 吞没非超时错误** — 只要读到任何数据就忽略所有错误,可能返回截断响应。现在仅对 timeout 类型错误做容忍 +- **service_probe 连接丢失后静默成功** — Write/Read 在 Conn=nil 时返回 nil 而非错误,导致后续探测静默跳过。现在返回明确的 errConnLost +- **SNMP 探测成功但终端无输出** — SNMP 插件缺少 `session.LogVuln` 调用,成功结果只写入文件不在终端显示 + +### ⚡ 可靠性改善 + +- **协议级超时下限(ModuleTimeout)** — 新增 `Config.ModuleTimeout()` 方法,保证插件级交互超时不低于 3s。自适应系统将端口扫描超时压到 1s 时,SSH 握手/SNMP 探测/数据库认证等多轮交互协议不再受影响。全部 44 个服务插件已迁移 +- **SSH 爆破并发优化** — SSH 并发从 30 降至 3,避免触发 OpenSSH MaxStartups 限流导致大量连接被丢弃 +- **SSH 限流错误分类(ErrorTypeThrottle)** — 新增限流错误类型,区分服务端限流(MaxStartups)和真正的网络不可达。限流错误不计入连续失败计数,仅触发 500ms 退避后继续,避免误判目标不可达而提前放弃 +- **SSH 握手 TCP deadline** — 在 SSH NewClientConn 前设置 TCP 级别 deadline 兜底整个握手过程,握手成功后清除 +- **进度条竞态修复** — `ProgressManager.isActive` 从 `bool` 改为 `atomic.Bool`,消除 UpdateProgress 与 FinishProgress 之间的数据竞态 +- **gmtls stdout 竞态修复** — 移除 `suppressGMTLSStdout` 中对 `os.Stdout` 的非同步重定向,消除与 gmtls 内部 goroutine 的数据竞态 +- **Lint 清理** — 修复 cassandra/ipmi/mongodb/webscan 中的 ineffassign、unused、errcheck 问题 + +### 📊 实测验证 + +| 指标 | v2.2.0-rc | v2.2.0-rc.1 | +|------|-----------|-------------| +| SSH `-m ssh` 爆破成功率 | ~60% | 100% (10/10) | +| SNMP `-p 161` 终端输出 | ✗ 不显示 | ✓ 正常 | +| UDP 插件 `-p` 指定端口 | ✗ 跳过 | ✓ 正确调度 | +| Redis exploit 超时保护 | ✗ 无 | ✓ 30s | + +--- + +## 反馈与贡献 + +- 🐛 发现 Bug → [提交 Bug 报告](https://github.com/shadow1ng/fscan/issues/new?template=bug_report.yml) +- 🎯 结果不准 → [提交误报/漏报](https://github.com/shadow1ng/fscan/issues/new?template=false_positive.yml) +- ✨ 功能建议 → [提交功能请求](https://github.com/shadow1ng/fscan/issues/new?template=feature_request.yml) +- 💬 使用疑问 → [Discussions](https://github.com/shadow1ng/fscan/discussions) diff --git a/common/config_struct.go b/common/config_struct.go index 48fec0d..87d13ba 100644 --- a/common/config_struct.go +++ b/common/config_struct.go @@ -179,6 +179,17 @@ func clonePortMap(values map[int][]string) map[int][]string { return cloned } +const minModuleTimeout = 3 * time.Second + +// ModuleTimeout 返回插件级超时(用于弱口令测试、服务交互等多轮协议) +// 保证下限 3s,避免自适应把端口扫描超时压低后影响 SSH/SNMP 等交互型协议 +func (c *Config) ModuleTimeout() time.Duration { + if c.Timeout >= minModuleTimeout { + return c.Timeout + } + return minModuleTimeout +} + // NewConfig 创建带默认值的Config(后备用,正常流程使用BuildConfigFromFlags) func NewConfig() *Config { return &Config{ diff --git a/common/globals.go b/common/globals.go index 2500645..7a1626c 100644 --- a/common/globals.go +++ b/common/globals.go @@ -69,7 +69,7 @@ const ( // 版本信息,通过 ldflags 注入 var ( - version = "2.2.0-rc" + version = "2.2.0-rc.1" commit = "unknown" date = "unknown" ) diff --git a/common/progress_manager.go b/common/progress_manager.go index a69a47b..e79f7d7 100644 --- a/common/progress_manager.go +++ b/common/progress_manager.go @@ -32,7 +32,7 @@ type ProgressManager struct { current atomic.Int64 description string startTime time.Time - isActive bool + isActive atomic.Bool terminalHeight int reservedLines int // 为进度条保留的行数 lastContentLine int // 最后一行内容的位置 @@ -121,7 +121,7 @@ func (pm *ProgressManager) InitProgress(total int64, description string) { pm.current.Store(0) pm.description = description pm.startTime = time.Now() - pm.isActive = true + pm.isActive.Store(true) pm.enabled = true pm.lastActivity = time.Now() pm.spinnerIndex = 0 @@ -139,7 +139,7 @@ func (pm *ProgressManager) InitProgress(total int64, description string) { // UpdateProgress 更新进度 func (pm *ProgressManager) UpdateProgress(increment int64) { - if !pm.enabled || !pm.isActive { + if !pm.enabled || !pm.isActive.Load() { return } @@ -171,7 +171,7 @@ func (pm *ProgressManager) UpdateProgress(increment int64) { // FinishProgress 完成进度条 func (pm *ProgressManager) FinishProgress() { - if !pm.enabled || !pm.isActive { + if !pm.enabled || !pm.isActive.Load() { return } @@ -189,7 +189,7 @@ func (pm *ProgressManager) FinishProgress() { // 清理进度条区域,恢复正常输出 pm.clearProgressArea() - pm.isActive = false + pm.isActive.Store(false) } // setupProgressSpace 设置进度条空间 @@ -342,7 +342,7 @@ func (pm *ProgressManager) clearProgressArea() { func (pm *ProgressManager) IsActive() bool { pm.mu.RLock() defer pm.mu.RUnlock() - return pm.isActive && pm.enabled + return pm.isActive.Load() && pm.enabled } // getTerminalHeight 获取终端高度 @@ -479,7 +479,7 @@ func (pm *ProgressManager) GetPercent() float64 { pm.mu.RLock() defer pm.mu.RUnlock() - if !pm.isActive || pm.total.Load() == 0 { + if !pm.isActive.Load() || pm.total.Load() == 0 { return 0 } return float64(pm.current.Load()) / float64(pm.total.Load()) * 100 @@ -517,7 +517,7 @@ func LogWithProgress(message string) { // renderProgressUnsafe 不加锁的进度条渲染(内部使用) func (pm *ProgressManager) renderProgressUnsafe() { - if !pm.enabled || !pm.isActive { + if !pm.enabled || !pm.isActive.Load() { return } @@ -586,7 +586,7 @@ func (pm *ProgressManager) startActivityIndicator() { select { case <-pm.activityTicker.C: // 只有在活跃状态下才更新指示器 - if pm.isActive && pm.enabled { + if pm.isActive.Load() && pm.enabled { pm.mu.Lock() pm.spinnerIndex = (pm.spinnerIndex + 1) % len(spinnerChars) pm.mu.Unlock() diff --git a/core/service_probe.go b/core/service_probe.go index f201cc2..693bbe0 100644 --- a/core/service_probe.go +++ b/core/service_probe.go @@ -21,6 +21,8 @@ const ( defaultIntensity = 7 // 默认探测强度 (1-9) ) +var errConnLost = errors.New("connection lost and reconnect failed") + // sslSecondProbes SSL服务二次探测的探针名称 var sslSecondProbes = []string{"TerminalServerCookie", "TerminalServer"} @@ -527,7 +529,7 @@ var defaultReadTimeoutMS = WrTimeout * 1000 // Write 写入数据到连接 func (i *Info) Write(msg []byte) error { if i.Conn == nil { - return nil + return errConnLost } // 设置写入超时 @@ -570,7 +572,7 @@ func (i *Info) Write(msg []byte) error { // Read 从连接读取响应 func (i *Info) Read() ([]byte, error) { if i.Conn == nil { - return nil, nil + return nil, errConnLost } // 设置读取超时(使用动态超时) diff --git a/core/service_scanner.go b/core/service_scanner.go index 460dc2e..320f271 100644 --- a/core/service_scanner.go +++ b/core/service_scanner.go @@ -186,10 +186,8 @@ func (s *ServiceScanStrategy) performHostScan(ctx context.Context, session *comm ep.TuneConfig(config, session) } - // 仅在默认端口扫描时调度 UDP 插件(用户指定 -p 时跳过,避免不相关的 UDP 探测拖慢扫描) - if config.Target.Ports == "" || config.Target.Ports == "all" { - s.dispatchUDPPlugins(ctx, session, hosts, info, config, ch, wg) - } + // UDP 插件调度:默认端口模式全量调度,用户指定 -p 时只调度端口有交集的 UDP 插件 + s.dispatchUDPPlugins(ctx, session, hosts, info, config, ch, wg) s.scanHostBatch(ctx, session, hosts, info, pluginsToRun, isCustomMode, ch, wg) } @@ -271,9 +269,22 @@ func (s *ServiceScanStrategy) dispatchUDPPlugins(ctx context.Context, session *c return } + // 用户指定 -p 时,只调度端口有交集的 UDP 插件 + var userPorts map[int]bool + if config.Target.Ports != "" && config.Target.Ports != "all" { + parsed := parsers.ParsePort(config.Target.Ports) + userPorts = make(map[int]bool, len(parsed)) + for _, p := range parsed { + userPorts[p] = true + } + } + for _, host := range hosts { for _, pluginName := range udpPlugins { for _, port := range plugins.GetPluginPorts(pluginName) { + if userPorts != nil && !userPorts[port] { + continue + } target := baseInfo target.Host = host target.Port = port diff --git a/core/web_scanner.go b/core/web_scanner.go index e3b2236..24e372b 100644 --- a/core/web_scanner.go +++ b/core/web_scanner.go @@ -7,7 +7,6 @@ import ( "net" "net/http" "net/url" - "os" "strconv" "strings" "sync" @@ -58,17 +57,14 @@ func DetectHTTPSchemeContext(ctx context.Context, host string, port int, config } // 第二步:尝试国密TLS握手(GM TLS fallback) - // 抑制 gmtls 库的 fmt.Println("handshake error") 噪声输出 - gmConn, gmErr := suppressGMTLSStdout(func() (net.Conn, error) { - return gmtls.DialWithDialer( - tlsDialer, - "tcp", addr, - &gmtls.Config{ - GMSupport: gmtls.NewGMSupport(), - InsecureSkipVerify: true, - }, - ) - }) + gmConn, gmErr := gmtls.DialWithDialer( + tlsDialer, + "tcp", addr, + &gmtls.Config{ + GMSupport: gmtls.NewGMSupport(), + InsecureSkipVerify: true, + }, + ) if gmErr == nil { _ = gmConn.Close() @@ -503,22 +499,3 @@ func hasMalformedURLPort(host string) bool { return strings.Contains(host, ":") } -// suppressGMTLSStdout 抑制 gmtls 库硬编码的 fmt.Println("handshake error") 输出 -// gmtls/conn.go:1304 在握手失败时直接 Println 到 os.Stdout,无法通过 API 关闭 -var gmtlsStdoutMu sync.Mutex - -func suppressGMTLSStdout(fn func() (net.Conn, error)) (net.Conn, error) { - gmtlsStdoutMu.Lock() - orig := os.Stdout - devNull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0) - if err == nil { - os.Stdout = devNull - } - conn, dialErr := fn() - os.Stdout = orig - if devNull != nil { - _ = devNull.Close() - } - gmtlsStdoutMu.Unlock() - return conn, dialErr -} diff --git a/core/web_scanner_test.go b/core/web_scanner_test.go index 1fb4e7d..ec07d66 100644 --- a/core/web_scanner_test.go +++ b/core/web_scanner_test.go @@ -708,12 +708,8 @@ func TestIsWebServiceByFingerprint_Priority(t *testing.T) { // TestDetectHTTPScheme 测试HTTP/HTTPS协议智能检测 func TestDetectHTTPScheme(t *testing.T) { - // 设置WebTimeout避免测试超时 - cfg := common.GetGlobalConfig() - oldTimeout := cfg.Network.WebTimeout + cfg := common.NewConfig() cfg.Network.WebTimeout = 2 * time.Second - defer func() { cfg.Network.WebTimeout = oldTimeout }() - session := common.NewScanSession(cfg, common.NewState(), common.GetFlagVars()) t.Run("HTTPS服务器检测", func(t *testing.T) { diff --git a/plugins/services/activemq.go b/plugins/services/activemq.go index b1da128..572d58b 100644 --- a/plugins/services/activemq.go +++ b/plugins/services/activemq.go @@ -72,7 +72,7 @@ func (p *ActiveMQPlugin) createAuthFunc(info *common.HostInfo, session *common.S func (p *ActiveMQPlugin) doActiveMQAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult { target := info.Target() config := session.Config - timeout := config.Timeout + timeout := config.ModuleTimeout() resultChan := make(chan *AuthResult, 1) @@ -157,7 +157,7 @@ func classifyActiveMQErrorType(err error) ErrorType { // authenticateSTOMP 使用STOMP协议认证ActiveMQ func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password string, config *common.Config) (bool, error) { - timeout := config.Timeout + timeout := config.ModuleTimeout() if err := rejectLineBreaks(username, password); err != nil { return false, err } @@ -202,7 +202,7 @@ func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password str // identifyService ActiveMQ服务识别 func (p *ActiveMQPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { target := info.Target() - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() conn, err := session.DialTCP(ctx, "tcp", target, timeout) if err != nil { diff --git a/plugins/services/bacnet.go b/plugins/services/bacnet.go index 1b07456..6bd0913 100644 --- a/plugins/services/bacnet.go +++ b/plugins/services/bacnet.go @@ -22,7 +22,7 @@ func NewBACnetPlugin() *BACnetPlugin { } func (p *BACnetPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/cassandra.go b/plugins/services/cassandra.go index 545757c..2e8a14c 100644 --- a/plugins/services/cassandra.go +++ b/plugins/services/cassandra.go @@ -88,7 +88,7 @@ const ( func (p *CassandraPlugin) doCassandraAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { addr := info.Target() - timeout := config.Timeout + timeout := config.ModuleTimeout() dialer := net.Dialer{Timeout: timeout} conn, err := dialer.DialContext(ctx, "tcp", addr) @@ -270,7 +270,7 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common. state := session.State target := info.Target() addr := info.Target() - timeout := config.Timeout + timeout := config.ModuleTimeout() dialer := net.Dialer{Timeout: timeout} conn, err := dialer.DialContext(ctx, "tcp", addr) @@ -286,7 +286,7 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common. state.IncrementTCPFailedPacketCount() return nil } - opcode, body, err := cqlRecv(conn) + opcode, _, err := cqlRecv(conn) if err != nil || opcode != cqlOpReady { return nil } @@ -296,6 +296,7 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common. if err := cqlSend(conn, cqlOpQuery, queryBody); err != nil { return nil } + var body []byte opcode, body, err = cqlRecv(conn) if err != nil { return nil @@ -321,7 +322,7 @@ func (p *CassandraPlugin) identifyService(ctx context.Context, info *common.Host state := session.State target := info.Target() addr := info.Target() - timeout := config.Timeout + timeout := config.ModuleTimeout() dialer := net.Dialer{Timeout: timeout} conn, err := dialer.DialContext(ctx, "tcp", addr) diff --git a/plugins/services/credential_tester.go b/plugins/services/credential_tester.go index f377e67..27844a7 100644 --- a/plugins/services/credential_tester.go +++ b/plugins/services/credential_tester.go @@ -37,9 +37,10 @@ credential_tester.go - 统一凭据测试框架 type ErrorType int const ( - ErrorTypeAuth ErrorType = iota // 认证错误 - 密码错误,不重试 - ErrorTypeNetwork // 网络错误 - 连接问题,可重试 - ErrorTypeUnknown // 未知错误 + ErrorTypeAuth ErrorType = iota // 认证错误 - 密码错误,不重试 + ErrorTypeNetwork // 网络错误 - 连接不可达,可重试但计入连续失败 + ErrorTypeThrottle // 限流错误 - 服务端拒绝连接(MaxStartups等),退避后重试,不计入连续失败 + ErrorTypeUnknown // 未知错误 ) // ============================================================================= @@ -321,10 +322,13 @@ func workerTestCredentials( return } - // 跟踪连续网络错误 - if errType == ErrorTypeNetwork { + // 跟踪连续网络错误(限流错误不计入,只做短暂退避) + switch errType { + case ErrorTypeNetwork: consecutiveNetErrors++ - } else { + case ErrorTypeThrottle: + time.Sleep(500 * time.Millisecond) + default: consecutiveNetErrors = 0 } } @@ -374,8 +378,8 @@ func testCredentialWithRetry( case ErrorTypeAuth: // 认证错误(密码错误),不重试 return nil, result.ErrorType - case ErrorTypeNetwork, ErrorTypeUnknown: - // 网络错误或未知错误,可以重试(可能是服务端限流等临时问题) + case ErrorTypeNetwork, ErrorTypeThrottle, ErrorTypeUnknown: + // 网络/限流/未知错误,可以重试 if attempt < testConfig.MaxRetries-1 { timer := time.NewTimer(testConfig.RetryDelay) select { diff --git a/plugins/services/dns.go b/plugins/services/dns.go index 61c8373..f80fed8 100644 --- a/plugins/services/dns.go +++ b/plugins/services/dns.go @@ -19,7 +19,7 @@ func NewDNSPlugin() *DNSPlugin { } func (p *DNSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/dnstcp.go b/plugins/services/dnstcp.go index db5a963..8d24bac 100644 --- a/plugins/services/dnstcp.go +++ b/plugins/services/dnstcp.go @@ -21,7 +21,7 @@ func NewDNSTCPPlugin() *DNSTCPPlugin { } func (p *DNSTCPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/elasticsearch.go b/plugins/services/elasticsearch.go index 86f71cb..500174d 100644 --- a/plugins/services/elasticsearch.go +++ b/plugins/services/elasticsearch.go @@ -76,7 +76,7 @@ func (p *ElasticsearchPlugin) Scan(ctx context.Context, info *common.HostInfo, s func (p *ElasticsearchPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) bool { config := session.Config client := &http.Client{ - Timeout: config.Timeout, + Timeout: config.ModuleTimeout(), Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, diff --git a/plugins/services/findnet.go b/plugins/services/findnet.go index ae2fe7c..40de88c 100644 --- a/plugins/services/findnet.go +++ b/plugins/services/findnet.go @@ -50,7 +50,7 @@ func (p *FindNetPlugin) Scan(ctx context.Context, info *common.HostInfo, session } } - conn, err := session.DialTCP(ctx, "tcp", target, config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, config.ModuleTimeout()) if err != nil { return &ScanResult{ Success: false, @@ -61,7 +61,7 @@ func (p *FindNetPlugin) Scan(ctx context.Context, info *common.HostInfo, session defer func() { _ = conn.Close() }() // 设置超时 - _ = conn.SetDeadline(time.Now().Add(config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(config.ModuleTimeout())) // 执行RPC网络发现 networkInfo, err := p.performNetworkDiscovery(conn) diff --git a/plugins/services/ftp.go b/plugins/services/ftp.go index 0b85fec..53b1f9d 100644 --- a/plugins/services/ftp.go +++ b/plugins/services/ftp.go @@ -81,7 +81,7 @@ func (p *FTPPlugin) createAuthFunc(info *common.HostInfo, config *common.Config, func (p *FTPPlugin) doFTPAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { target := info.Target() - conn, err := ftplib.Dial(target, ftpDialOptions(ctx, config.Timeout)...) + conn, err := ftplib.Dial(target, ftpDialOptions(ctx, config.ModuleTimeout())...) if err != nil { state.IncrementTCPFailedPacketCount() return &AuthResult{ @@ -162,7 +162,7 @@ func (p *FTPPlugin) identifyService(info *common.HostInfo, session *common.ScanS state := session.State target := info.Target() - conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.Timeout)) + conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.ModuleTimeout())) if err != nil { state.IncrementTCPFailedPacketCount() return &ScanResult{ @@ -241,7 +241,7 @@ func (p *FTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostIn func (p *FTPPlugin) getFileListAfterAuth(info *common.HostInfo, username, password string, config *common.Config, state *common.State) []string { target := info.Target() - conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.Timeout)) + conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.ModuleTimeout())) if err != nil { return nil } diff --git a/plugins/services/imap.go b/plugins/services/imap.go index 758ff99..a028144 100644 --- a/plugins/services/imap.go +++ b/plugins/services/imap.go @@ -22,7 +22,7 @@ func NewIMAPPlugin() *IMAPPlugin { func (p *IMAPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { config := session.Config - timeout := config.Timeout + timeout := config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/ipmi.go b/plugins/services/ipmi.go index 8b1b5a0..c07109d 100644 --- a/plugins/services/ipmi.go +++ b/plugins/services/ipmi.go @@ -20,7 +20,7 @@ func NewIPMIPlugin() *IPMIPlugin { } func (p *IPMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } @@ -60,8 +60,6 @@ func (p *IPMIPlugin) rmcpPing(ctx context.Context, target string, timeout time.D } } - // getChannelAuth 需要独立连接,暂不执行(核心检测已完成) - return &ScanResult{ Success: true, Type: plugins.ResultTypeVuln, @@ -71,68 +69,6 @@ func (p *IPMIPlugin) rmcpPing(ctx context.Context, target string, timeout time.D } } -func (p *IPMIPlugin) getChannelAuth(conn interface { - Read([]byte) (int, error) - Write([]byte) (int, error) - SetDeadline(time.Time) error -}) string { - _ = conn.SetDeadline(time.Now().Add(2 * time.Second)) - - // IPMI Get Channel Authentication Capabilities - // RMCP header + IPMI session wrapper + message - pkt := []byte{ - 0x06, 0x00, 0xff, 0x07, // RMCP: version, reserved, seq=0xff, class=IPMI - 0x00, 0x00, 0x00, 0x00, // auth type = none - 0x00, 0x00, 0x00, 0x00, // session seq - 0x00, 0x00, 0x00, 0x00, // session id - 0x09, // message length - 0x20, // target = BMC - 0x18, // netFn=App(6) << 2 | lun=0 - 0xc8, // checksum - 0x81, // source - 0x00, // seq - 0x38, // cmd = Get Channel Auth Capabilities - 0x8e, // channel=14 (current), IPMI v2.0 - 0x04, // privilege = Administrator - 0xb5, // checksum - } - - if _, err := conn.Write(pkt); err != nil { - return "" - } - - buf := make([]byte, 512) - n, err := conn.Read(buf) - if err != nil || n < 30 { - return "" - } - - // Parse auth capabilities from response - if n >= 27 { - authTypes := buf[22] - var methods []string - if authTypes&0x01 != 0 { - methods = append(methods, "none") - } - if authTypes&0x02 != 0 { - methods = append(methods, "md2") - } - if authTypes&0x04 != 0 { - methods = append(methods, "md5") - } - if authTypes&0x10 != 0 { - methods = append(methods, "password") - } - if authTypes&0x20 != 0 { - methods = append(methods, "oem") - } - if len(methods) > 0 { - return fmt.Sprintf("[auth: %v]", methods) - } - } - return "" -} - func init() { RegisterUDPPluginWithPorts("ipmi", func() Plugin { return NewIPMIPlugin() diff --git a/plugins/services/jdwp.go b/plugins/services/jdwp.go index 4d44e47..f625f20 100644 --- a/plugins/services/jdwp.go +++ b/plugins/services/jdwp.go @@ -23,7 +23,7 @@ func NewJDWPPlugin() *JDWPPlugin { } func (p *JDWPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/kafka.go b/plugins/services/kafka.go index 85719be..1e5af2d 100644 --- a/plugins/services/kafka.go +++ b/plugins/services/kafka.go @@ -67,7 +67,7 @@ func (p *KafkaPlugin) createAuthFunc(info *common.HostInfo, config *common.Confi func (p *KafkaPlugin) doKafkaAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { target := info.Target() - timeout := config.Timeout + timeout := config.ModuleTimeout() dialer := net.Dialer{Timeout: timeout} conn, err := dialer.DialContext(ctx, "tcp", target) @@ -242,7 +242,7 @@ func (p *KafkaPlugin) identifyService(ctx context.Context, info *common.HostInfo config := session.Config state := session.State target := info.Target() - timeout := config.Timeout + timeout := config.ModuleTimeout() dialer := net.Dialer{Timeout: timeout} conn, err := dialer.DialContext(ctx, "tcp", target) diff --git a/plugins/services/ldap.go b/plugins/services/ldap.go index 8bbb264..1d62ef2 100644 --- a/plugins/services/ldap.go +++ b/plugins/services/ldap.go @@ -210,7 +210,7 @@ func (p *LDAPPlugin) connectLDAP(ctx context.Context, info *common.HostInfo, ses resultChan := make(chan result, 1) go func() { - tcpConn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + tcpConn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- result{nil, err} return @@ -222,7 +222,7 @@ func (p *LDAPPlugin) connectLDAP(ctx context.Context, info *common.HostInfo, ses } else { conn = ldaplib.NewConn(tcpConn, false) } - conn.SetTimeout(session.Config.Timeout) + conn.SetTimeout(session.Config.ModuleTimeout()) conn.Start() resultChan <- result{conn, nil} diff --git a/plugins/services/memcached.go b/plugins/services/memcached.go index e91b4ee..fd5874d 100644 --- a/plugins/services/memcached.go +++ b/plugins/services/memcached.go @@ -68,7 +68,7 @@ func (p *MemcachedPlugin) testUnauthorizedAccess(ctx context.Context, info *comm func (p *MemcachedPlugin) connectToMemcached(ctx context.Context, info *common.HostInfo, session *common.ScanSession) net.Conn { target := info.Target() - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() connChan := make(chan net.Conn, 1) @@ -97,12 +97,12 @@ func (p *MemcachedPlugin) connectToMemcached(ctx context.Context, info *common.H } func (p *MemcachedPlugin) testBasicCommand(conn net.Conn, config *common.Config) bool { - _ = conn.SetWriteDeadline(time.Now().Add(config.Timeout)) + _ = conn.SetWriteDeadline(time.Now().Add(config.ModuleTimeout())) if _, err := conn.Write([]byte("version\r\n")); err != nil { return false } - _ = conn.SetReadDeadline(time.Now().Add(config.Timeout)) + _ = conn.SetReadDeadline(time.Now().Add(config.ModuleTimeout())) response := make([]byte, 1024) n, err := conn.Read(response) if err != nil { diff --git a/plugins/services/modbus.go b/plugins/services/modbus.go index 38e6891..0e27fe6 100644 --- a/plugins/services/modbus.go +++ b/plugins/services/modbus.go @@ -21,7 +21,7 @@ func NewModbusPlugin() *ModbusPlugin { } func (p *ModbusPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/mongodb.go b/plugins/services/mongodb.go index afc5456..f61fedc 100644 --- a/plugins/services/mongodb.go +++ b/plugins/services/mongodb.go @@ -91,7 +91,7 @@ func (p *MongoDBPlugin) createAuthFunc(info *common.HostInfo, config *common.Con func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { addr := info.Target() - timeout := config.Timeout + timeout := config.ModuleTimeout() conn, err := dialTCP(ctx, addr, timeout) if err != nil { @@ -369,6 +369,7 @@ type mongoCommandReply struct { errmsg string } +//nolint:gocyclo func parseMongoCommandReply(doc []byte) (mongoCommandReply, error) { var reply mongoCommandReply if len(doc) < 5 { @@ -557,11 +558,6 @@ func dialTCP(ctx context.Context, addr string, timeout time.Duration) (net.Conn, return dialer.DialContext(ctx, "tcp", addr) } -// base64EncodeStr Base64 编码(标准编码) -func base64EncodeStr(s string) string { - return base64.StdEncoding.EncodeToString([]byte(s)) -} - // randomString 生成加密安全的随机字符串 func randomString(n int) string { const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" @@ -641,7 +637,7 @@ func (p *MongoDBPlugin) mongodbUnauth(ctx context.Context, info *common.HostInfo } func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, packet []byte, session *common.ScanSession) (string, error) { - conn, err := session.DialTCP(ctx, "tcp", address, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", address, session.Config.ModuleTimeout()) if err != nil { return "", fmt.Errorf(i18n.Tr("service_connection_failed", "%w"), err) } @@ -653,7 +649,7 @@ func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, pack default: } - if deadlineErr := conn.SetDeadline(time.Now().Add(session.Config.Timeout)); deadlineErr != nil { + if deadlineErr := conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())); deadlineErr != nil { return "", deadlineErr } diff --git a/plugins/services/mqtt.go b/plugins/services/mqtt.go index ec9f649..8ca8163 100644 --- a/plugins/services/mqtt.go +++ b/plugins/services/mqtt.go @@ -32,7 +32,7 @@ func NewMQTTPlugin() *MQTTPlugin { } func (p *MQTTPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/ms17010.go b/plugins/services/ms17010.go index 3defc30..f6ed73a 100644 --- a/plugins/services/ms17010.go +++ b/plugins/services/ms17010.go @@ -291,13 +291,13 @@ func (p *MS17010Plugin) checkMS17010Vulnerability(ctx context.Context, ip string } func (p *MS17010Plugin) checkMS17010VulnerabilityAt(ctx context.Context, address string, session *common.ScanSession) (bool, string, bool, error) { - conn, err := session.DialTCP(ctx, "tcp", address, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", address, session.Config.ModuleTimeout()) if err != nil { return false, "", false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_connection_error"), err) } defer func() { _ = conn.Close() }() - if err = conn.SetDeadline(time.Now().Add(session.Config.Timeout)); err != nil { + if err = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())); err != nil { return false, "", false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_set_timeout_error"), err) } diff --git a/plugins/services/mssql.go b/plugins/services/mssql.go index daeea6e..b26ecca 100644 --- a/plugins/services/mssql.go +++ b/plugins/services/mssql.go @@ -63,10 +63,10 @@ func (p *MSSQLPlugin) createAuthFunc(info *common.HostInfo, config *common.Confi // doMSSQLAuth 执行MSSQL认证 func (p *MSSQLPlugin) doMSSQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { - authCtx, cancel := context.WithTimeout(ctx, config.Timeout) + authCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout()) defer cancel() - _, err := mssqlRawLogin(authCtx, info.Host, info.Port, cred.Username, cred.Password, config.Timeout) + _, err := mssqlRawLogin(authCtx, info.Host, info.Port, cred.Username, cred.Password, config.ModuleTimeout()) if err != nil { state.IncrementTCPFailedPacketCount() return &AuthResult{ @@ -129,10 +129,10 @@ func (p *MSSQLPlugin) identifyService(ctx context.Context, info *common.HostInfo state := session.State target := info.Target() - identifyCtx, cancel := context.WithTimeout(ctx, config.Timeout) + identifyCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout()) defer cancel() - result, err := mssqlRawLogin(identifyCtx, info.Host, info.Port, "invalid", "invalid", config.Timeout) + result, err := mssqlRawLogin(identifyCtx, info.Host, info.Port, "invalid", "invalid", config.ModuleTimeout()) if err != nil { state.IncrementTCPFailedPacketCount() diff --git a/plugins/services/mysql.go b/plugins/services/mysql.go index 6b97adb..030324f 100644 --- a/plugins/services/mysql.go +++ b/plugins/services/mysql.go @@ -79,7 +79,7 @@ func (p *MySQLPlugin) createAuthFunc(info *common.HostInfo, config *common.Confi // doMySQLAuth 执行MySQL认证 func (p *MySQLPlugin) doMySQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { - connStr, err := mySQLConnString(cred.Username, cred.Password, info, config.Timeout) + connStr, err := mySQLConnString(cred.Username, cred.Password, info, config.ModuleTimeout()) if err != nil { return &AuthResult{ Success: false, @@ -98,7 +98,7 @@ func (p *MySQLPlugin) doMySQLAuth(ctx context.Context, info *common.HostInfo, cr } } - db.SetConnMaxLifetime(config.Timeout) + db.SetConnMaxLifetime(config.ModuleTimeout()) db.SetMaxOpenConns(1) db.SetMaxIdleConns(0) @@ -194,7 +194,7 @@ func (p *MySQLPlugin) identifyService(ctx context.Context, info *common.HostInfo } func (p *MySQLPlugin) readMySQLBanner(conn net.Conn, config *common.Config) string { - _ = conn.SetReadDeadline(time.Now().Add(config.Timeout)) + _ = conn.SetReadDeadline(time.Now().Add(config.ModuleTimeout())) header := make([]byte, 5) if _, err := io.ReadFull(conn, header); err != nil { diff --git a/plugins/services/neo4j.go b/plugins/services/neo4j.go index 38c5748..b4c5fb5 100644 --- a/plugins/services/neo4j.go +++ b/plugins/services/neo4j.go @@ -72,7 +72,7 @@ func (p *Neo4jPlugin) doNeo4jAuth(ctx context.Context, info *common.HostInfo, cr config := session.Config baseURL := "http://" + info.Target() - client := &http.Client{Timeout: config.Timeout} + client := &http.Client{Timeout: config.ModuleTimeout()} req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/user/neo4j", nil) if err != nil { @@ -148,7 +148,7 @@ func (p *Neo4jPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H config := session.Config baseURL := "http://" + info.Target() - client := &http.Client{Timeout: config.Timeout} + client := &http.Client{Timeout: config.ModuleTimeout()} req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/db/data/", nil) if err != nil { @@ -193,7 +193,7 @@ func (p *Neo4jPlugin) identifyService(ctx context.Context, info *common.HostInfo target := info.Target() baseURL := "http://" + info.Target() - client := &http.Client{Timeout: config.Timeout} + client := &http.Client{Timeout: config.ModuleTimeout()} req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil) if err != nil { diff --git a/plugins/services/netbios.go b/plugins/services/netbios.go index 886a29b..44bfcac 100644 --- a/plugins/services/netbios.go +++ b/plugins/services/netbios.go @@ -164,14 +164,14 @@ func (p *NetBIOSPlugin) queryNetBIOSNames(host string, config *common.Config, st target := fmt.Sprintf("%s:137", host) - conn, err := net.DialTimeout("udp", target, config.Timeout) + conn, err := net.DialTimeout("udp", target, config.ModuleTimeout()) if err != nil { return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_name_connect_failed"), err) } state.IncrementUDPPacketCount() defer func() { _ = conn.Close() }() - _ = conn.SetDeadline(time.Now().Add(config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(config.ModuleTimeout())) _, err = conn.Write(queryPacket) if err != nil { @@ -191,13 +191,13 @@ func (p *NetBIOSPlugin) queryNetBIOSNames(host string, config *common.Config, st func (p *NetBIOSPlugin) queryNetBIOSSession(ctx context.Context, host string, session *common.ScanSession) (*NetBIOSInfo, error) { target := fmt.Sprintf("%s:139", host) - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_session_connect_failed"), err) } defer func() { _ = conn.Close() }() - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())) // 发送SMB协商数据包 smbNegotiate1 := []byte{ diff --git a/plugins/services/nfs.go b/plugins/services/nfs.go index 47fd901..cfeccd5 100644 --- a/plugins/services/nfs.go +++ b/plugins/services/nfs.go @@ -22,7 +22,7 @@ func NewNFSPlugin() *NFSPlugin { } func (p *NFSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/oracle.go b/plugins/services/oracle.go index c8103d8..887bf82 100644 --- a/plugins/services/oracle.go +++ b/plugins/services/oracle.go @@ -72,8 +72,8 @@ func (p *OraclePlugin) doOracleAuth(ctx context.Context, info *common.HostInfo, serviceNames := []string{"ORCL", "XE", "XEPDB1", target} for _, serviceName := range serviceNames { - connectCtx, cancel := context.WithTimeout(ctx, config.Timeout) - err := oracleRawAuth(connectCtx, info.Host, info.Port, serviceName, cred.Username, cred.Password, config.Timeout) + connectCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout()) + err := oracleRawAuth(connectCtx, info.Host, info.Port, serviceName, cred.Username, cred.Password, config.ModuleTimeout()) if err != nil { cancel() errorType := classifyOracleErrorType(err) @@ -168,7 +168,7 @@ func (p *OraclePlugin) testUnauthorizedAccess(ctx context.Context, info *common. func (p *OraclePlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { target := info.Target() - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { return &ScanResult{ Success: false, diff --git a/plugins/services/pop3.go b/plugins/services/pop3.go index 02c251d..a83d8a5 100644 --- a/plugins/services/pop3.go +++ b/plugins/services/pop3.go @@ -23,7 +23,7 @@ func NewPOP3Plugin() *POP3Plugin { func (p *POP3Plugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { config := session.Config - timeout := config.Timeout + timeout := config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/postgresql.go b/plugins/services/postgresql.go index 467a47e..f0761ff 100644 --- a/plugins/services/postgresql.go +++ b/plugins/services/postgresql.go @@ -73,7 +73,7 @@ func (p *PostgreSQLPlugin) createAuthFunc(info *common.HostInfo, config *common. // doPostgreSQLAuth 执行PostgreSQL认证 func (p *PostgreSQLPlugin) doPostgreSQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { - connStr := postgreSQLConnString(cred.Username, cred.Password, info, int64(config.Timeout.Seconds())) + connStr := postgreSQLConnString(cred.Username, cred.Password, info, int64(config.ModuleTimeout().Seconds())) db, err := sql.Open("postgres", connStr) if err != nil { @@ -85,11 +85,11 @@ func (p *PostgreSQLPlugin) doPostgreSQLAuth(ctx context.Context, info *common.Ho } } - db.SetConnMaxLifetime(config.Timeout) + db.SetConnMaxLifetime(config.ModuleTimeout()) db.SetMaxOpenConns(1) db.SetMaxIdleConns(0) - pingCtx, cancel := context.WithTimeout(ctx, config.Timeout) + pingCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout()) defer cancel() err = db.PingContext(pingCtx) @@ -169,7 +169,7 @@ func postgreSQLConnString(username, password string, info *common.HostInfo, time // testUnauthorizedAccess 测试PostgreSQL未授权访问 func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult { - connStr := postgreSQLConnString("postgres", "", info, int64(config.Timeout.Seconds())) + connStr := postgreSQLConnString("postgres", "", info, int64(config.ModuleTimeout().Seconds())) db, err := sql.Open("postgres", connStr) if err != nil { @@ -177,11 +177,11 @@ func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *com } defer func() { _ = db.Close() }() - db.SetConnMaxLifetime(config.Timeout) + db.SetConnMaxLifetime(config.ModuleTimeout()) db.SetMaxOpenConns(1) db.SetMaxIdleConns(0) - pingCtx, cancel := context.WithTimeout(ctx, config.Timeout) + pingCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout()) defer cancel() err = db.PingContext(pingCtx) @@ -192,7 +192,7 @@ func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *com state.IncrementTCPSuccessPacketCount() - queryCtx, queryCancel := context.WithTimeout(ctx, config.Timeout) + queryCtx, queryCancel := context.WithTimeout(ctx, config.ModuleTimeout()) defer queryCancel() var version string @@ -222,7 +222,7 @@ func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.Hos state := session.State target := info.Target() - connStr := postgreSQLConnString("invalid", "invalid", info, int64(config.Timeout.Seconds())) + connStr := postgreSQLConnString("invalid", "invalid", info, int64(config.ModuleTimeout().Seconds())) db, err := sql.Open("postgres", connStr) if err != nil { @@ -234,7 +234,7 @@ func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.Hos } defer func() { _ = db.Close() }() - pingCtx, cancel := context.WithTimeout(ctx, config.Timeout) + pingCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout()) defer cancel() err = db.PingContext(pingCtx) diff --git a/plugins/services/rabbitmq.go b/plugins/services/rabbitmq.go index a413698..216a301 100644 --- a/plugins/services/rabbitmq.go +++ b/plugins/services/rabbitmq.go @@ -84,7 +84,7 @@ func (p *RabbitMQPlugin) doRabbitMQAuth(ctx context.Context, info *common.HostIn } baseURL := "http://" + net.JoinHostPort(info.Host, strconv.Itoa(port)) - client := &http.Client{Timeout: config.Timeout} + client := &http.Client{Timeout: config.ModuleTimeout()} req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/overview", nil) if err != nil { @@ -165,7 +165,7 @@ func (p *RabbitMQPlugin) testUnauthorizedAccess(ctx context.Context, info *commo } baseURL := "http://" + net.JoinHostPort(info.Host, strconv.Itoa(port)) - client := &http.Client{Timeout: config.Timeout} + client := &http.Client{Timeout: config.ModuleTimeout()} // 测试无认证访问 req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/overview", nil) @@ -213,13 +213,13 @@ func (p *RabbitMQPlugin) testUnauthorizedAccess(ctx context.Context, info *commo func (p *RabbitMQPlugin) testAMQPProtocol(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { target := info.Target() - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { return nil } defer func() { _ = conn.Close() }() - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())) // 发送AMQP协议头 amqpHeader := []byte{0x41, 0x4d, 0x51, 0x50, 0x00, 0x00, 0x09, 0x01} @@ -280,7 +280,7 @@ func (p *RabbitMQPlugin) testManagementInterface(ctx context.Context, info *comm target := info.Target() baseURL := "http://" + info.Target() - client := &http.Client{Timeout: config.Timeout} + client := &http.Client{Timeout: config.ModuleTimeout()} req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil) if err != nil { diff --git a/plugins/services/rdp.go b/plugins/services/rdp.go index 79065a6..73281e4 100644 --- a/plugins/services/rdp.go +++ b/plugins/services/rdp.go @@ -159,7 +159,7 @@ func (p *RDPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co // rdpCrack 使用NLA认证验证凭据,不建立完整会话,不会挤掉已登录用户 func (p *RDPPlugin) rdpCrack(host, domain, user, password string, config *common.Config, state *common.State) (bool, error) { - timeout := int64(config.Timeout.Seconds()) + timeout := int64(config.ModuleTimeout().Seconds()) // 使用NLA仅验证模式:只验证凭据,不建立RDP会话 // 这样不会挤掉目标机器上已登录的用户 @@ -180,7 +180,7 @@ func (p *RDPPlugin) rdpCrack(host, domain, user, password string, config *common // probeOSInfo 通过NLA协商获取系统信息(无需密码) func (p *RDPPlugin) probeOSInfo(host string, config *common.Config, state *common.State) map[string]any { - timeout := int64(config.Timeout.Seconds()) + timeout := int64(config.ModuleTimeout().Seconds()) client := login.NewClient(host, glog.NONE) // 使用 PROTOCOL_HYBRID 协议探测系统信息 diff --git a/plugins/services/redis.go b/plugins/services/redis.go index 8cd0035..75fc62a 100644 --- a/plugins/services/redis.go +++ b/plugins/services/redis.go @@ -86,7 +86,7 @@ func (p *RedisPlugin) createAuthFunc(info *common.HostInfo, session *common.Scan // doRedisAuth 执行Redis认证 func (p *RedisPlugin) doRedisAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult { target := info.Target() - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() // 建立TCP连接 conn, err := session.DialTCP(ctx, "tcp", target, timeout) @@ -223,7 +223,7 @@ func (p *RedisPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H func (p *RedisPlugin) exploitWithPassword(ctx context.Context, info *common.HostInfo, password string, session *common.ScanSession) { target := info.Target() - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { session.LogError(i18n.Tr("redis_reconnect_failed", err)) return @@ -232,11 +232,11 @@ func (p *RedisPlugin) exploitWithPassword(ctx context.Context, info *common.Host // 如果有密码,先认证 if password != "" { - _ = conn.SetWriteDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetWriteDeadline(time.Now().Add(session.Config.ModuleTimeout())) if _, writeErr := conn.Write(buildRedisAuthCommand(password)); writeErr != nil { return } - _ = conn.SetReadDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetReadDeadline(time.Now().Add(session.Config.ModuleTimeout())) response := make([]byte, 512) if _, readErr := conn.Read(response); readErr != nil { return @@ -249,7 +249,7 @@ func (p *RedisPlugin) exploitWithPassword(ctx context.Context, info *common.Host // identifyService 服务识别 func (p *RedisPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { target := info.Target() - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() conn, err := session.DialTCP(ctx, "tcp", target, timeout) if err != nil { @@ -325,7 +325,7 @@ func (p *RedisPlugin) exploit(ctx context.Context, info *common.HostInfo, conn n return } - _ = conn.SetDeadline(time.Time{}) + _ = conn.SetDeadline(time.Now().Add(30 * time.Second)) dbfilename, dir, err := p.getConfig(conn) if err != nil { @@ -397,14 +397,24 @@ func (p *RedisPlugin) exploit(ctx context.Context, info *common.HostInfo, conn n // ============================================================================= func (p *RedisPlugin) readReply(conn net.Conn) (string, error) { - _ = conn.SetReadDeadline(time.Now().Add(time.Second)) + _ = conn.SetReadDeadline(time.Now().Add(3 * time.Second)) bytes, err := io.ReadAll(io.LimitReader(conn, maxRedisReplyBytes)) - if len(bytes) > 0 { + if len(bytes) > 0 && isTimeoutError(err) { err = nil } return string(bytes), err } +func isTimeoutError(err error) bool { + if err == nil { + return false + } + if ne, ok := err.(net.Error); ok && ne.Timeout() { + return true + } + return false +} + // sendCmd 发送Redis命令并检查OK响应 // 返回响应文本、是否成功、错误 func (p *RedisPlugin) sendCmd(conn net.Conn, cmd []byte) (text string, ok bool, err error) { diff --git a/plugins/services/rmi.go b/plugins/services/rmi.go index 42cd897..f621768 100644 --- a/plugins/services/rmi.go +++ b/plugins/services/rmi.go @@ -24,7 +24,7 @@ func NewRMIPlugin() *RMIPlugin { } func (p *RMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/rsync.go b/plugins/services/rsync.go index 5acc841..1350752 100644 --- a/plugins/services/rsync.go +++ b/plugins/services/rsync.go @@ -242,7 +242,7 @@ func (p *RsyncPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H // connectToRsync 连接到Rsync服务 func (p *RsyncPlugin) connectToRsync(ctx context.Context, info *common.HostInfo, session *common.ScanSession) net.Conn { target := info.Target() - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() connChan := make(chan net.Conn, 1) @@ -272,7 +272,7 @@ func (p *RsyncPlugin) connectToRsync(ctx context.Context, info *common.HostInfo, // getModules 获取Rsync模块列表 func (p *RsyncPlugin) getModules(conn net.Conn, config *common.Config) []string { - timeout := config.Timeout + timeout := config.ModuleTimeout() // 读取服务器版本 _ = conn.SetReadDeadline(time.Now().Add(timeout)) @@ -342,7 +342,7 @@ func (p *RsyncPlugin) identifyService(ctx context.Context, info *common.HostInfo } defer func() { _ = conn.Close() }() - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() _ = conn.SetWriteDeadline(time.Now().Add(timeout)) if _, err := conn.Write([]byte("\n")); err != nil { diff --git a/plugins/services/smb.go b/plugins/services/smb.go index 4f60eb1..8bbc86e 100644 --- a/plugins/services/smb.go +++ b/plugins/services/smb.go @@ -39,7 +39,7 @@ func (p *SmbPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co } // 1. 协议探测和信息收集 - smbTarget, err := probeTarget(ctx, info.Host, info.Port, config.Timeout, session) + smbTarget, err := probeTarget(ctx, info.Host, info.Port, config.ModuleTimeout(), session) if err != nil { return &ScanResult{ Success: false, @@ -53,7 +53,7 @@ func (p *SmbPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co // 2. 漏洞检测 (仅SMBv2+且端口445) if smbTarget.Protocol == SMBProtocol2 && info.Port == 445 { - if checkSMBGhost(ctx, info.Host, config.Timeout, session) { + if checkSMBGhost(ctx, info.Host, config.ModuleTimeout(), session) { smbTarget.Vulnerable = &SMBVuln{CVE20200796: true} session.LogVuln(i18n.Tr("smbghost_vuln", target)) } @@ -120,7 +120,7 @@ func (p *SmbPlugin) getAuthenticator(protocol SMBProtocol) SMBAuthenticator { func (p *SmbPlugin) createAuthFunc(info *common.HostInfo, auth SMBAuthenticator, session *common.ScanSession) AuthFunc { config := session.Config return func(ctx context.Context, cred Credential) *AuthResult { - result, _ := auth.Authenticate(ctx, info.Host, info.Port, cred, config.Credentials.Domain, config.Timeout, session) + result, _ := auth.Authenticate(ctx, info.Host, info.Port, cred, config.Credentials.Domain, config.ModuleTimeout(), session) return result } } @@ -136,7 +136,7 @@ func (p *SmbPlugin) testUnauthorizedAccess(ctx context.Context, info *common.Hos } for _, cred := range unauthorizedCreds { - shareInfo, err := auth.ListShares(ctx, info.Host, info.Port, cred, config.Credentials.Domain, config.Timeout, session) + shareInfo, err := auth.ListShares(ctx, info.Host, info.Port, cred, config.Credentials.Domain, config.ModuleTimeout(), session) if err == nil && len(shareInfo) > 0 { var output strings.Builder displayUser := cred.Username diff --git a/plugins/services/smtp.go b/plugins/services/smtp.go index ce992ef..dece689 100644 --- a/plugins/services/smtp.go +++ b/plugins/services/smtp.go @@ -78,7 +78,7 @@ func (p *SMTPPlugin) createAuthFunc(info *common.HostInfo, session *common.ScanS // doSMTPAuth 执行SMTP认证 func (p *SMTPPlugin) doSMTPAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult { target := info.Target() - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() resultChan := make(chan *AuthResult, 1) @@ -236,7 +236,7 @@ func (p *SMTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostI resultChan := make(chan *ScanResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- nil return @@ -288,7 +288,7 @@ func (p *SMTPPlugin) testOpenRelay(ctx context.Context, info *common.HostInfo, s resultChan := make(chan *ScanResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- nil return @@ -340,14 +340,14 @@ func (p *SMTPPlugin) testVRFYCommand(ctx context.Context, info *common.HostInfo, resultChan := make(chan *ScanResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- nil return } defer func() { _ = conn.Close() }() - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())) if _, heloWriteErr := fmt.Fprintf(conn, "HELO fscan.test\r\n"); heloWriteErr != nil { resultChan <- nil @@ -410,14 +410,14 @@ func (p *SMTPPlugin) testEXPNCommand(ctx context.Context, info *common.HostInfo, resultChan := make(chan *ScanResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- nil return } defer func() { _ = conn.Close() }() - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())) if _, heloWriteErr := fmt.Fprintf(conn, "HELO fscan.test\r\n"); heloWriteErr != nil { resultChan <- nil @@ -480,14 +480,14 @@ func (p *SMTPPlugin) getServerInfo(ctx context.Context, info *common.HostInfo, s resultChan := make(chan string, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- "" return } defer func() { _ = conn.Close() }() - _ = conn.SetReadDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetReadDeadline(time.Now().Add(session.Config.ModuleTimeout())) buffer := make([]byte, 1024) n, err := conn.Read(buffer) if err != nil { @@ -524,7 +524,7 @@ func (p *SMTPPlugin) identifyService(ctx context.Context, info *common.HostInfo, if serverInfo != "" { banner = i18n.Tr("smtp_mail_service_info", serverInfo) } else { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { return &ScanResult{ Success: false, diff --git a/plugins/services/snmp.go b/plugins/services/snmp.go index 55daeab..02a64a9 100644 --- a/plugins/services/snmp.go +++ b/plugins/services/snmp.go @@ -23,10 +23,7 @@ func NewSNMPPlugin() *SNMPPlugin { func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { config := session.Config - timeout := config.Timeout - if timeout <= 0 { - timeout = 3 * time.Second - } + timeout := config.ModuleTimeout() target := info.Target() @@ -35,6 +32,8 @@ func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c return &ScanResult{Success: false, Service: "snmp"} } + session.LogVuln(fmt.Sprintf("SNMP %s %s", target, result.Banner)) + if config.DisableBrute { return result } diff --git a/plugins/services/ssh.go b/plugins/services/ssh.go index 43468ec..ed22cf1 100644 --- a/plugins/services/ssh.go +++ b/plugins/services/ssh.go @@ -64,8 +64,12 @@ func (p *SSHPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co } // 使用公共框架进行并发凭据测试 + // SSH 并发限制为 3:OpenSSH MaxStartups 默认 10:30:60,高并发会被随机丢弃 authFn := p.createAuthFunc(info, session) testConfig := DefaultConcurrentTestConfigWithTarget(config, info) + if testConfig.Concurrency > 3 { + testConfig.Concurrency = 3 + } result := TestCredentialsConcurrently(ctx, credentials, authFn, "ssh", testConfig) @@ -90,9 +94,10 @@ func (p *SSHPlugin) doSSHAuth(ctx context.Context, info *common.HostInfo, cred C target := info.Target() // 创建SSH配置 + moduleTimeout := config.ModuleTimeout() sshConfig := &ssh.ClientConfig{ User: cred.Username, - Timeout: config.Timeout, + Timeout: moduleTimeout, //nolint:gosec // G106: 扫描工具需要忽略主机密钥验证以连接未知主机 HostKeyCallback: ssh.InsecureIgnoreHostKey(), } @@ -133,6 +138,9 @@ func (p *SSHPlugin) doSSHAuth(ctx context.Context, info *common.HostInfo, cred C } }() + // 设置 TCP 级别 deadline 兜底整个握手过程 + _ = conn.SetDeadline(time.Now().Add(moduleTimeout)) + // 在TCP连接上创建SSH客户端 sshConn, chans, reqs, err := ssh.NewClientConn(conn, target, sshConfig) if err != nil { @@ -151,6 +159,9 @@ func (p *SSHPlugin) doSSHAuth(ctx context.Context, info *common.HostInfo, cred C } } + // 握手成功,清除 deadline + _ = conn.SetDeadline(time.Time{}) + // 创建SSH客户端 client := ssh.NewClient(sshConn, chans, reqs) @@ -183,16 +194,39 @@ func classifySSHErrorType(err error) ErrorType { "no supported methods remain", ) - // SSH 特有的网络/临时错误(需要重试) - sshNetworkErrors := append(CommonNetworkErrors, - "handshake failed", // 握手失败,可能是服务端限流 - "ssh: disconnect", // SSH 主动断开 - "connection closed", // 连接被关闭 - "max startups", // SSH MaxStartups 限制 - "too many authentication", // 认证次数过多 - ) + // SSH 限流错误 — 服务端主动拒绝(MaxStartups 等),退避后重试即可 + sshThrottleErrors := []string{ + "handshake failed", + "ssh: disconnect", + "connection closed", + "max startups", + "too many authentication", + } - return ClassifyError(err, sshAuthErrors, sshNetworkErrors) + return classifySSHError(err, sshAuthErrors, sshThrottleErrors) +} + +func classifySSHError(err error, authKeywords, throttleKeywords []string) ErrorType { + if err == nil { + return ErrorTypeUnknown + } + errStr := err.Error() + for _, kw := range authKeywords { + if containsIgnoreCase(errStr, kw) { + return ErrorTypeAuth + } + } + for _, kw := range throttleKeywords { + if containsIgnoreCase(errStr, kw) { + return ErrorTypeThrottle + } + } + for _, kw := range CommonNetworkErrors { + if containsIgnoreCase(errStr, kw) { + return ErrorTypeNetwork + } + } + return ErrorTypeUnknown } // scanWithKey 使用SSH私钥扫描 @@ -272,7 +306,7 @@ func (p *SSHPlugin) identifyService(ctx context.Context, info *common.HostInfo, // readSSHBanner 读取SSH服务器Banner func (p *SSHPlugin) readSSHBanner(conn net.Conn, config *common.Config) string { - _ = conn.SetReadDeadline(time.Now().Add(config.Timeout)) + _ = conn.SetReadDeadline(time.Now().Add(config.ModuleTimeout())) banner := make([]byte, 256) n, err := conn.Read(banner) diff --git a/plugins/services/telnet.go b/plugins/services/telnet.go index ade5916..66259db 100644 --- a/plugins/services/telnet.go +++ b/plugins/services/telnet.go @@ -121,7 +121,7 @@ func (p *TelnetPlugin) doTelnetAuth(ctx context.Context, info *common.HostInfo, resultChan := make(chan *AuthResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- &AuthResult{ Success: false, @@ -131,7 +131,7 @@ func (p *TelnetPlugin) doTelnetAuth(ctx context.Context, info *common.HostInfo, return } - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())) if p.performTelnetAuth(conn, cred.Username, cred.Password) { resultChan <- &AuthResult{ @@ -215,14 +215,14 @@ func (p *TelnetPlugin) testUnauthAccess(ctx context.Context, info *common.HostIn resultChan := make(chan *ScanResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- nil return } defer func() { _ = conn.Close() }() - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())) buffer := make([]byte, 1024) attempts := 0 @@ -510,7 +510,7 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf resultChan := make(chan *ScanResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- &ScanResult{ Success: false, @@ -521,7 +521,7 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf } defer func() { _ = conn.Close() }() - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())) buffer := make([]byte, 2048) n, err := conn.Read(buffer) @@ -595,14 +595,14 @@ func (p *TelnetPlugin) verifyCommandExecution(ctx context.Context, info *common. resultChan := make(chan rceResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- rceResult{} return } defer func() { _ = conn.Close() }() - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout + telnetRCEExtraTimeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout() + telnetRCEExtraTimeout)) // 需要认证时先登录 if username != "" || password != "" { @@ -808,7 +808,7 @@ func (p *TelnetPlugin) checkCVE202624061Concurrent(ctx context.Context, info *co // 利用 NEW-ENVIRON (option 39) 子协商注入恶意环境变量,实现认证绕过 // 返回 (是否漏洞, 触发用户名, 证据) func (p *TelnetPlugin) checkCVE202624061(ctx context.Context, info *common.HostInfo, session *common.ScanSession, user string) (bool, string, string) { - conn, err := session.DialTCP(ctx, "tcp", info.Target(), session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", info.Target(), session.Config.ModuleTimeout()) if err != nil { return false, "", "" } diff --git a/plugins/services/tftp.go b/plugins/services/tftp.go index ad57e98..2bf5836 100644 --- a/plugins/services/tftp.go +++ b/plugins/services/tftp.go @@ -21,7 +21,7 @@ func NewTFTPPlugin() *TFTPPlugin { } func (p *TFTPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/plugins/services/vnc.go b/plugins/services/vnc.go index d8683fd..9d6aec3 100644 --- a/plugins/services/vnc.go +++ b/plugins/services/vnc.go @@ -74,7 +74,7 @@ func (p *VNCPlugin) doVNCAuth(ctx context.Context, info *common.HostInfo, cred C resultChan := make(chan *AuthResult, 1) go func() { - conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout) + conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout()) if err != nil { resultChan <- &AuthResult{ Success: false, @@ -84,7 +84,7 @@ func (p *VNCPlugin) doVNCAuth(ctx context.Context, info *common.HostInfo, cred C return } - _ = conn.SetDeadline(time.Now().Add(session.Config.Timeout)) + _ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())) vncConfig := &vnc.ClientConfig{ Auth: []vnc.ClientAuth{ diff --git a/plugins/services/zookeeper.go b/plugins/services/zookeeper.go index 91f3e44..40561b2 100644 --- a/plugins/services/zookeeper.go +++ b/plugins/services/zookeeper.go @@ -20,7 +20,7 @@ func NewZooKeeperPlugin() *ZooKeeperPlugin { } func (p *ZooKeeperPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { - timeout := session.Config.Timeout + timeout := session.Config.ModuleTimeout() if timeout <= 0 { timeout = 3 * time.Second } diff --git a/webscan/fingerprint/enhanced.go b/webscan/fingerprint/enhanced.go index ec376fc..3739b6e 100644 --- a/webscan/fingerprint/enhanced.go +++ b/webscan/fingerprint/enhanced.go @@ -303,14 +303,18 @@ func matchRegex(matcher struct { // 从 sync.Map 缓存获取或编译正则 var re *regexp.Regexp if cached, ok := enhancedDB.regexCache.Load(cacheKey); ok { - re = cached.(*regexp.Regexp) + if r, ok := cached.(*regexp.Regexp); ok { + re = r + } } else { compiled, err := regexp.Compile(cacheKey) if err != nil { continue } actual, _ := enhancedDB.regexCache.LoadOrStore(cacheKey, compiled) - re = actual.(*regexp.Regexp) + if r, ok := actual.(*regexp.Regexp); ok { + re = r + } } // 确保 re 不为 nil(防止并发场景下的 nil panic) diff --git a/webscan/lib/eval_string.go b/webscan/lib/eval_string.go index d5af036..083c70f 100644 --- a/webscan/lib/eval_string.go +++ b/webscan/lib/eval_string.go @@ -73,14 +73,14 @@ func registerStringImplementations() []*functions.Overload { pattern := string(v1) var re *regexp.Regexp if cached, found := regexCache.Load(pattern); found { - re = cached.(*regexp.Regexp) + re, _ = cached.(*regexp.Regexp) } else { compiled, err := regexp.Compile(pattern) if err != nil { return types.NewErr("%v", err) } actual, _ := regexCache.LoadOrStore(pattern, compiled) - re = actual.(*regexp.Regexp) + re, _ = actual.(*regexp.Regexp) } return types.Bool(re.Match(v2)) }, diff --git a/webscan/lib/poc_executor.go b/webscan/lib/poc_executor.go index 476cbd3..a11a3e1 100644 --- a/webscan/lib/poc_executor.go +++ b/webscan/lib/poc_executor.go @@ -301,7 +301,7 @@ func doSearch(re string, body string) map[string]string { // 编译正则表达式(带缓存) var r *regexp.Regexp if cached, ok := regexCache.Load(re); ok { - r = cached.(*regexp.Regexp) + r, _ = cached.(*regexp.Regexp) } else { compiled, err := regexp.Compile(re) if err != nil { @@ -309,7 +309,7 @@ func doSearch(re string, body string) map[string]string { return nil } actual, _ := regexCache.LoadOrStore(re, compiled) - r = actual.(*regexp.Regexp) + r, _ = actual.(*regexp.Regexp) } // 执行正则匹配