github.com/chainreactors/fingers@v1.2.1/fingers/level_send_data_test.go (about) 1 package fingers 2 3 import ( 4 "strings" 5 "testing" 6 ) 7 8 func rawHTTP(body string) []byte { 9 return []byte("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" + body) 10 } 11 12 func contains(xs []string, want string) bool { 13 for _, x := range xs { 14 if x == want { 15 return true 16 } 17 } 18 return false 19 } 20 21 // TestLevelSendDataRecognition verifies that: 22 // 1) level 0 performs no active probing, 23 // 2) level 1 uses finger-level send_data only, 24 // 3) level 2 additionally uses rule-level send_data. 25 func TestLevelSendDataRecognition(t *testing.T) { 26 const ( 27 fingerPath = "/finger-level" 28 rulePath = "/rule-level" 29 ) 30 31 finger := &Finger{ 32 Name: "level-test", 33 Protocol: HTTPProtocol, 34 DefaultPort: []string{"80"}, 35 SendDataStr: fingerPath, 36 Rules: Rules{ 37 { 38 Regexps: &Regexps{ 39 Body: []string{"finger-hit"}, 40 }, 41 Level: 1, 42 }, 43 { 44 Regexps: &Regexps{ 45 Body: []string{"rule-hit"}, 46 }, 47 SendDataStr: rulePath, 48 Level: 2, 49 }, 50 }, 51 } 52 53 if err := finger.Compile(false); err != nil { 54 t.Fatalf("failed to compile finger: %v", err) 55 } 56 57 responses := map[string][]byte{ 58 fingerPath: rawHTTP("<title>finger-hit</title>"), 59 rulePath: rawHTTP("<title>rule-hit</title>"), 60 } 61 62 makeSender := func(sent *[]string) Sender { 63 return Sender(func(data []byte) ([]byte, bool) { 64 path := string(data) 65 *sent = append(*sent, path) 66 resp, ok := responses[path] 67 return resp, ok 68 }) 69 } 70 71 // level 0: passive only, should not send and should not match. 72 { 73 var sent []string 74 frame, _, ok := finger.Match(NewContent(rawHTTP("no-hit"), "", true), 0, makeSender(&sent)) 75 if ok || frame != nil { 76 t.Fatalf("level 0 should not match, got ok=%v frame=%v", ok, frame) 77 } 78 if len(sent) != 0 { 79 t.Fatalf("level 0 should not send, sent=%v", sent) 80 } 81 } 82 83 // level 1: should send finger-level only and match via finger-level response. 84 { 85 var sent []string 86 frame, _, ok := finger.Match(NewContent(rawHTTP("no-hit"), "", true), 1, makeSender(&sent)) 87 if !ok || frame == nil { 88 t.Fatalf("level 1 should match via finger send_data") 89 } 90 if !contains(sent, fingerPath) { 91 t.Fatalf("level 1 should send finger path %q, sent=%v", fingerPath, sent) 92 } 93 if contains(sent, rulePath) { 94 t.Fatalf("level 1 should not send rule path %q, sent=%v", rulePath, sent) 95 } 96 } 97 98 // level 2: should send both finger-level and rule-level. 99 { 100 var sent []string 101 frame, _, ok := finger.Match(NewContent(rawHTTP("no-hit"), "", true), 2, makeSender(&sent)) 102 if !ok || frame == nil { 103 t.Fatalf("level 2 should match via active probing") 104 } 105 if !contains(sent, fingerPath) { 106 t.Fatalf("level 2 should send finger path %q, sent=%v", fingerPath, sent) 107 } 108 if !contains(sent, rulePath) { 109 t.Fatalf("level 2 should send rule path %q, sent=%v", rulePath, sent) 110 } 111 112 // Sanity-check that the level 2 run actually used active content. 113 if frame.Version == "" && !strings.Contains(frame.Name, "level-test") { 114 t.Fatalf("unexpected framework returned at level 2: %#v", frame) 115 } 116 } 117 } 118 119 func TestMatchDetail_Passive(t *testing.T) { 120 finger := &Finger{ 121 Name: "rule-tag-test", 122 Protocol: HTTPProtocol, 123 EnableMatchDetail: true, 124 Rules: Rules{ 125 { 126 Regexps: &Regexps{ 127 Body: []string{"first-hit"}, 128 }, 129 }, 130 { 131 Regexps: &Regexps{ 132 Body: []string{"second-hit"}, 133 }, 134 }, 135 }, 136 } 137 138 if err := finger.Compile(false); err != nil { 139 t.Fatalf("failed to compile finger: %v", err) 140 } 141 142 frame, _, ok := finger.Match(NewContent(rawHTTP("second-hit"), "", true), 0, nil) 143 if !ok || frame == nil { 144 t.Fatalf("expected passive match to succeed") 145 } 146 if frame.MatchDetail == nil { 147 t.Fatalf("expected match detail to be populated") 148 } 149 if frame.MatchDetail.RuleIndex != 1 { 150 t.Fatalf("expected rule index 1, got %d", frame.MatchDetail.RuleIndex) 151 } 152 if frame.MatchDetail.MatcherType != "body" { 153 t.Fatalf("expected matcher type body, got %q", frame.MatchDetail.MatcherType) 154 } 155 if frame.MatchDetail.MatcherIndex != 0 { 156 t.Fatalf("expected matcher index 0, got %d", frame.MatchDetail.MatcherIndex) 157 } 158 if frame.MatchDetail.SendData != "" { 159 t.Fatalf("expected passive match detail send_data to be empty, got %q", frame.MatchDetail.SendData) 160 } 161 } 162 163 func TestMatchDetail_ActiveSendData(t *testing.T) { 164 const ( 165 fingerPath = "/finger-active" 166 rulePath = "/rule-active" 167 ) 168 169 finger := &Finger{ 170 Name: "active-detail-test", 171 Protocol: HTTPProtocol, 172 EnableMatchDetail: true, 173 SendDataStr: fingerPath, 174 Rules: Rules{ 175 { 176 Regexps: &Regexps{ 177 Body: []string{"rule-hit"}, 178 }, 179 SendDataStr: rulePath, 180 Level: 2, 181 }, 182 }, 183 } 184 185 if err := finger.Compile(false); err != nil { 186 t.Fatalf("failed to compile finger: %v", err) 187 } 188 189 sender := Sender(func(data []byte) ([]byte, bool) { 190 switch string(data) { 191 case fingerPath: 192 return rawHTTP("no-hit"), true 193 case rulePath: 194 return rawHTTP("rule-hit"), true 195 default: 196 return nil, false 197 } 198 }) 199 200 frame, _, ok := finger.ActiveMatch(2, sender) 201 if !ok || frame == nil { 202 t.Fatalf("expected active match to succeed") 203 } 204 if frame.MatchDetail == nil { 205 t.Fatalf("expected match detail to be populated") 206 } 207 if frame.MatchDetail.SendData != rulePath { 208 t.Fatalf("expected send_data %q, got %q", rulePath, frame.MatchDetail.SendData) 209 } 210 }