mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
fix: 大规模扫描全局超时过短导致提前终止 (#588)
4 万 IP 全端口扫描默认 -gt 180s 完全不够用,3 分钟后报 "解析目标失败: context deadline exceeded" 误导用户。 1. 自适应全局超时:用户未显式指定 -gt 时,根据端口数和是否有 hosts 文件自动调大超时(最高 24h),并输出调整日志 2. 修正超时错误信息:context deadline exceeded 不再包装为 "解析目标失败",改为提示用户调大 -gt 或设为 0 禁用
This commit is contained in:
@@ -63,7 +63,8 @@ type Config struct {
|
|||||||
Target TargetConfig // 扫描目标配置
|
Target TargetConfig // 扫描目标配置
|
||||||
|
|
||||||
// 全局超时
|
// 全局超时
|
||||||
GlobalTimeout time.Duration
|
GlobalTimeout time.Duration
|
||||||
|
GlobalTimeoutExplicit bool
|
||||||
|
|
||||||
// SOCKS5代理端口配置
|
// SOCKS5代理端口配置
|
||||||
Socks5ProxyPort int // SOCKS5代理端口
|
Socks5ProxyPort int // SOCKS5代理端口
|
||||||
|
|||||||
@@ -226,6 +226,8 @@ func Flag(Info *HostInfo) error {
|
|||||||
fv.ModuleThreadNumExplicit = true
|
fv.ModuleThreadNumExplicit = true
|
||||||
case "retry":
|
case "retry":
|
||||||
fv.MaxRetriesExplicit = true
|
fv.MaxRetriesExplicit = true
|
||||||
|
case "gt":
|
||||||
|
fv.GlobalTimeoutExplicit = true
|
||||||
case "icmp-rate":
|
case "icmp-rate":
|
||||||
fv.ICMPRateExplicit = true
|
fv.ICMPRateExplicit = true
|
||||||
case "num":
|
case "num":
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ type FlagVars struct {
|
|||||||
TimeoutSec int64 // 秒,需转换为 time.Duration
|
TimeoutSec int64 // 秒,需转换为 time.Duration
|
||||||
TimeoutExplicit bool
|
TimeoutExplicit bool
|
||||||
GlobalTimeout int64
|
GlobalTimeout int64
|
||||||
|
GlobalTimeoutExplicit bool
|
||||||
DisablePing bool
|
DisablePing bool
|
||||||
DisableTcpProbe bool
|
DisableTcpProbe bool
|
||||||
DisableSubnetProbe bool
|
DisableSubnetProbe bool
|
||||||
@@ -171,7 +172,8 @@ func BuildConfigFromFlags(fv *FlagVars) *Config {
|
|||||||
DefaultMap: cloneStringSlice(config.DefaultProbeMap),
|
DefaultMap: cloneStringSlice(config.DefaultProbeMap),
|
||||||
|
|
||||||
// 全局超时
|
// 全局超时
|
||||||
GlobalTimeout: time.Duration(fv.GlobalTimeout) * time.Second,
|
GlobalTimeout: time.Duration(fv.GlobalTimeout) * time.Second,
|
||||||
|
GlobalTimeoutExplicit: fv.GlobalTimeoutExplicit,
|
||||||
|
|
||||||
// SOCKS5代理端口
|
// SOCKS5代理端口
|
||||||
Socks5ProxyPort: fv.Socks5ProxyPort,
|
Socks5ProxyPort: fv.Socks5ProxyPort,
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ flag_module_thread_num:
|
|||||||
other: "Module thread count"
|
other: "Module thread count"
|
||||||
flag_global_timeout:
|
flag_global_timeout:
|
||||||
other: "Global timeout"
|
other: "Global timeout"
|
||||||
|
global_timeout_adjusted:
|
||||||
|
other: "Large scan detected, global timeout adjusted from {{.V0}}s to {{.V1}}s (use -gt to override)"
|
||||||
|
global_timeout_exceeded:
|
||||||
|
other: "Global timeout reached (-gt {{.V0}}s), scan aborted. Use -gt to increase or set to 0 to disable"
|
||||||
flag_disable_ping:
|
flag_disable_ping:
|
||||||
other: "Disable ping detection"
|
other: "Disable ping detection"
|
||||||
flag_disable_tcp_probe:
|
flag_disable_tcp_probe:
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ flag_module_thread_num:
|
|||||||
other: "模块线程数"
|
other: "模块线程数"
|
||||||
flag_global_timeout:
|
flag_global_timeout:
|
||||||
other: "全局超时时间"
|
other: "全局超时时间"
|
||||||
|
global_timeout_adjusted:
|
||||||
|
other: "扫描规模较大,全局超时从 {{.V0}}s 自动调整为 {{.V1}}s(可用 -gt 手动指定)"
|
||||||
|
global_timeout_exceeded:
|
||||||
|
other: "全局超时已到(-gt {{.V0}}s),扫描被终止。大规模扫描请用 -gt 调大超时或设为 0 禁用"
|
||||||
flag_disable_ping:
|
flag_disable_ping:
|
||||||
other: "禁用ping探测"
|
other: "禁用ping探测"
|
||||||
flag_disable_tcp_probe:
|
flag_disable_tcp_probe:
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ func (s *AliveScanStrategy) performAliveScan(ctx context.Context, info common.Ho
|
|||||||
for {
|
for {
|
||||||
hosts, err := iter.NextBatch(ctx, targetHostBatchSize(session.Config))
|
hosts, err := iter.NextBatch(ctx, targetHostBatchSize(session.Config))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
session.LogError(i18n.Tr("global_timeout_exceeded",
|
||||||
|
int(session.Config.GlobalTimeout.Seconds())))
|
||||||
|
return
|
||||||
|
}
|
||||||
session.LogError(i18n.Tr("parse_target_failed", err))
|
session.LogError(i18n.Tr("parse_target_failed", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
"github.com/shadow1ng/fscan/common/i18n"
|
"github.com/shadow1ng/fscan/common/i18n"
|
||||||
"github.com/shadow1ng/fscan/common/output"
|
"github.com/shadow1ng/fscan/common/output"
|
||||||
|
"github.com/shadow1ng/fscan/common/parsers"
|
||||||
"github.com/shadow1ng/fscan/plugins"
|
"github.com/shadow1ng/fscan/plugins"
|
||||||
"github.com/shadow1ng/fscan/webscan/lib"
|
"github.com/shadow1ng/fscan/webscan/lib"
|
||||||
)
|
)
|
||||||
@@ -96,6 +97,15 @@ func RunScan(ctx context.Context, info common.HostInfo, session *common.ScanSess
|
|||||||
start := time.Now()
|
start := time.Now()
|
||||||
config := session.Config
|
config := session.Config
|
||||||
|
|
||||||
|
// 全局超时自适应:用户未显式指定 -gt 时,根据扫描规模自动调大
|
||||||
|
if !config.GlobalTimeoutExplicit && config.GlobalTimeout > 0 {
|
||||||
|
if adjusted := estimateGlobalTimeout(config, session); adjusted > config.GlobalTimeout {
|
||||||
|
session.LogInfo(i18n.Tr("global_timeout_adjusted",
|
||||||
|
int(config.GlobalTimeout.Seconds()), int(adjusted.Seconds())))
|
||||||
|
config.GlobalTimeout = adjusted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 全局超时:-gt 参数设置整个扫描的硬性截止时间
|
// 全局超时:-gt 参数设置整个扫描的硬性截止时间
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
if config.GlobalTimeout > 0 {
|
if config.GlobalTimeout > 0 {
|
||||||
@@ -489,3 +499,32 @@ func addCommonDetails(result *plugins.Result, details map[string]interface{}) {
|
|||||||
details["server"] = result.Server
|
details["server"] = result.Server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func estimateGlobalTimeout(config *common.Config, session *common.ScanSession) time.Duration {
|
||||||
|
portCount := len(parsers.ParsePort(config.Target.Ports))
|
||||||
|
if portCount == 0 {
|
||||||
|
portCount = len(parsers.ParsePort("21,22,80,443,445,1433,3306,3389,6379,8080"))
|
||||||
|
}
|
||||||
|
|
||||||
|
hasHostFile := session.Params != nil && session.Params.HostsFile != ""
|
||||||
|
|
||||||
|
// 启发式:端口数越多、有文件输入(目标可能很多),超时越大
|
||||||
|
switch {
|
||||||
|
case portCount > 10000 && hasHostFile:
|
||||||
|
return 24 * time.Hour
|
||||||
|
case portCount > 10000:
|
||||||
|
return 6 * time.Hour
|
||||||
|
case portCount > 1000 && hasHostFile:
|
||||||
|
return 6 * time.Hour
|
||||||
|
case portCount > 1000:
|
||||||
|
return 1 * time.Hour
|
||||||
|
case portCount > 100 && hasHostFile:
|
||||||
|
return 1 * time.Hour
|
||||||
|
case portCount > 100:
|
||||||
|
return 30 * time.Minute
|
||||||
|
case hasHostFile:
|
||||||
|
return 30 * time.Minute
|
||||||
|
default:
|
||||||
|
return config.GlobalTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -161,6 +161,11 @@ func (s *ServiceScanStrategy) performHostScan(ctx context.Context, session *comm
|
|||||||
for {
|
for {
|
||||||
hosts, err := iter.NextBatch(ctx, targetHostBatchSize(config))
|
hosts, err := iter.NextBatch(ctx, targetHostBatchSize(config))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
session.LogError(i18n.Tr("global_timeout_exceeded",
|
||||||
|
int(config.GlobalTimeout.Seconds())))
|
||||||
|
return
|
||||||
|
}
|
||||||
session.LogError(fmt.Sprintf("%s: %v", i18n.GetText("parse_target_failed"), err))
|
session.LogError(fmt.Sprintf("%s: %v", i18n.GetText("parse_target_failed"), err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user