mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00
feat: 新增发包频率控制功能
- 添加-rate参数:控制每分钟最大发包次数 - 添加-maxpkts参数:控制整个程序最大发包总数 - 在所有网络操作点集成发包限制检查 - 支持端口扫描、Web检测、服务插件、POC扫描等场景 - 默认不限制,保持向后兼容性
This commit is contained in:
parent
a36767c158
commit
d19abcac36
@ -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"))
|
||||
|
||||
// ═════════════════════════════════════════════════
|
||||
// 输出与显示控制参数
|
||||
// ═════════════════════════════════════════════════
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user