mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00
feat: 新增发包频率控制功能
- 添加-rate参数:控制每分钟最大发包次数 - 添加-maxpkts参数:控制整个程序最大发包总数 - 在所有网络操作点集成发包限制检查 - 支持端口扫描、Web检测、服务插件、POC扫描等场景 - 默认不限制,保持向后兼容性
This commit is contained in:
parent
a36767c158
commit
d19abcac36
@ -63,6 +63,10 @@ var (
|
|||||||
DownloadURL string // 下载文件的URL
|
DownloadURL string // 下载文件的URL
|
||||||
DownloadSavePath string // 下载文件保存路径
|
DownloadSavePath string // 下载文件保存路径
|
||||||
|
|
||||||
|
// 发包频率控制相关变量
|
||||||
|
PacketRateLimit int64 // 每分钟最大发包次数 (0表示不限制)
|
||||||
|
MaxPacketCount int64 // 整个程序最大发包总数 (0表示不限制)
|
||||||
|
|
||||||
// Parse.go 使用的变量
|
// Parse.go 使用的变量
|
||||||
HostPort []string
|
HostPort []string
|
||||||
URLs []string
|
URLs []string
|
||||||
@ -199,6 +203,12 @@ func Flag(Info *HostInfo) {
|
|||||||
flag.BoolVar(&DisableBrute, "nobr", false, i18n.GetText("flag_disable_brute"))
|
flag.BoolVar(&DisableBrute, "nobr", false, i18n.GetText("flag_disable_brute"))
|
||||||
flag.IntVar(&MaxRetries, "retry", 3, i18n.GetText("flag_max_retries"))
|
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
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -110,6 +112,11 @@ var (
|
|||||||
UDPPacketCount int64 // UDP包数量
|
UDPPacketCount int64 // UDP包数量
|
||||||
HTTPPacketCount int64 // HTTP请求数量
|
HTTPPacketCount int64 // HTTP请求数量
|
||||||
|
|
||||||
|
// 发包频率控制变量
|
||||||
|
PacketRateStartTime int64 // 当前分钟的开始时间戳
|
||||||
|
PacketRateCurrentCount int64 // 当前分钟内的发包数量
|
||||||
|
PacketRateMutex sync.Mutex // 发包频率控制互斥锁
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 初始化控制
|
// 初始化控制
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@ -257,3 +264,70 @@ func ResetPacketCounters() {
|
|||||||
atomic.StoreInt64(&UDPPacketCount, 0)
|
atomic.StoreInt64(&UDPPacketCount, 0)
|
||||||
atomic.StoreInt64(&HTTPPacketCount, 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: "最大重试次数",
|
LangZH: "最大重试次数",
|
||||||
LangEN: "Maximum retries",
|
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": {
|
"flag_output_file": {
|
||||||
LangZH: "输出文件",
|
LangZH: "输出文件",
|
||||||
LangEN: "Output file",
|
LangEN: "Output file",
|
||||||
|
@ -77,6 +77,12 @@ func EnhancedPortScan(hosts []string, ports string, timeout int64) []string {
|
|||||||
common.UpdateProgressBar(1)
|
common.UpdateProgressBar(1)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// 检查发包限制
|
||||||
|
if canSend, reason := common.CanSendPacket(); !canSend {
|
||||||
|
common.LogError(fmt.Sprintf("端口扫描 %s 受限: %s", addr, reason))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 连接测试 - 支持SOCKS5代理
|
// 连接测试 - 支持SOCKS5代理
|
||||||
conn, err := common.WrapperTcpWithTimeout("tcp", addr, to)
|
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("User-Agent", "fscan-web-detector/2.2")
|
||||||
req.Header.Set("Accept", "*/*")
|
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)
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -121,6 +121,12 @@ func (p *RedisPlugin) testCredential(ctx context.Context, info *common.HostInfo,
|
|||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查发包限制
|
||||||
|
if canSend, reason := common.CanSendPacket(); !canSend {
|
||||||
|
common.LogError(fmt.Sprintf("Redis连接 %s 受限: %s", target, reason))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
connChan := make(chan connResult, 1)
|
connChan := make(chan connResult, 1)
|
||||||
|
|
||||||
go func() {
|
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)
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||||
timeout := time.Duration(common.Timeout) * time.Second
|
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服务
|
// 尝试连接Redis服务
|
||||||
conn, err := net.DialTimeout("tcp", target, timeout)
|
conn, err := net.DialTimeout("tcp", target, timeout)
|
||||||
if err != nil {
|
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 {
|
func (p *SNMPPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
|
||||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
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)
|
conn, err := net.DialTimeout("udp", target, time.Duration(common.Timeout)*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
|
@ -164,6 +164,12 @@ func (p *SSHPlugin) testCredential(ctx context.Context, info *common.HostInfo, c
|
|||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查发包限制
|
||||||
|
if canSend, reason := common.CanSendPacket(); !canSend {
|
||||||
|
common.LogError(fmt.Sprintf("SSH连接 %s 受限: %s", target, reason))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
resultChan := make(chan sshResult, 1)
|
resultChan := make(chan sshResult, 1)
|
||||||
go func() {
|
go func() {
|
||||||
client, err := ssh.Dial("tcp", target, config)
|
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 {
|
func (p *SSHPlugin) identifyService(info *common.HostInfo) *ScanResult {
|
||||||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
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
|
// 尝试连接获取SSH Banner
|
||||||
conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
|
conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
|
||||||
if err != nil {
|
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 (
|
var (
|
||||||
oResp *http.Response
|
oResp *http.Response
|
||||||
err error
|
err error
|
||||||
|
Loading…
Reference in New Issue
Block a user