github.com/chainreactors/fingers@v1.2.1/fingers/ac_test.go (about) 1 package fingers 2 3 import ( 4 "testing" 5 6 "github.com/chainreactors/fingers/resources" 7 ) 8 9 func TestKeywordIndex_FastPath(t *testing.T) { 10 httpfs, err := LoadFingers(resources.FingersHTTPData) 11 if err != nil { 12 t.Fatalf("Failed to load HTTP fingers: %v", err) 13 } 14 for _, f := range httpfs { 15 f.Compile(false) 16 } 17 18 idx := NewKeywordIndex(httpfs) 19 20 total := len(httpfs) 21 fastCount := len(idx.fastPath) 22 23 t.Logf("Total HTTP fingers: %d", total) 24 t.Logf("Fast-path fingers: %d (%.1f%%)", fastCount, float64(fastCount)/float64(total)*100) 25 t.Logf("Slow-path fingers: %d (%.1f%%)", total-fastCount, float64(total-fastCount)/float64(total)*100) 26 27 if fastCount == 0 { 28 t.Error("No fast-path fingers found — optimization provides no benefit") 29 } 30 } 31 32 func TestACPassiveMatch_Consistency(t *testing.T) { 33 httpfs, err := LoadFingers(resources.FingersHTTPData) 34 if err != nil { 35 t.Fatalf("Failed to load HTTP fingers: %v", err) 36 } 37 for _, f := range httpfs { 38 f.Compile(false) 39 } 40 41 idx := NewKeywordIndex(httpfs) 42 43 responses := []struct { 44 name string 45 raw string 46 }{ 47 { 48 "nginx", 49 "HTTP/1.1 200 OK\r\nServer: nginx/1.18.0\r\nContent-Type: text/html\r\n\r\n<html><body>Welcome to nginx!</body></html>", 50 }, 51 { 52 "apache", 53 "HTTP/1.1 200 OK\r\nServer: Apache/2.4.41\r\nContent-Type: text/html\r\n\r\n<html><body>It works!</body></html>", 54 }, 55 { 56 "wordpress", 57 "HTTP/1.1 200 OK\r\nServer: nginx\r\nContent-Type: text/html\r\n\r\n<html><head><meta name=\"generator\" content=\"WordPress 5.8\" /></head><body>Blog</body></html>", 58 }, 59 { 60 "empty", 61 "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<html><body>Hello</body></html>", 62 }, 63 } 64 65 for _, tc := range responses { 66 t.Run(tc.name, func(t *testing.T) { 67 input := NewContent([]byte(tc.raw), "", true) 68 69 baseFrames, baseVulns := httpfs.PassiveMatch(input, false) 70 acFrames, acVulns := httpfs.ACPassiveMatch(input, idx, false) 71 72 if len(baseFrames) != len(acFrames) { 73 t.Errorf("Framework count: baseline=%d ac=%d", len(baseFrames), len(acFrames)) 74 t.Logf("Baseline: %v", baseFrames) 75 t.Logf("AC: %v", acFrames) 76 return 77 } 78 79 for name := range baseFrames { 80 if _, ok := acFrames[name]; !ok { 81 t.Errorf("Missing in AC result: %s", name) 82 } 83 } 84 for name := range acFrames { 85 if _, ok := baseFrames[name]; !ok { 86 t.Errorf("Extra in AC result: %s", name) 87 } 88 } 89 90 t.Logf("Matched %d frameworks, %d vulns", len(acFrames), len(acVulns)) 91 _ = baseVulns 92 }) 93 } 94 } 95 96 func make1MBResponse() []byte { 97 header := "HTTP/1.1 200 OK\r\nServer: nginx/1.18.0\r\nContent-Type: text/html\r\n\r\n" 98 bodySize := 1*1024*1024 - len(header) 99 body := make([]byte, bodySize) 100 for i := range body { 101 body[i] = 'a' + byte(i%26) 102 } 103 return append([]byte(header), body...) 104 } 105 106 func BenchmarkPassiveMatch_1MB_Baseline(b *testing.B) { 107 httpfs, _ := LoadFingers(resources.FingersHTTPData) 108 for _, f := range httpfs { 109 f.Compile(false) 110 } 111 input := NewContent(make1MBResponse(), "", true) 112 b.SetBytes(int64(len(input.Content))) 113 b.ResetTimer() 114 for i := 0; i < b.N; i++ { 115 httpfs.PassiveMatch(input, false) 116 } 117 } 118 119 func BenchmarkPassiveMatch_1MB_WithAC(b *testing.B) { 120 httpfs, _ := LoadFingers(resources.FingersHTTPData) 121 for _, f := range httpfs { 122 f.Compile(false) 123 } 124 idx := NewKeywordIndex(httpfs) 125 input := NewContent(make1MBResponse(), "", true) 126 b.SetBytes(int64(len(input.Content))) 127 b.ResetTimer() 128 for i := 0; i < b.N; i++ { 129 httpfs.ACPassiveMatch(input, idx, false) 130 } 131 } 132 133 // 只收集有正则的 finger,对比正则暴力匹配 vs AC 预过滤 134 func filterRegexFingers(fs Fingers) Fingers { 135 var out Fingers 136 for _, f := range fs { 137 for _, rule := range f.Rules { 138 if rule.Regexps != nil && (len(rule.Regexps.CompliedRegexp) > 0 || len(rule.Regexps.CompiledVulnRegexp) > 0) { 139 out = append(out, f) 140 break 141 } 142 } 143 } 144 return out 145 } 146 147 func BenchmarkRegexMatch_1MB_Baseline(b *testing.B) { 148 httpfs, _ := LoadFingers(resources.FingersHTTPData) 149 for _, f := range httpfs { 150 f.Compile(false) 151 } 152 regexFs := filterRegexFingers(httpfs) 153 input := NewContent(make1MBResponse(), "", true) 154 b.Logf("Regex fingers: %d / %d total", len(regexFs), len(httpfs)) 155 b.SetBytes(int64(len(input.Content))) 156 b.ResetTimer() 157 for i := 0; i < b.N; i++ { 158 regexFs.PassiveMatch(input, false) 159 } 160 } 161 162 func BenchmarkRegexMatch_1MB_WithAC(b *testing.B) { 163 httpfs, _ := LoadFingers(resources.FingersHTTPData) 164 for _, f := range httpfs { 165 f.Compile(false) 166 } 167 regexFs := filterRegexFingers(httpfs) 168 idx := NewKeywordIndex(regexFs) 169 input := NewContent(make1MBResponse(), "", true) 170 b.Logf("Regex fingers: %d / %d total, fast-path: %d", len(regexFs), len(httpfs), len(idx.fastPath)) 171 b.SetBytes(int64(len(input.Content))) 172 b.ResetTimer() 173 for i := 0; i < b.N; i++ { 174 regexFs.ACPassiveMatch(input, idx, false) 175 } 176 } 177 178 func BenchmarkPassiveMatch_Baseline(b *testing.B) { 179 httpfs, err := LoadFingers(resources.FingersHTTPData) 180 if err != nil { 181 b.Fatalf("Failed to load: %v", err) 182 } 183 for _, f := range httpfs { 184 f.Compile(false) 185 } 186 187 input := NewContent([]byte("HTTP/1.1 200 OK\r\nServer: nginx/1.18.0\r\nContent-Type: text/html\r\n\r\n<html><head><meta name=\"generator\" content=\"WordPress 5.8\" /></head><body>Blog</body></html>"), "", true) 188 189 b.ResetTimer() 190 for i := 0; i < b.N; i++ { 191 httpfs.PassiveMatch(input, false) 192 } 193 } 194 195 func BenchmarkPassiveMatch_WithAC(b *testing.B) { 196 httpfs, err := LoadFingers(resources.FingersHTTPData) 197 if err != nil { 198 b.Fatalf("Failed to load: %v", err) 199 } 200 for _, f := range httpfs { 201 f.Compile(false) 202 } 203 idx := NewKeywordIndex(httpfs) 204 205 input := NewContent([]byte("HTTP/1.1 200 OK\r\nServer: nginx/1.18.0\r\nContent-Type: text/html\r\n\r\n<html><head><meta name=\"generator\" content=\"WordPress 5.8\" /></head><body>Blog</body></html>"), "", true) 206 207 b.ResetTimer() 208 for i := 0; i < b.N; i++ { 209 httpfs.ACPassiveMatch(input, idx, false) 210 } 211 }