github.com/chainreactors/fingers@v1.2.1/xray/xray_test.go (about) 1 package xray 2 3 import ( 4 "crypto/tls" 5 "fmt" 6 "io/ioutil" 7 "net" 8 "net/http" 9 "sort" 10 "strings" 11 "sync" 12 "testing" 13 "time" 14 15 "github.com/chainreactors/fingers/resources" 16 ) 17 18 func TestScanRange(t *testing.T) { 19 if testing.Short() { 20 t.Skip("skipping live network test in short mode") 21 } 22 engine, err := NewXrayEngine(resources.XrayWebData) 23 if err != nil { 24 t.Fatalf("load engine: %v", err) 25 } 26 t.Logf("Loaded %d templates", engine.Len()) 27 28 // Generate targets 29 var targets []string 30 for i := 1; i < 255; i++ { 31 ip := fmt.Sprintf("101.132.149.%d", i) 32 targets = append(targets, fmt.Sprintf("http://%s", ip)) 33 targets = append(targets, fmt.Sprintf("https://%s", ip)) 34 } 35 36 // Probe alive 37 t.Log("Probing...") 38 alive := probeAlive(targets, 3*time.Second, 50) 39 t.Logf("Alive: %d / %d", len(alive), len(targets)) 40 if len(alive) == 0 { 41 t.Skip("No alive targets") 42 } 43 44 client := &http.Client{ 45 Timeout: 10 * time.Second, 46 Transport: &http.Transport{ 47 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 48 DisableKeepAlives: true, 49 }, 50 CheckRedirect: func(req *http.Request, via []*http.Request) error { 51 if len(via) >= 3 { 52 return http.ErrUseLastResponse 53 } 54 return nil 55 }, 56 } 57 58 type result struct { 59 URL string 60 Frames []string 61 } 62 var mu sync.Mutex 63 var results []result 64 sem := make(chan struct{}, 20) 65 var wg sync.WaitGroup 66 67 for _, target := range alive { 68 wg.Add(1) 69 sem <- struct{}{} 70 go func(url string) { 71 defer wg.Done() 72 defer func() { <-sem }() 73 74 resp, err := client.Get(url) 75 if err != nil { 76 return 77 } 78 defer resp.Body.Close() 79 body, _ := ioutil.ReadAll(resp.Body) 80 81 // Build raw HTTP response for WebMatch 82 raw := buildRawResponse(resp, body) 83 frames := engine.WebMatch(raw) 84 85 if len(frames) > 0 { 86 var names []string 87 for _, f := range frames { 88 names = append(names, f.Name) 89 } 90 sort.Strings(names) 91 mu.Lock() 92 results = append(results, result{URL: url, Frames: names}) 93 mu.Unlock() 94 } 95 }(target) 96 } 97 wg.Wait() 98 99 sort.Slice(results, func(i, j int) bool { return results[i].URL < results[j].URL }) 100 101 // Print results 102 t.Logf("\n=== Fingerprint Results (%d hits) ===", len(results)) 103 for _, r := range results { 104 t.Logf(" %-45s %s", r.URL, strings.Join(r.Frames, ", ")) 105 } 106 107 // Analyze: find fingerprints that appear on too many targets (potential FP) 108 fpCount := map[string]int{} 109 for _, r := range results { 110 for _, f := range r.Frames { 111 fpCount[f]++ 112 } 113 } 114 type kv struct { 115 k string 116 v int 117 } 118 var sorted []kv 119 for k, v := range fpCount { 120 sorted = append(sorted, kv{k, v}) 121 } 122 sort.Slice(sorted, func(i, j int) bool { return sorted[i].v > sorted[j].v }) 123 124 t.Logf("\n=== Fingerprint Frequency (potential FP if too high) ===") 125 for _, s := range sorted { 126 tag := "" 127 if s.v > len(results)/3 { 128 tag = " *** SUSPICIOUS" 129 } 130 t.Logf(" %4d %s%s", s.v, s.k, tag) 131 } 132 } 133 134 func buildRawResponse(resp *http.Response, body []byte) []byte { 135 var buf strings.Builder 136 buf.WriteString(fmt.Sprintf("HTTP/1.1 %s\r\n", resp.Status)) 137 for k, vals := range resp.Header { 138 for _, v := range vals { 139 buf.WriteString(fmt.Sprintf("%s: %s\r\n", k, v)) 140 } 141 } 142 buf.WriteString("\r\n") 143 buf.Write(body) 144 return []byte(buf.String()) 145 } 146 147 func probeAlive(targets []string, timeout time.Duration, conc int) []string { 148 var alive []string 149 var mu sync.Mutex 150 sem := make(chan struct{}, conc) 151 var wg sync.WaitGroup 152 for _, target := range targets { 153 wg.Add(1) 154 sem <- struct{}{} 155 go func(url string) { 156 defer wg.Done() 157 defer func() { <-sem }() 158 host := strings.TrimPrefix(strings.TrimPrefix(url, "http://"), "https://") 159 port := "80" 160 if strings.HasPrefix(url, "https://") { 161 port = "443" 162 } 163 conn, err := net.DialTimeout("tcp", host+":"+port, timeout) 164 if err != nil { 165 return 166 } 167 conn.Close() 168 mu.Lock() 169 alive = append(alive, url) 170 mu.Unlock() 171 }(target) 172 } 173 wg.Wait() 174 return alive 175 }