github.com/chainreactors/fingers@v1.2.1/cmd/test/main.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "flag" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "os" 10 "strings" 11 "time" 12 13 "github.com/chainreactors/fingers" 14 "github.com/chainreactors/fingers/alias" 15 "github.com/chainreactors/fingers/common" 16 "gopkg.in/yaml.v3" 17 ) 18 19 func main() { 20 var ( 21 aliasFile = flag.String("alias", "", "Path to alias file") 22 target = flag.String("target", "", "Target URL or address to test (overrides alias targets if provided)") 23 aliasName = flag.String("name", "", "Filter to test only specific alias by name") 24 timeout = flag.Int("timeout", 10, "Request timeout in seconds") 25 verbose = flag.Bool("verbose", false, "Enable verbose output") 26 detectAll = flag.Bool("detect-all", false, "Run general fingerprint detection on target") 27 help = flag.Bool("help", false, "Show help information") 28 ) 29 30 flag.Parse() 31 32 if *help { 33 showHelp() 34 return 35 } 36 37 if *target == "" && *aliasFile == "" { 38 fmt.Println("Error: At least one of -target or -alias is required") 39 showHelp() 40 os.Exit(1) 41 } 42 43 if *detectAll && *target == "" { 44 fmt.Println("Error: -detect-all requires -target to be specified") 45 os.Exit(1) 46 } 47 48 // Initialize fingerprint engine 49 eng, err := fingers.NewEngine() 50 if err != nil { 51 fmt.Printf("Error initializing engine: %s\n", err.Error()) 52 os.Exit(1) 53 } 54 55 if *target != "" { 56 fmt.Printf("🎯 Testing target: %s\n", *target) 57 } 58 59 // If detect-all flag is set, run general detection 60 if *detectAll { 61 fmt.Println("🔍 Running general fingerprint detection...") 62 allResults := runGeneralDetection(eng, *target, *timeout, *verbose) 63 64 fmt.Printf("📊 General Detection Results:\n") 65 if len(allResults) > 0 { 66 fmt.Printf(" Found %d fingerprints:\n", len(allResults)) 67 for _, result := range allResults { 68 fmt.Printf(" 🎯 %s (%s) - %s\n", result.Name, result.From, result.Version) 69 } 70 } else { 71 fmt.Println(" ❌ No fingerprints detected") 72 } 73 fmt.Println() 74 } 75 76 // Load and test aliases if alias file provided 77 if *aliasFile != "" { 78 aliases, err := loadAliasFile(*aliasFile) 79 if err != nil { 80 fmt.Printf("Error loading alias file: %s\n", err.Error()) 81 os.Exit(1) 82 } 83 84 fmt.Printf("📁 Loaded %d aliases from %s\n", len(aliases), *aliasFile) 85 if *aliasName != "" { 86 fmt.Printf("🎯 Filtering for alias: %s\n", *aliasName) 87 } 88 if *target != "" { 89 fmt.Printf("🔄 Using target override: %s\n", *target) 90 } 91 fmt.Println() 92 testAliasMatching(eng, *target, aliases, *aliasName, *timeout, *verbose) 93 } 94 } 95 96 func runGeneralDetection(eng *fingers.Engine, target string, timeout int, verbose bool) []common.Framework { 97 // Create HTTP client with timeout 98 client := &http.Client{ 99 Timeout: time.Duration(timeout) * time.Second, 100 } 101 102 // Ensure target has protocol 103 if !strings.HasPrefix(target, "http://") && !strings.HasPrefix(target, "https://") { 104 target = "http://" + target 105 } 106 107 if verbose { 108 fmt.Printf(" 🌐 Requesting: %s\n", target) 109 } 110 111 // Make HTTP request 112 resp, err := client.Get(target) 113 if err != nil { 114 if verbose { 115 fmt.Printf(" ❌ Request failed: %s\n", err.Error()) 116 } 117 return nil 118 } 119 defer resp.Body.Close() 120 121 // Detect frameworks 122 frameworks, err := eng.DetectResponse(resp) 123 if err != nil { 124 if verbose { 125 fmt.Printf(" ❌ Detection failed: %s\n", err.Error()) 126 } 127 return nil 128 } 129 130 // Convert map to slice 131 var results []common.Framework 132 for _, framework := range frameworks { 133 results = append(results, *framework) 134 } 135 136 return results 137 } 138 139 func testAliasMatching(eng *fingers.Engine, target string, aliases []alias.Alias, filterName string, timeout int, verbose bool) { 140 // Test each alias against the target 141 var totalTests, successfulMatches int 142 for _, aliasEntry := range aliases { 143 // Filter by name if specified 144 if filterName != "" && aliasEntry.Name != filterName { 145 if verbose { 146 fmt.Printf("⏭️ Skipping %s - name filter doesn't match\n", aliasEntry.Name) 147 } 148 continue 149 } 150 151 // Determine target to test 152 testTarget := target 153 if target == "" { 154 // Use alias targets if no target override provided 155 if len(aliasEntry.Link) == 0 { 156 if verbose { 157 fmt.Printf("⏭️ Skipping %s - no target URLs defined\n", aliasEntry.Name) 158 } 159 continue 160 } 161 // Use first target from alias 162 testTarget = aliasEntry.Link[0] 163 } 164 165 totalTests++ 166 fmt.Printf("🔍 Testing alias: %s (priority: %d)\n", aliasEntry.Name, aliasEntry.Priority) 167 168 // Show target being used 169 if target != "" { 170 fmt.Printf(" 🎯 Using override target: %s\n", testTarget) 171 } else { 172 fmt.Printf(" 🎯 Using alias target: %s\n", testTarget) 173 } 174 175 // Test fingerprint detection 176 results := testFingerprintDetection(eng, testTarget, aliasEntry, timeout, verbose) 177 178 if len(results) > 0 { 179 successfulMatches++ 180 fmt.Printf(" ✅ Found %d matching fingerprints:\n", len(results)) 181 for _, result := range results { 182 fmt.Printf(" 🎯 %s (%s) - %s\n", result.Name, result.From, result.Version) 183 } 184 } else { 185 fmt.Printf(" ❌ No matching fingerprints detected\n") 186 } 187 fmt.Println() 188 } 189 190 // Summary 191 fmt.Printf("📊 Alias Testing Summary:\n") 192 fmt.Printf(" Total aliases tested: %d\n", totalTests) 193 fmt.Printf(" Successful matches: %d\n", successfulMatches) 194 if totalTests > 0 { 195 fmt.Printf(" Success rate: %.1f%%\n", float64(successfulMatches)/float64(totalTests)*100) 196 } 197 } 198 199 func showHelp() { 200 fmt.Println("Fingers Alias Tester") 201 fmt.Println() 202 fmt.Println("Usage:") 203 fmt.Println(" test [options]") 204 fmt.Println() 205 fmt.Println("Options:") 206 fmt.Println(" -alias string") 207 fmt.Println(" Path to alias YAML file") 208 fmt.Println(" -target string") 209 fmt.Println(" Target URL or address to test (overrides alias targets if provided)") 210 fmt.Println(" -name string") 211 fmt.Println(" Filter to test only specific alias by name") 212 fmt.Println(" -detect-all") 213 fmt.Println(" Run general fingerprint detection on target") 214 fmt.Println(" -timeout int") 215 fmt.Println(" Request timeout in seconds (default 10)") 216 fmt.Println(" -verbose") 217 fmt.Println(" Enable verbose output") 218 fmt.Println(" -help") 219 fmt.Println(" Show this help information") 220 fmt.Println() 221 fmt.Println("Examples:") 222 fmt.Println(" # Run general fingerprint detection") 223 fmt.Println(" test -target https://nginx.org -detect-all") 224 fmt.Println() 225 fmt.Println(" # Test specific alias with override target") 226 fmt.Println(" test -alias aliases.yaml -name nginx_test -target https://custom-nginx.com") 227 fmt.Println() 228 fmt.Println(" # Test all aliases using their defined targets") 229 fmt.Println(" test -alias aliases.yaml") 230 fmt.Println() 231 fmt.Println(" # Test specific alias using its defined target") 232 fmt.Println(" test -alias aliases.yaml -name github_test -verbose") 233 } 234 235 func loadAliasFile(filename string) ([]alias.Alias, error) { 236 content, err := ioutil.ReadFile(filename) 237 if err != nil { 238 return nil, fmt.Errorf("failed to read file: %w", err) 239 } 240 241 var aliases []alias.Alias 242 if strings.HasSuffix(filename, ".json") { 243 err = json.Unmarshal(content, &aliases) 244 } else { 245 err = yaml.Unmarshal(content, &aliases) 246 } 247 248 if err != nil { 249 return nil, fmt.Errorf("failed to parse file: %w", err) 250 } 251 252 return aliases, nil 253 } 254 255 func matchesTarget(currentTarget, aliasTarget string) bool { 256 // Simple matching logic - can be enhanced 257 // Extract domain/IP from URLs for comparison 258 currentClean := extractHostFromTarget(currentTarget) 259 aliasClean := extractHostFromTarget(aliasTarget) 260 261 return strings.EqualFold(currentClean, aliasClean) || 262 strings.Contains(strings.ToLower(currentTarget), strings.ToLower(aliasTarget)) || 263 strings.Contains(strings.ToLower(aliasTarget), strings.ToLower(currentTarget)) 264 } 265 266 func extractHostFromTarget(target string) string { 267 // Remove protocol if present 268 if strings.HasPrefix(target, "http://") { 269 target = target[7:] 270 } else if strings.HasPrefix(target, "https://") { 271 target = target[8:] 272 } 273 274 // Remove path if present 275 if idx := strings.Index(target, "/"); idx != -1 { 276 target = target[:idx] 277 } 278 279 return target 280 } 281 282 func testFingerprintDetection(eng *fingers.Engine, target string, aliasEntry alias.Alias, timeout int, verbose bool) []common.Framework { 283 // Create HTTP client with timeout 284 client := &http.Client{ 285 Timeout: time.Duration(timeout) * time.Second, 286 } 287 288 // Ensure target has protocol 289 if !strings.HasPrefix(target, "http://") && !strings.HasPrefix(target, "https://") { 290 target = "http://" + target 291 } 292 293 if verbose { 294 fmt.Printf(" 🌐 Requesting: %s\n", target) 295 } 296 297 // Make HTTP request 298 resp, err := client.Get(target) 299 if err != nil { 300 if verbose { 301 fmt.Printf(" ❌ Request failed: %s\n", err.Error()) 302 } 303 return nil 304 } 305 defer resp.Body.Close() 306 307 // Detect frameworks 308 frameworks, err := eng.DetectResponse(resp) 309 if err != nil { 310 if verbose { 311 fmt.Printf(" ❌ Detection failed: %s\n", err.Error()) 312 } 313 return nil 314 } 315 316 // Filter results based on alias mappings 317 var matchingFrameworks []common.Framework 318 for _, framework := range frameworks { 319 if isExpectedFramework(*framework, aliasEntry) { 320 matchingFrameworks = append(matchingFrameworks, *framework) 321 } 322 } 323 324 return matchingFrameworks 325 } 326 327 func isExpectedFramework(framework common.Framework, aliasEntry alias.Alias) bool { 328 // Check if this framework matches any of the expected aliases 329 engineName := framework.From.String() 330 331 if expectedNames, exists := aliasEntry.AliasMap[engineName]; exists { 332 for _, expectedName := range expectedNames { 333 if strings.EqualFold(framework.Name, expectedName) || 334 strings.Contains(strings.ToLower(framework.Name), strings.ToLower(expectedName)) { 335 return true 336 } 337 } 338 } 339 340 return false 341 }