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  }