mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 19:21:52 +08:00
476 lines
12 KiB
Go
476 lines
12 KiB
Go
package output
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
/*
|
|
buffer_test.go - ResultBuffer 高价值测试
|
|
|
|
测试重点:
|
|
1. 去重逻辑 - 不同结果类型的去重策略差异
|
|
2. 完整度评分 - 决定是否替换已有服务记录
|
|
3. 并发安全 - 多goroutine同时Add
|
|
*/
|
|
|
|
// =============================================================================
|
|
// 基本去重测试
|
|
// =============================================================================
|
|
|
|
// TestResultBuffer_HostDeduplication 测试主机去重
|
|
func TestResultBuffer_HostDeduplication(t *testing.T) {
|
|
buf := NewResultBuffer()
|
|
|
|
// 添加相同主机多次
|
|
for i := 0; i < 10; i++ {
|
|
buf.Add(&ScanResult{
|
|
Type: TypeHost,
|
|
Target: "192.168.1.1",
|
|
Status: "alive",
|
|
})
|
|
}
|
|
|
|
hosts, _, _, _ := buf.Summary()
|
|
if hosts != 1 {
|
|
t.Errorf("主机应去重为1个,实际 %d", hosts)
|
|
}
|
|
}
|
|
|
|
// TestResultBuffer_PortDeduplication 测试端口去重
|
|
func TestResultBuffer_PortDeduplication(t *testing.T) {
|
|
buf := NewResultBuffer()
|
|
|
|
// 相同IP:Port应去重
|
|
for i := 0; i < 5; i++ {
|
|
buf.Add(&ScanResult{
|
|
Type: TypePort,
|
|
Target: "192.168.1.1",
|
|
Details: map[string]interface{}{"port": 80},
|
|
})
|
|
}
|
|
|
|
// 不同端口不去重
|
|
buf.Add(&ScanResult{
|
|
Type: TypePort,
|
|
Target: "192.168.1.1",
|
|
Details: map[string]interface{}{"port": 443},
|
|
})
|
|
|
|
_, ports, _, _ := buf.Summary()
|
|
if ports != 2 {
|
|
t.Errorf("端口应有2个(80和443),实际 %d", ports)
|
|
}
|
|
}
|
|
|
|
// TestResultBuffer_ServiceDeduplication 测试服务去重
|
|
func TestResultBuffer_ServiceDeduplication(t *testing.T) {
|
|
buf := NewResultBuffer()
|
|
|
|
// 相同Target的服务应去重
|
|
buf.Add(&ScanResult{
|
|
Type: TypeService,
|
|
Target: "192.168.1.1:80",
|
|
Status: "http",
|
|
})
|
|
buf.Add(&ScanResult{
|
|
Type: TypeService,
|
|
Target: "192.168.1.1:80",
|
|
Status: "nginx",
|
|
})
|
|
|
|
_, _, services, _ := buf.Summary()
|
|
if services != 1 {
|
|
t.Errorf("相同Target的服务应去重为1个,实际 %d", services)
|
|
}
|
|
}
|
|
|
|
// TestResultBuffer_VulnDeduplication 测试漏洞去重
|
|
func TestResultBuffer_VulnDeduplication(t *testing.T) {
|
|
buf := NewResultBuffer()
|
|
|
|
// 相同Target+Status的漏洞应去重
|
|
for i := 0; i < 3; i++ {
|
|
buf.Add(&ScanResult{
|
|
Type: TypeVuln,
|
|
Target: "192.168.1.1:445",
|
|
Status: "MS17-010",
|
|
})
|
|
}
|
|
|
|
// 不同漏洞不去重
|
|
buf.Add(&ScanResult{
|
|
Type: TypeVuln,
|
|
Target: "192.168.1.1:445",
|
|
Status: "CVE-2020-0796",
|
|
})
|
|
|
|
_, _, _, vulns := buf.Summary()
|
|
if vulns != 2 {
|
|
t.Errorf("漏洞应有2个,实际 %d", vulns)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// 完整度评分测试
|
|
// =============================================================================
|
|
|
|
// TestResultBuffer_CompletenessScore 测试完整度评分
|
|
func TestResultBuffer_CompletenessScore(t *testing.T) {
|
|
buf := NewResultBuffer()
|
|
|
|
tests := []struct {
|
|
name string
|
|
result *ScanResult
|
|
expectedScore int
|
|
}{
|
|
{
|
|
name: "空Details",
|
|
result: &ScanResult{Details: nil},
|
|
expectedScore: 0,
|
|
},
|
|
{
|
|
name: "只有status",
|
|
result: &ScanResult{Details: map[string]interface{}{"status": 200}},
|
|
expectedScore: 2,
|
|
},
|
|
{
|
|
name: "有server",
|
|
result: &ScanResult{Details: map[string]interface{}{"server": "nginx/1.18.0"}},
|
|
expectedScore: 2,
|
|
},
|
|
{
|
|
name: "有title",
|
|
result: &ScanResult{Details: map[string]interface{}{"title": "Welcome"}},
|
|
expectedScore: 1,
|
|
},
|
|
{
|
|
name: "有指纹-[]string",
|
|
result: &ScanResult{Details: map[string]interface{}{"fingerprints": []string{"nginx"}}},
|
|
expectedScore: 3,
|
|
},
|
|
{
|
|
name: "有指纹-[]interface{}",
|
|
result: &ScanResult{Details: map[string]interface{}{"fingerprints": []interface{}{"apache", "php"}}},
|
|
expectedScore: 3,
|
|
},
|
|
{
|
|
name: "有banner",
|
|
result: &ScanResult{Details: map[string]interface{}{"banner": "SSH-2.0-OpenSSH"}},
|
|
expectedScore: 1,
|
|
},
|
|
{
|
|
name: "完整记录",
|
|
result: &ScanResult{
|
|
Details: map[string]interface{}{
|
|
"status": 200,
|
|
"server": "nginx",
|
|
"title": "Home",
|
|
"fingerprints": []string{"nginx", "php"},
|
|
"banner": "test",
|
|
},
|
|
},
|
|
expectedScore: 9, // 2+2+1+3+1
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
score := buf.CalculateCompleteness(tt.result)
|
|
if score != tt.expectedScore {
|
|
t.Errorf("完整度评分 = %d, 期望 %d", score, tt.expectedScore)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestResultBuffer_ServiceUpdate 测试服务记录更新
|
|
//
|
|
// 当新记录比旧记录更完整时,应该替换
|
|
func TestResultBuffer_ServiceUpdate(t *testing.T) {
|
|
buf := NewResultBuffer()
|
|
|
|
// 先添加简单记录
|
|
buf.Add(&ScanResult{
|
|
Type: TypeService,
|
|
Target: "192.168.1.1:80",
|
|
Status: "http",
|
|
Details: map[string]interface{}{},
|
|
})
|
|
|
|
// 再添加更完整的记录
|
|
buf.Add(&ScanResult{
|
|
Type: TypeService,
|
|
Target: "192.168.1.1:80",
|
|
Status: "http",
|
|
Details: map[string]interface{}{
|
|
"status": 200,
|
|
"server": "nginx/1.18.0",
|
|
"title": "Welcome",
|
|
"fingerprints": []string{"nginx", "php"},
|
|
},
|
|
})
|
|
|
|
_, _, services, _ := buf.Summary()
|
|
if services != 1 {
|
|
t.Fatal("服务数量应为1")
|
|
}
|
|
|
|
// 验证是更完整的记录
|
|
if buf.ServiceResults[0].Details == nil {
|
|
t.Fatal("Details不应为nil")
|
|
}
|
|
if buf.ServiceResults[0].Details["server"] != "nginx/1.18.0" {
|
|
t.Error("应保留更完整的记录")
|
|
}
|
|
}
|
|
|
|
func TestResultBuffer_ServiceUpdateMergesDetails(t *testing.T) {
|
|
buf := NewResultBuffer()
|
|
|
|
buf.Add(&ScanResult{
|
|
Type: TypeService,
|
|
Target: "192.168.1.1:80",
|
|
Status: "identified",
|
|
Details: map[string]interface{}{
|
|
"service": "http",
|
|
"banner": "HTTP/1.1 200 OK",
|
|
},
|
|
})
|
|
buf.Add(&ScanResult{
|
|
Type: TypeService,
|
|
Target: "192.168.1.1:80",
|
|
Status: "web",
|
|
Details: map[string]interface{}{
|
|
"title": "Home",
|
|
"status": 200,
|
|
"server": "nginx",
|
|
},
|
|
})
|
|
|
|
if len(buf.ServiceResults) != 1 {
|
|
t.Fatalf("期望1条服务记录,实际 %d", len(buf.ServiceResults))
|
|
}
|
|
details := buf.ServiceResults[0].Details
|
|
for _, key := range []string{"service", "banner", "title", "status", "server"} {
|
|
if _, ok := details[key]; !ok {
|
|
t.Errorf("合并后的服务记录缺少字段 %q: %#v", key, details)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestResultBuffer_ServiceNoDowngrade 测试不降级服务记录
|
|
//
|
|
// 当新记录不如旧记录完整时,不应替换
|
|
func TestResultBuffer_ServiceNoDowngrade(t *testing.T) {
|
|
buf := NewResultBuffer()
|
|
|
|
// 先添加完整记录
|
|
buf.Add(&ScanResult{
|
|
Type: TypeService,
|
|
Target: "192.168.1.1:80",
|
|
Status: "http",
|
|
Details: map[string]interface{}{
|
|
"status": 200,
|
|
"server": "nginx/1.18.0",
|
|
"fingerprints": []string{"nginx"},
|
|
},
|
|
})
|
|
|
|
// 再添加简单记录
|
|
buf.Add(&ScanResult{
|
|
Type: TypeService,
|
|
Target: "192.168.1.1:80",
|
|
Status: "http",
|
|
Details: map[string]interface{}{},
|
|
})
|
|
|
|
// 验证仍保留完整记录
|
|
if buf.ServiceResults[0].Details["server"] != "nginx/1.18.0" {
|
|
t.Error("不应降级到不完整的记录")
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// 并发安全测试
|
|
// =============================================================================
|
|
|
|
// TestResultBuffer_ConcurrentAdd 测试并发添加
|
|
func TestResultBuffer_ConcurrentAdd(t *testing.T) {
|
|
buf := NewResultBuffer()
|
|
|
|
const goroutines = 100
|
|
const resultsPerGoroutine = 100
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(goroutines)
|
|
|
|
for i := 0; i < goroutines; i++ {
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
for j := 0; j < resultsPerGoroutine; j++ {
|
|
// 每个goroutine添加不同类型的结果
|
|
switch j % 4 {
|
|
case 0:
|
|
buf.Add(&ScanResult{
|
|
Type: TypeHost,
|
|
Target: fmt.Sprintf("192.168.%d.%d", id, j),
|
|
})
|
|
case 1:
|
|
buf.Add(&ScanResult{
|
|
Type: TypePort,
|
|
Target: fmt.Sprintf("192.168.%d.%d", id, j),
|
|
Details: map[string]interface{}{"port": j},
|
|
})
|
|
case 2:
|
|
buf.Add(&ScanResult{
|
|
Type: TypeService,
|
|
Target: fmt.Sprintf("192.168.%d.%d:%d", id, j, j),
|
|
})
|
|
case 3:
|
|
buf.Add(&ScanResult{
|
|
Type: TypeVuln,
|
|
Target: fmt.Sprintf("192.168.%d.%d", id, j),
|
|
Status: fmt.Sprintf("CVE-%d", j),
|
|
})
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// 验证没有panic,数据完整
|
|
hosts, ports, services, vulns := buf.Summary()
|
|
total := hosts + ports + services + vulns
|
|
|
|
if total == 0 {
|
|
t.Error("并发添加后应有结果")
|
|
}
|
|
|
|
t.Logf("并发测试完成: %d hosts, %d ports, %d services, %d vulns",
|
|
hosts, ports, services, vulns)
|
|
}
|
|
|
|
// TestResultBuffer_ConcurrentSummary 测试并发获取摘要
|
|
func TestResultBuffer_ConcurrentSummary(t *testing.T) {
|
|
buf := NewResultBuffer()
|
|
|
|
// 预填充一些数据
|
|
for i := 0; i < 100; i++ {
|
|
buf.Add(&ScanResult{
|
|
Type: TypeHost,
|
|
Target: fmt.Sprintf("192.168.1.%d", i),
|
|
})
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(100)
|
|
|
|
for i := 0; i < 100; i++ {
|
|
go func() {
|
|
defer wg.Done()
|
|
// 同时获取摘要和添加
|
|
buf.Summary()
|
|
buf.Add(&ScanResult{
|
|
Type: TypeHost,
|
|
Target: "10.0.0.1",
|
|
})
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
// 没有panic即为成功
|
|
}
|
|
|
|
// =============================================================================
|
|
// 边界情况测试
|
|
// =============================================================================
|
|
|
|
// TestResultBuffer_NilResult 测试nil结果
|
|
func TestResultBuffer_NilResult(t *testing.T) {
|
|
buf := NewResultBuffer()
|
|
buf.Add(nil) // 不应panic
|
|
|
|
hosts, ports, services, vulns := buf.Summary()
|
|
if hosts+ports+services+vulns != 0 {
|
|
t.Error("添加nil后应无结果")
|
|
}
|
|
}
|
|
|
|
// TestResultBuffer_PortWithoutDetails 测试无Details的端口
|
|
func TestResultBuffer_PortWithoutDetails(t *testing.T) {
|
|
buf := NewResultBuffer()
|
|
|
|
buf.Add(&ScanResult{
|
|
Type: TypePort,
|
|
Target: "192.168.1.1",
|
|
Details: nil,
|
|
})
|
|
|
|
_, ports, _, _ := buf.Summary()
|
|
if ports != 1 {
|
|
t.Error("无Details的端口也应被添加")
|
|
}
|
|
}
|
|
|
|
// TestResultBuffer_Clear 测试清空
|
|
func TestResultBuffer_Clear(t *testing.T) {
|
|
buf := NewResultBuffer()
|
|
|
|
// 添加各类结果
|
|
buf.Add(&ScanResult{Type: TypeHost, Target: "192.168.1.1"})
|
|
buf.Add(&ScanResult{Type: TypePort, Target: "192.168.1.1", Details: map[string]interface{}{"port": 80}})
|
|
buf.Add(&ScanResult{Type: TypeService, Target: "192.168.1.1:80"})
|
|
buf.Add(&ScanResult{Type: TypeVuln, Target: "192.168.1.1", Status: "CVE-2021-1234"})
|
|
|
|
// 清空
|
|
buf.Clear()
|
|
|
|
hosts, ports, services, vulns := buf.Summary()
|
|
if hosts+ports+services+vulns != 0 {
|
|
t.Error("Clear后应无结果")
|
|
}
|
|
|
|
// 验证可以继续添加
|
|
buf.Add(&ScanResult{Type: TypeHost, Target: "10.0.0.1"})
|
|
hosts, _, _, _ = buf.Summary()
|
|
if hosts != 1 {
|
|
t.Error("Clear后应能继续添加")
|
|
}
|
|
}
|
|
|
|
// TestResultBuffer_EmptyFingerprints 测试空指纹数组
|
|
func TestResultBuffer_EmptyFingerprints(t *testing.T) {
|
|
buf := NewResultBuffer()
|
|
|
|
// 空字符串数组
|
|
score1 := buf.CalculateCompleteness(&ScanResult{
|
|
Details: map[string]interface{}{"fingerprints": []string{}},
|
|
})
|
|
if score1 != 0 {
|
|
t.Errorf("空指纹数组不应加分,实际 %d", score1)
|
|
}
|
|
|
|
// 空interface数组
|
|
score2 := buf.CalculateCompleteness(&ScanResult{
|
|
Details: map[string]interface{}{"fingerprints": []interface{}{}},
|
|
})
|
|
if score2 != 0 {
|
|
t.Errorf("空interface数组不应加分,实际 %d", score2)
|
|
}
|
|
}
|
|
|
|
// TestResultBuffer_StatusZero 测试status为0
|
|
func TestResultBuffer_StatusZero(t *testing.T) {
|
|
buf := NewResultBuffer()
|
|
|
|
score := buf.CalculateCompleteness(&ScanResult{
|
|
Details: map[string]interface{}{"status": 0},
|
|
})
|
|
if score != 0 {
|
|
t.Errorf("status为0不应加分,实际 %d", score)
|
|
}
|
|
}
|