diff --git a/common/flag.go b/common/flag.go index 97a12af..d938178 100644 --- a/common/flag.go +++ b/common/flag.go @@ -63,6 +63,10 @@ var ( DownloadURL string // 下载文件的URL DownloadSavePath string // 下载文件保存路径 + // 发包频率控制相关变量 + PacketRateLimit int64 // 每分钟最大发包次数 (0表示不限制) + MaxPacketCount int64 // 整个程序最大发包总数 (0表示不限制) + // Parse.go 使用的变量 HostPort []string URLs []string @@ -199,6 +203,12 @@ func Flag(Info *HostInfo) { flag.BoolVar(&DisableBrute, "nobr", false, i18n.GetText("flag_disable_brute")) flag.IntVar(&MaxRetries, "retry", 3, i18n.GetText("flag_max_retries")) + // ═════════════════════════════════════════════════ + // 发包频率控制参数 + // ═════════════════════════════════════════════════ + flag.Int64Var(&PacketRateLimit, "rate", 0, i18n.GetText("flag_packet_rate_limit")) + flag.Int64Var(&MaxPacketCount, "maxpkts", 0, i18n.GetText("flag_max_packet_count")) + // ═════════════════════════════════════════════════ // 输出与显示控制参数 // ═════════════════════════════════════════════════ diff --git a/common/globals.go b/common/globals.go index 7d35009..97ffe4a 100644 --- a/common/globals.go +++ b/common/globals.go @@ -1,6 +1,8 @@ package common import ( + "fmt" + "strings" "sync" "sync/atomic" "time" @@ -110,6 +112,11 @@ var ( UDPPacketCount int64 // UDP包数量 HTTPPacketCount int64 // HTTP请求数量 + // 发包频率控制变量 + PacketRateStartTime int64 // 当前分钟的开始时间戳 + PacketRateCurrentCount int64 // 当前分钟内的发包数量 + PacketRateMutex sync.Mutex // 发包频率控制互斥锁 + // ============================================================================= // 初始化控制 // ============================================================================= @@ -256,4 +263,71 @@ func ResetPacketCounters() { atomic.StoreInt64(&TCPFailedPacketCount, 0) atomic.StoreInt64(&UDPPacketCount, 0) atomic.StoreInt64(&HTTPPacketCount, 0) +} + +// ============================================================================= +// 发包频率控制功能 +// ============================================================================= + +// CanSendPacket 检查是否可以发包 - 同时检查频率限制和总数限制 +// 返回值: (可以发包, 错误信息) +func CanSendPacket() (bool, string) { + // 检查总数限制 + if MaxPacketCount > 0 { + currentTotal := atomic.LoadInt64(&PacketCount) + if currentTotal >= MaxPacketCount { + return false, fmt.Sprintf("已达到最大发包数量限制: %d", MaxPacketCount) + } + } + + // 检查频率限制 + if PacketRateLimit > 0 { + PacketRateMutex.Lock() + defer PacketRateMutex.Unlock() + + currentTime := time.Now().Unix() / 60 // 当前分钟 + + // 如果是新的分钟,重置计数器 + if PacketRateStartTime != currentTime { + PacketRateStartTime = currentTime + atomic.StoreInt64(&PacketRateCurrentCount, 0) + } + + // 检查当前分钟的发包数是否超限 + currentCount := atomic.LoadInt64(&PacketRateCurrentCount) + if currentCount >= PacketRateLimit { + return false, fmt.Sprintf("已达到每分钟发包频率限制: %d", PacketRateLimit) + } + + // 增加当前分钟的发包计数 + atomic.AddInt64(&PacketRateCurrentCount, 1) + } + + return true, "" +} + +// WaitForPacketLimit 等待直到可以发包 - 智能等待策略 +func WaitForPacketLimit() error { + maxWaitTime := 60 * time.Second // 最大等待1分钟 + startWait := time.Now() + + for { + canSend, reason := CanSendPacket() + if canSend { + return nil + } + + // 如果是总数限制,直接返回错误 + if MaxPacketCount > 0 && strings.Contains(reason, "最大发包数量") { + return fmt.Errorf(reason) + } + + // 如果等待超时,返回错误 + if time.Since(startWait) > maxWaitTime { + return fmt.Errorf("等待发包权限超时: %s", reason) + } + + // 短暂休眠后重试 + time.Sleep(100 * time.Millisecond) + } } \ No newline at end of file diff --git a/common/i18n/messages/flag.go b/common/i18n/messages/flag.go index 0e42ccc..1aaa567 100644 --- a/common/i18n/messages/flag.go +++ b/common/i18n/messages/flag.go @@ -187,6 +187,14 @@ var FlagMessages = map[string]map[string]string{ LangZH: "最大重试次数", LangEN: "Maximum retries", }, + "flag_packet_rate_limit": { + LangZH: "每分钟最大发包次数 (0表示不限制)", + LangEN: "Maximum packets per minute (0 means no limit)", + }, + "flag_max_packet_count": { + LangZH: "整个程序最大发包总数 (0表示不限制)", + LangEN: "Maximum total packet count for entire program (0 means no limit)", + }, "flag_output_file": { LangZH: "输出文件", LangEN: "Output file", diff --git a/core/PortScan.go b/core/PortScan.go index a0a5a2e..6945e3f 100644 --- a/core/PortScan.go +++ b/core/PortScan.go @@ -77,6 +77,12 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string { common.UpdateProgressBar(1) }() + // 检查发包限制 + if canSend, reason := common.CanSendPacket(); !canSend { + common.LogError(fmt.Sprintf("端口扫描 %s 受限: %s", addr, reason)) + return nil + } + // 连接测试 - 支持SOCKS5代理 conn, err := common.WrapperTcpWithTimeout("tcp", addr, to) diff --git a/core/WebDetection.go b/core/WebDetection.go index a2bb6a0..f26791b 100644 --- a/core/WebDetection.go +++ b/core/WebDetection.go @@ -253,6 +253,12 @@ func (w *WebPortDetector) tryHTTPConnectionDirect(ctx context.Context, host stri req.Header.Set("User-Agent", "fscan-web-detector/2.2") req.Header.Set("Accept", "*/*") + // 检查发包限制 + if canSend, reason := common.CanSendPacket(); !canSend { + common.LogError(fmt.Sprintf("HTTP请求 %s 受限: %s", req.URL.String(), reason)) + return false + } + resp, err := client.Do(req) if err != nil { diff --git a/plugins/services/redis.go b/plugins/services/redis.go index 22e49ae..6ce9c3f 100644 --- a/plugins/services/redis.go +++ b/plugins/services/redis.go @@ -121,6 +121,12 @@ func (p *RedisPlugin) testCredential(ctx context.Context, info *common.HostInfo, err error } + // 检查发包限制 + if canSend, reason := common.CanSendPacket(); !canSend { + common.LogError(fmt.Sprintf("Redis连接 %s 受限: %s", target, reason)) + return nil + } + connChan := make(chan connResult, 1) go func() { @@ -355,6 +361,16 @@ func (p *RedisPlugin) identifyService(ctx context.Context, info *common.HostInfo target := fmt.Sprintf("%s:%s", info.Host, info.Ports) timeout := time.Duration(common.Timeout) * time.Second + // 检查发包限制 + if canSend, reason := common.CanSendPacket(); !canSend { + common.LogError(fmt.Sprintf("Redis服务识别 %s 受限: %s", target, reason)) + return &ScanResult{ + Success: false, + Service: "redis", + Error: fmt.Errorf("发包受限: %s", reason), + } + } + // 尝试连接Redis服务 conn, err := net.DialTimeout("tcp", target, timeout) if err != nil { diff --git a/plugins/services/snmp.go b/plugins/services/snmp.go index 2232678..15788a0 100644 --- a/plugins/services/snmp.go +++ b/plugins/services/snmp.go @@ -62,6 +62,12 @@ func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResul func (p *SNMPPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + // 检查发包限制 + if canSend, reason := common.CanSendPacket(); !canSend { + common.LogError(fmt.Sprintf("SNMP请求 %s 受限: %s", target, reason)) + return false + } + conn, err := net.DialTimeout("udp", target, time.Duration(common.Timeout)*time.Second) if err != nil { return false diff --git a/plugins/services/ssh.go b/plugins/services/ssh.go index 3bf5c1a..7e05389 100644 --- a/plugins/services/ssh.go +++ b/plugins/services/ssh.go @@ -164,6 +164,12 @@ func (p *SSHPlugin) testCredential(ctx context.Context, info *common.HostInfo, c err error } + // 检查发包限制 + if canSend, reason := common.CanSendPacket(); !canSend { + common.LogError(fmt.Sprintf("SSH连接 %s 受限: %s", target, reason)) + return nil + } + resultChan := make(chan sshResult, 1) go func() { client, err := ssh.Dial("tcp", target, config) @@ -201,6 +207,16 @@ func (p *SSHPlugin) executeCommand(client *ssh.Client, cmd string) (string, erro func (p *SSHPlugin) identifyService(info *common.HostInfo) *ScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + // 检查发包限制 + if canSend, reason := common.CanSendPacket(); !canSend { + common.LogError(fmt.Sprintf("SSH服务识别 %s 受限: %s", target, reason)) + return &ScanResult{ + Success: false, + Service: "ssh", + Error: fmt.Errorf("发包受限: %s", reason), + } + } + // 尝试连接获取SSH Banner conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) if err != nil { diff --git a/webscan/lib/Eval.go b/webscan/lib/Eval.go index d61801c..95a0043 100644 --- a/webscan/lib/Eval.go +++ b/webscan/lib/Eval.go @@ -675,6 +675,12 @@ func DoRequest(req *http.Request, redirect bool) (*Response, error) { } // 执行请求 + // 检查发包限制 + if canSend, reason := common.CanSendPacket(); !canSend { + common.LogError(fmt.Sprintf("POC HTTP请求 %s 受限: %s", req.URL.String(), reason)) + return nil, fmt.Errorf("发包受限: %s", reason) + } + var ( oResp *http.Response err error