mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-26 21:21:53 +08:00
perf(scan): 实现启发式优化提升扫描体验
1. 端口优先级排序:高价值端口(80,443,22,3389等)优先扫描 - 用户能更快看到有意义的结果 - 不影响端口喷洒策略 2. TCP 补充探测:ICMP 响应率<10%时自动启用 - 对未响应主机用 TCP 80/443/22/445 补充探测 - 解决防火墙过滤 ICMP 导致漏检的问题
This commit is contained in:
@@ -269,6 +269,10 @@ segment_16_alive:
|
|||||||
other: "{{.Arg1}}.0.0/16 segment alive: {{.Arg2}}"
|
other: "{{.Arg1}}.0.0/16 segment alive: {{.Arg2}}"
|
||||||
segment_24_alive:
|
segment_24_alive:
|
||||||
other: "{{.Arg1}}.0/24 segment alive: {{.Arg2}}"
|
other: "{{.Arg1}}.0/24 segment alive: {{.Arg2}}"
|
||||||
|
tcp_probe_low_icmp_rate:
|
||||||
|
other: "Low ICMP response rate ({{.Arg1}}), enabling TCP supplementary probe ({{.Arg2}} hosts)"
|
||||||
|
tcp_probe_found:
|
||||||
|
other: "TCP probe found {{.Arg1}} alive hosts"
|
||||||
|
|
||||||
# ========================= Alive Scan Stats Messages =========================
|
# ========================= Alive Scan Stats Messages =========================
|
||||||
parse_target_failed:
|
parse_target_failed:
|
||||||
|
|||||||
@@ -269,6 +269,10 @@ segment_16_alive:
|
|||||||
other: "{{.Arg1}}.0.0/16 网段存活: {{.Arg2}}"
|
other: "{{.Arg1}}.0.0/16 网段存活: {{.Arg2}}"
|
||||||
segment_24_alive:
|
segment_24_alive:
|
||||||
other: "{{.Arg1}}.0/24 网段存活: {{.Arg2}}"
|
other: "{{.Arg1}}.0/24 网段存活: {{.Arg2}}"
|
||||||
|
tcp_probe_low_icmp_rate:
|
||||||
|
other: "ICMP响应率过低({{.Arg1}}),启用TCP补充探测({{.Arg2}}个主机)"
|
||||||
|
tcp_probe_found:
|
||||||
|
other: "TCP补充探测发现 {{.Arg1}} 个存活主机"
|
||||||
|
|
||||||
# ========================= 存活扫描统计消息 =========================
|
# ========================= 存活扫描统计消息 =========================
|
||||||
parse_target_failed:
|
parse_target_failed:
|
||||||
|
|||||||
+143
@@ -38,6 +38,7 @@ var pingErrorKeywords = []string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CheckLive 检测主机存活状态
|
// CheckLive 检测主机存活状态
|
||||||
|
// 支持 ICMP/Ping 探测,并在响应率过低时自动启用 TCP 补充探测
|
||||||
func CheckLive(hostslist []string, Ping bool, config *common.Config, state *common.State) []string {
|
func CheckLive(hostslist []string, Ping bool, config *common.Config, state *common.State) []string {
|
||||||
// 创建局部WaitGroup
|
// 创建局部WaitGroup
|
||||||
var livewg sync.WaitGroup
|
var livewg sync.WaitGroup
|
||||||
@@ -65,12 +66,53 @@ func CheckLive(hostslist []string, Ping bool, config *common.Config, state *comm
|
|||||||
livewg.Wait()
|
livewg.Wait()
|
||||||
close(chanHosts)
|
close(chanHosts)
|
||||||
|
|
||||||
|
// TCP 补充探测:当 ICMP/Ping 响应率过低时自动启用
|
||||||
|
// 这对防火墙过滤 ICMP 的环境特别有用
|
||||||
|
aliveHosts = tcpSupplementaryProbe(hostslist, aliveHosts, config)
|
||||||
|
|
||||||
// 输出存活统计信息
|
// 输出存活统计信息
|
||||||
printAliveStats(aliveHosts, hostslist)
|
printAliveStats(aliveHosts, hostslist)
|
||||||
|
|
||||||
return aliveHosts
|
return aliveHosts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tcpSupplementaryProbe TCP 补充探测
|
||||||
|
// 当 ICMP 响应率过低时(<10%),对未响应主机进行 TCP 探测
|
||||||
|
func tcpSupplementaryProbe(allHosts []string, aliveHosts []string, config *common.Config) []string {
|
||||||
|
totalHosts := len(allHosts)
|
||||||
|
if totalHosts == 0 {
|
||||||
|
return aliveHosts
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算 ICMP 响应率
|
||||||
|
responseRate := float64(len(aliveHosts)) / float64(totalHosts)
|
||||||
|
|
||||||
|
// 响应率高于阈值,无需补充探测
|
||||||
|
if responseRate >= tcpProbeThreshold {
|
||||||
|
return aliveHosts
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取未响应的主机
|
||||||
|
unrespondedHosts := getUnrespondedHosts(allHosts, aliveHosts)
|
||||||
|
if len(unrespondedHosts) == 0 {
|
||||||
|
return aliveHosts
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提示用户正在进行 TCP 补充探测
|
||||||
|
common.LogInfo(i18n.Tr("tcp_probe_low_icmp_rate", fmt.Sprintf("%.1f%%", responseRate*100), len(unrespondedHosts)))
|
||||||
|
|
||||||
|
// 执行 TCP 补充探测
|
||||||
|
tcpAliveHosts := runTcpProbeForHosts(unrespondedHosts, config)
|
||||||
|
|
||||||
|
// 合并结果
|
||||||
|
if len(tcpAliveHosts) > 0 {
|
||||||
|
aliveHosts = append(aliveHosts, tcpAliveHosts...)
|
||||||
|
common.LogInfo(i18n.Tr("tcp_probe_found", len(tcpAliveHosts)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return aliveHosts
|
||||||
|
}
|
||||||
|
|
||||||
// IsContain 检查切片中是否包含指定元素
|
// IsContain 检查切片中是否包含指定元素
|
||||||
func IsContain(items []string, item string) bool {
|
func IsContain(items []string, item string) bool {
|
||||||
for _, eachItem := range items {
|
for _, eachItem := range items {
|
||||||
@@ -622,3 +664,104 @@ func ArrayCountValueTop(arrInit []string, length int, flag bool) (arrTop []strin
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// TCP 补充探测 - 当 ICMP 响应率过低时自动启用
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// tcpProbeCommonPorts TCP 探测使用的常用端口
|
||||||
|
// 这些端口在大多数服务器上至少有一个开放
|
||||||
|
var tcpProbeCommonPorts = []int{80, 443, 22, 445}
|
||||||
|
|
||||||
|
// tcpProbeTimeout TCP 探测超时时间(较短,只做存活判断)
|
||||||
|
const tcpProbeTimeout = 2 * time.Second
|
||||||
|
|
||||||
|
// tcpProbeThreshold TCP 补充探测触发阈值
|
||||||
|
// 当 ICMP 响应率低于此值时,自动启用 TCP 补充探测
|
||||||
|
const tcpProbeThreshold = 0.1 // 10%
|
||||||
|
|
||||||
|
// tcpProbeAlive 使用 TCP 探测主机是否存活
|
||||||
|
// 尝试连接常用端口,任一端口响应即认为存活
|
||||||
|
func tcpProbeAlive(host string) bool {
|
||||||
|
for _, port := range tcpProbeCommonPorts {
|
||||||
|
addr := fmt.Sprintf("%s:%d", host, port)
|
||||||
|
conn, err := common.WrapperTcpWithTimeout("tcp", addr, tcpProbeTimeout)
|
||||||
|
if err == nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// runTcpProbeForHosts 对指定主机列表进行 TCP 补充探测
|
||||||
|
// 返回存活的主机列表
|
||||||
|
func runTcpProbeForHosts(hosts []string, config *common.Config) []string {
|
||||||
|
if len(hosts) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var mu sync.Mutex
|
||||||
|
aliveHosts := make([]string, 0)
|
||||||
|
|
||||||
|
// 并发控制,避免资源耗尽
|
||||||
|
concurrency := 50
|
||||||
|
if len(hosts) < concurrency {
|
||||||
|
concurrency = len(hosts)
|
||||||
|
}
|
||||||
|
limiter := make(chan struct{}, concurrency)
|
||||||
|
|
||||||
|
for _, host := range hosts {
|
||||||
|
wg.Add(1)
|
||||||
|
limiter <- struct{}{}
|
||||||
|
|
||||||
|
go func(h string) {
|
||||||
|
defer func() {
|
||||||
|
<-limiter
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if tcpProbeAlive(h) {
|
||||||
|
mu.Lock()
|
||||||
|
aliveHosts = append(aliveHosts, h)
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
// 保存结果
|
||||||
|
result := &output.ScanResult{
|
||||||
|
Time: time.Now(),
|
||||||
|
Type: output.TypeHost,
|
||||||
|
Target: h,
|
||||||
|
Status: "alive",
|
||||||
|
Details: map[string]interface{}{
|
||||||
|
"protocol": "TCP",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_ = common.SaveResult(result)
|
||||||
|
|
||||||
|
if !config.Output.Silent {
|
||||||
|
common.LogInfo(i18n.Tr("host_alive", h, "TCP"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
return aliveHosts
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUnrespondedHosts 获取未响应的主机列表
|
||||||
|
func getUnrespondedHosts(allHosts []string, aliveHosts []string) []string {
|
||||||
|
aliveSet := make(map[string]struct{}, len(aliveHosts))
|
||||||
|
for _, h := range aliveHosts {
|
||||||
|
aliveSet[h] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
unresponded := make([]string, 0, len(allHosts)-len(aliveHosts))
|
||||||
|
for _, h := range allHosts {
|
||||||
|
if _, alive := aliveSet[h]; !alive {
|
||||||
|
unresponded = append(unresponded, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unresponded
|
||||||
|
}
|
||||||
|
|||||||
+63
-2
@@ -1,9 +1,35 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// highPriorityPorts 高价值端口优先级表
|
||||||
|
// 数字越小优先级越高,用户最关心这些服务能快速出结果
|
||||||
|
var highPriorityPorts = map[int]int{
|
||||||
|
80: 1, // HTTP
|
||||||
|
443: 2, // HTTPS
|
||||||
|
22: 3, // SSH
|
||||||
|
3389: 4, // RDP
|
||||||
|
445: 5, // SMB
|
||||||
|
3306: 6, // MySQL
|
||||||
|
1433: 7, // MSSQL
|
||||||
|
6379: 8, // Redis
|
||||||
|
21: 9, // FTP
|
||||||
|
23: 10, // Telnet
|
||||||
|
8080: 11, // HTTP-Alt
|
||||||
|
8443: 12, // HTTPS-Alt
|
||||||
|
5432: 13, // PostgreSQL
|
||||||
|
27017: 14, // MongoDB
|
||||||
|
1521: 15, // Oracle
|
||||||
|
5900: 16, // VNC
|
||||||
|
25: 17, // SMTP
|
||||||
|
110: 18, // POP3
|
||||||
|
143: 19, // IMAP
|
||||||
|
53: 20, // DNS
|
||||||
|
}
|
||||||
|
|
||||||
// SocketIterator 流式生成 host:port 组合
|
// SocketIterator 流式生成 host:port 组合
|
||||||
// 设计原则:O(1) 内存,按需生成
|
// 设计原则:O(1) 内存,按需生成
|
||||||
// 使用端口喷洒策略:Port1全IP -> Port2全IP -> ...
|
// 使用端口喷洒策略:Port1全IP -> Port2全IP -> ...
|
||||||
@@ -18,15 +44,50 @@ type SocketIterator struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewSocketIterator 创建流式迭代器
|
// NewSocketIterator 创建流式迭代器
|
||||||
|
// 自动对端口进行智能排序:高价值端口优先,让用户更快看到有意义的结果
|
||||||
func NewSocketIterator(hosts []string, ports []int, exclude map[int]struct{}) *SocketIterator {
|
func NewSocketIterator(hosts []string, ports []int, exclude map[int]struct{}) *SocketIterator {
|
||||||
validPorts := filterExcludedPorts(ports, exclude)
|
validPorts := filterExcludedPorts(ports, exclude)
|
||||||
|
sortedPorts := sortPortsByPriority(validPorts)
|
||||||
return &SocketIterator{
|
return &SocketIterator{
|
||||||
hosts: hosts,
|
hosts: hosts,
|
||||||
ports: validPorts,
|
ports: sortedPorts,
|
||||||
total: len(hosts) * len(validPorts),
|
total: len(hosts) * len(sortedPorts),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sortPortsByPriority 智能排序端口
|
||||||
|
// 策略:高价值端口优先,其余按数字升序
|
||||||
|
func sortPortsByPriority(ports []int) []int {
|
||||||
|
if len(ports) <= 1 {
|
||||||
|
return ports
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]int, len(ports))
|
||||||
|
copy(result, ports)
|
||||||
|
|
||||||
|
sort.Slice(result, func(i, j int) bool {
|
||||||
|
pi, pj := result[i], result[j]
|
||||||
|
priI, okI := highPriorityPorts[pi]
|
||||||
|
priJ, okJ := highPriorityPorts[pj]
|
||||||
|
|
||||||
|
// 都有优先级:按优先级排序
|
||||||
|
if okI && okJ {
|
||||||
|
return priI < priJ
|
||||||
|
}
|
||||||
|
// 只有一个有优先级:有优先级的排前面
|
||||||
|
if okI {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if okJ {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// 都没有优先级:按端口号升序
|
||||||
|
return pi < pj
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// Next 返回下一个 host:port 组合,ok=false 表示迭代结束
|
// Next 返回下一个 host:port 组合,ok=false 表示迭代结束
|
||||||
// 端口喷洒顺序:先遍历所有IP的同一端口,再换下一个端口
|
// 端口喷洒顺序:先遍历所有IP的同一端口,再换下一个端口
|
||||||
func (it *SocketIterator) Next() (string, int, bool) {
|
func (it *SocketIterator) Next() (string, int, bool) {
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ func TestSocketIterator_ExcludePorts(t *testing.T) {
|
|||||||
|
|
||||||
it := NewSocketIterator(hosts, ports, exclude)
|
it := NewSocketIterator(hosts, ports, exclude)
|
||||||
|
|
||||||
// 应该只有22和443
|
// 应该只有22和443,按优先级排序:443(优先级2) 在 22(优先级3) 之前
|
||||||
var gotPorts []int
|
var gotPorts []int
|
||||||
for {
|
for {
|
||||||
_, port, ok := it.Next()
|
_, port, ok := it.Next()
|
||||||
@@ -140,8 +140,9 @@ func TestSocketIterator_ExcludePorts(t *testing.T) {
|
|||||||
if len(gotPorts) != 2 {
|
if len(gotPorts) != 2 {
|
||||||
t.Fatalf("期望2个端口, 实际 %d", len(gotPorts))
|
t.Fatalf("期望2个端口, 实际 %d", len(gotPorts))
|
||||||
}
|
}
|
||||||
if gotPorts[0] != 22 || gotPorts[1] != 443 {
|
// 443优先级高于22,所以443在前
|
||||||
t.Errorf("期望 [22, 443], 实际 %v", gotPorts)
|
if gotPorts[0] != 443 || gotPorts[1] != 22 {
|
||||||
|
t.Errorf("期望 [443, 22] (按优先级排序), 实际 %v", gotPorts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证Total也正确
|
// 验证Total也正确
|
||||||
@@ -181,6 +182,40 @@ func TestSocketIterator_EmptyInputs(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSocketIterator_PortPrioritySort 验证端口优先级排序
|
||||||
|
// 高价值端口(80, 443, 22等)应该排在前面
|
||||||
|
func TestSocketIterator_PortPrioritySort(t *testing.T) {
|
||||||
|
hosts := []string{"192.168.1.1"}
|
||||||
|
// 故意乱序输入,包含高优先级和普通端口
|
||||||
|
ports := []int{9999, 22, 8888, 80, 7777, 443, 3389, 1234}
|
||||||
|
|
||||||
|
it := NewSocketIterator(hosts, ports, nil)
|
||||||
|
|
||||||
|
var gotPorts []int
|
||||||
|
for {
|
||||||
|
_, port, ok := it.Next()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
gotPorts = append(gotPorts, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 期望顺序:高优先级端口按优先级排序,然后是普通端口按数字升序
|
||||||
|
// 80(优先级1), 443(2), 22(3), 3389(4), 然后 1234, 7777, 8888, 9999
|
||||||
|
expected := []int{80, 443, 22, 3389, 1234, 7777, 8888, 9999}
|
||||||
|
|
||||||
|
if len(gotPorts) != len(expected) {
|
||||||
|
t.Fatalf("端口数量不匹配: 期望 %d, 实际 %d", len(expected), len(gotPorts))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, exp := range expected {
|
||||||
|
if gotPorts[i] != exp {
|
||||||
|
t.Errorf("第%d个端口: 期望 %d, 实际 %d\n完整结果: %v", i, exp, gotPorts[i], gotPorts)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestSocketIterator_SingleElements 验证单元素情况
|
// TestSocketIterator_SingleElements 验证单元素情况
|
||||||
func TestSocketIterator_SingleElements(t *testing.T) {
|
func TestSocketIterator_SingleElements(t *testing.T) {
|
||||||
t.Run("单IP单端口", func(t *testing.T) {
|
t.Run("单IP单端口", func(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user