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  }