mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
fix: 全局超时改为动态估算,替代硬编码阈值表
根据 hostCount × portCount / threads 计算端口扫描耗时, 结合开放率估算插件扫描耗时,加 20% 余量,上限 2h。 新增 EstimateHostCount 快速统计 CIDR/range/文件中的主机数。
This commit is contained in:
@@ -488,3 +488,90 @@ func ipToUint32(ip net.IP) (uint32, bool) {
|
|||||||
func uint32ToIP(v uint32) string {
|
func uint32ToIP(v uint32) string {
|
||||||
return fmt.Sprintf("%d.%d.%d.%d", byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
return fmt.Sprintf("%d.%d.%d.%d", byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EstimateHostCount 快速估算主机总数(不消费 iterator)
|
||||||
|
func EstimateHostCount(host string, filename string) int64 {
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
if filename != "" {
|
||||||
|
if f, err := os.Open(filename); err == nil {
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
total += estimateHostEntry(line)
|
||||||
|
}
|
||||||
|
_ = f.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, h := range strings.Split(host, ",") {
|
||||||
|
h = strings.TrimSpace(h)
|
||||||
|
if h != "" {
|
||||||
|
total += estimateHostEntry(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
func estimateHostEntry(entry string) int64 {
|
||||||
|
switch {
|
||||||
|
case entry == "192":
|
||||||
|
return 65536 // /16
|
||||||
|
case entry == "172":
|
||||||
|
return 1 << 20 // /12
|
||||||
|
case entry == "10":
|
||||||
|
return 1 << 24 // /8
|
||||||
|
case strings.Contains(entry, "/"):
|
||||||
|
_, ipNet, err := net.ParseCIDR(entry)
|
||||||
|
if err != nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
ones, bits := ipNet.Mask.Size()
|
||||||
|
if bits != 32 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
size := int64(1) << uint(32-ones)
|
||||||
|
if size > 2 {
|
||||||
|
size -= 2
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
case strings.Contains(entry, "-") && !strings.Contains(entry, ":") && looksLikeIPRange(entry):
|
||||||
|
parts := strings.SplitN(entry, "-", 2)
|
||||||
|
startIP := net.ParseIP(strings.TrimSpace(parts[0]))
|
||||||
|
if startIP == nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
startU, ok := ipToUint32(startIP)
|
||||||
|
if !ok {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
endStr := strings.TrimSpace(parts[1])
|
||||||
|
var endU uint32
|
||||||
|
if len(endStr) < 4 || !strings.Contains(endStr, ".") {
|
||||||
|
n, err := strconv.Atoi(endStr)
|
||||||
|
if err != nil || n > 255 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
endU = (startU & 0xFFFFFF00) | uint32(n)
|
||||||
|
} else {
|
||||||
|
endIP := net.ParseIP(endStr)
|
||||||
|
if endIP == nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
endU, ok = ipToUint32(endIP)
|
||||||
|
if !ok {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if endU < startU {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return int64(endU-startU) + 1
|
||||||
|
default:
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+42
-22
@@ -501,30 +501,50 @@ func addCommonDetails(result *plugins.Result, details map[string]interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func estimateGlobalTimeout(config *common.Config, session *common.ScanSession) time.Duration {
|
func estimateGlobalTimeout(config *common.Config, session *common.ScanSession) time.Duration {
|
||||||
portCount := len(parsers.ParsePort(config.Target.Ports))
|
portCount := int64(len(parsers.ParsePort(config.Target.Ports)))
|
||||||
if portCount == 0 {
|
if portCount == 0 {
|
||||||
portCount = len(parsers.ParsePort("21,22,80,443,445,1433,3306,3389,6379,8080"))
|
portCount = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
hasHostFile := session.Params != nil && session.Params.HostsFile != ""
|
var hostFile string
|
||||||
|
var hostStr string
|
||||||
// 启发式:端口数越多、有文件输入(目标可能很多),超时越大
|
if session.Params != nil {
|
||||||
switch {
|
hostFile = session.Params.HostsFile
|
||||||
case portCount > 10000 && hasHostFile:
|
hostStr = session.Params.Host
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
hostCount := parsers.EstimateHostCount(hostStr, hostFile)
|
||||||
|
if hostCount <= 0 {
|
||||||
|
hostCount = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
totalTasks := hostCount * portCount
|
||||||
|
threads := int64(config.ThreadNum)
|
||||||
|
if threads <= 0 {
|
||||||
|
threads = 600
|
||||||
|
}
|
||||||
|
|
||||||
|
// 端口扫描:平均每个任务约 50ms(大部分连接快速失败)
|
||||||
|
portScanSec := float64(totalTasks) * 0.05 / float64(threads)
|
||||||
|
|
||||||
|
// 插件扫描:开放率随端口数下降(全端口约 0.1%,少量端口约 5%)
|
||||||
|
openRate := 0.05
|
||||||
|
if portCount > 1000 {
|
||||||
|
openRate = 0.002
|
||||||
|
} else if portCount > 100 {
|
||||||
|
openRate = 0.01
|
||||||
|
}
|
||||||
|
moduleThreads := float64(config.ModuleThreadNum)
|
||||||
|
if moduleThreads <= 0 {
|
||||||
|
moduleThreads = 20
|
||||||
|
}
|
||||||
|
pluginSec := float64(totalTasks) * openRate * 2.0 / moduleThreads
|
||||||
|
// 总估算 + 20% 余量
|
||||||
|
estimatedSec := (portScanSec + pluginSec) * 1.2
|
||||||
|
|
||||||
|
const maxTimeout = 2 * time.Hour
|
||||||
|
estimated := time.Duration(estimatedSec) * time.Second
|
||||||
|
if estimated > maxTimeout {
|
||||||
|
estimated = maxTimeout
|
||||||
|
}
|
||||||
|
return estimated
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user