github.com/chainreactors/fingers@v1.2.1/fingers/engine.go (about) 1 package fingers 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "net/http" 9 10 "github.com/chainreactors/fingers/common" 11 "github.com/chainreactors/fingers/favicon" 12 "github.com/chainreactors/fingers/resources" 13 "gopkg.in/yaml.v3" 14 ) 15 16 const ( 17 HTTPProtocol = "http" 18 TCPProtocol = "tcp" 19 UDPProtocol = "udp" 20 ) 21 22 func NewEngine(httpFingers, socketFingers Fingers) (*FingersEngine, error) { 23 engine := &FingersEngine{ 24 HTTPFingers: httpFingers, 25 SocketFingers: socketFingers, 26 Favicons: favicon.NewFavicons(), 27 } 28 29 err := engine.Compile() 30 if err != nil { 31 return nil, err 32 } 33 return engine, nil 34 } 35 36 func NewFingersEngine(httpData, socketData, portData []byte) (*FingersEngine, error) { 37 // httpdata must be not nil 38 // socketdata can be nil 39 if resources.PrePort == nil && portData != nil { 40 // 临时设置 PortData 以供 LoadPorts 使用 41 resources.PortData = portData 42 _, err := resources.LoadPorts() 43 if err != nil { 44 return nil, err 45 } 46 } 47 48 httpfs, err := LoadFingers(httpData) 49 if err != nil { 50 return nil, err 51 } 52 53 socketfs, err := LoadFingers(socketData) 54 if err != nil { 55 return nil, err 56 } 57 58 return NewEngine(httpfs, socketfs) 59 } 60 61 type FingersEngine struct { 62 HTTPFingers Fingers 63 HTTPFingersActiveFingers Fingers 64 SocketFingers Fingers 65 SocketGroup FingerMapper 66 Favicons *favicon.FaviconsEngine 67 MatchDetailEnabled bool 68 httpKeywordIndex *KeywordIndex 69 } 70 71 func (engine *FingersEngine) Name() string { 72 return "fingers" 73 } 74 75 func (engine *FingersEngine) Len() int { 76 return len(engine.HTTPFingers) + len(engine.SocketFingers) 77 } 78 79 // addToSocketGroup 将指纹添加到SocketGroup中 80 func (engine *FingersEngine) addToSocketGroup(f *Finger) { 81 if engine.SocketGroup == nil { 82 engine.SocketGroup = make(FingerMapper) 83 } 84 if f.DefaultPort != nil { 85 for _, port := range resources.PrePort.ParsePortSlice(f.DefaultPort) { 86 engine.SocketGroup[port] = append(engine.SocketGroup[port], f) 87 } 88 } else { 89 engine.SocketGroup["0"] = append(engine.SocketGroup["0"], f) 90 } 91 } 92 93 func (engine *FingersEngine) Compile() error { 94 var err error 95 if engine.HTTPFingers == nil { 96 return errors.New("fingers is nil") 97 } 98 engine.HTTPFingersActiveFingers = nil 99 for _, finger := range engine.HTTPFingers { 100 finger.EnableMatchDetail = engine.MatchDetailEnabled 101 err = finger.Compile(false) 102 if err != nil { 103 return err 104 } 105 if finger.IsActive { 106 engine.HTTPFingersActiveFingers = append(engine.HTTPFingersActiveFingers, finger) 107 } 108 } 109 110 //初始化favicon规则 111 for _, finger := range engine.HTTPFingers { 112 for _, rule := range finger.Rules { 113 if rule.Favicon != nil { 114 for _, mmh3 := range rule.Favicon.Mmh3 { 115 engine.Favicons.Mmh3Fingers[mmh3] = finger.Name 116 } 117 for _, md5 := range rule.Favicon.Md5 { 118 engine.Favicons.Md5Fingers[md5] = finger.Name 119 } 120 } 121 } 122 } 123 124 engine.httpKeywordIndex = NewKeywordIndex(engine.HTTPFingers) 125 126 if engine.SocketFingers != nil { 127 for _, finger := range engine.SocketFingers { 128 finger.EnableMatchDetail = engine.MatchDetailEnabled 129 err = finger.Compile(true) 130 if err != nil { 131 return err 132 } 133 engine.addToSocketGroup(finger) 134 } 135 } 136 return nil 137 } 138 139 func (engine *FingersEngine) Append(fingers Fingers) error { 140 for _, f := range fingers { 141 f.EnableMatchDetail = engine.MatchDetailEnabled 142 err := f.Compile(false) 143 if err != nil { 144 return err 145 } 146 if f.Protocol == HTTPProtocol { 147 engine.HTTPFingers = append(engine.HTTPFingers, f) 148 if f.IsActive { 149 engine.HTTPFingersActiveFingers = append(engine.HTTPFingersActiveFingers, f) 150 } 151 } else if f.Protocol == TCPProtocol { 152 engine.SocketFingers = append(engine.SocketFingers, f) 153 engine.addToSocketGroup(f) 154 } 155 } 156 engine.httpKeywordIndex = NewKeywordIndex(engine.HTTPFingers) 157 return nil 158 } 159 160 // SetMatchDetailEnabled toggles match detail collection on all fingers. 161 func (engine *FingersEngine) SetMatchDetailEnabled(enabled bool) { 162 engine.MatchDetailEnabled = enabled 163 for _, finger := range engine.HTTPFingers { 164 finger.EnableMatchDetail = enabled 165 } 166 for _, finger := range engine.SocketFingers { 167 finger.EnableMatchDetail = enabled 168 } 169 } 170 171 // EnableMatchDetail enables detailed matcher metadata collection. 172 func (engine *FingersEngine) EnableMatchDetail() { 173 engine.SetMatchDetailEnabled(true) 174 } 175 176 // LoadFromYAML loads fingerprints from YAML file or URL and appends them to the engine 177 // This method only supports YAML format for custom fingerprints 178 func (engine *FingersEngine) LoadFromYAML(path string) error { 179 content, err := resources.LoadFingersFromYAML(path) 180 if err != nil { 181 return err 182 } 183 184 var fingers Fingers 185 if err := yaml.Unmarshal(content, &fingers); err != nil { 186 return err 187 } 188 189 return engine.Append(fingers) 190 } 191 192 func (engine *FingersEngine) SocketMatch(content []byte, port string, level int, sender Sender, callback Callback) (*common.Framework, *common.Vuln) { 193 // socket service only match one fingerprint 194 var alreadyFrameworks = make(map[string]bool) 195 input := NewContent(content, "", false) 196 var fs common.Frameworks 197 var vs common.Vulns 198 if port != "" { 199 fs, vs = engine.SocketGroup[port].Match(input, level, sender, callback, true) 200 if len(fs) > 0 { 201 return fs.One(), vs.One() 202 } 203 for _, fs := range engine.SocketGroup[port] { 204 alreadyFrameworks[fs.Name] = true 205 } 206 } 207 208 fs, vs = engine.SocketGroup["0"].Match(input, level, sender, callback, true) 209 if len(fs) > 0 { 210 return fs.One(), vs.One() 211 } 212 for _, fs := range engine.SocketGroup["0"] { 213 alreadyFrameworks[fs.Name] = true 214 } 215 216 for _, fs := range engine.SocketGroup { 217 for _, finger := range fs { 218 if _, ok := alreadyFrameworks[finger.Name]; ok { 219 continue 220 } else { 221 alreadyFrameworks[finger.Name] = true 222 } 223 224 frame, vuln, ok := finger.Match(input, level, sender) 225 if ok { 226 if callback != nil { 227 callback(frame, vuln) 228 } 229 return frame, vuln 230 } 231 } 232 } 233 return nil, nil 234 } 235 236 // WebMatch 实现Web指纹匹配 237 func (engine *FingersEngine) WebMatch(content []byte) common.Frameworks { 238 fs, _ := engine.HTTPMatch(content, "") 239 return fs 240 } 241 242 // ServiceMatch 实现Service指纹匹配 243 func (engine *FingersEngine) ServiceMatch(host string, portStr string, level int, sender common.ServiceSender, callback common.ServiceCallback) *common.ServiceResult { 244 if sender == nil { 245 return nil 246 } 247 248 // 创建自适应的Callback 249 fingersCallback := func(framework *common.Framework, vuln *common.Vuln) { 250 if callback != nil { 251 result := &common.ServiceResult{ 252 Framework: framework, 253 Vuln: vuln, 254 } 255 callback(result) 256 } 257 } 258 259 // 创建适配器将common.ServiceSender转换为fingers.Sender 260 // fingers.Sender: func([]byte) ([]byte, bool) 261 // common.ServiceSender.Send(host, port, data) ([]byte, error) 262 fingersSender := Sender(func(data []byte) ([]byte, bool) { 263 response, err := sender.Send(host, portStr, data, "tcp") 264 if err != nil { 265 return nil, false 266 } 267 return response, true 268 }) 269 270 framework, vuln := engine.SocketMatch(nil, portStr, level, fingersSender, fingersCallback) 271 272 return &common.ServiceResult{ 273 Framework: framework, 274 Vuln: vuln, 275 } 276 } 277 278 func (engine *FingersEngine) Capability() common.EngineCapability { 279 return common.EngineCapability{ 280 SupportWeb: true, // fingers支持Web指纹 281 SupportService: true, // fingers支持Service指纹 282 } 283 } 284 285 func (engine *FingersEngine) HTTPMatch(content []byte, cert string) (common.Frameworks, common.Vulns) { 286 input := NewContent(content, cert, true) 287 if engine.httpKeywordIndex != nil { 288 return engine.HTTPFingers.ACPassiveMatch(input, engine.httpKeywordIndex, false) 289 } 290 return engine.HTTPFingers.PassiveMatch(input, false) 291 } 292 293 func (engine *FingersEngine) HTTPActiveMatch(baseURL string, level int, transport http.RoundTripper, callback Callback) (common.Frameworks, common.Vulns) { 294 // 将 http.RoundTripper 适配为 Sender 295 sender := roundTripperToSender(transport, baseURL) 296 return engine.HTTPFingersActiveFingers.ActiveMatch(level, sender, callback, false) 297 } 298 299 // roundTripperToSender 将 http.RoundTripper 适配为 Sender 300 // 这样可以让内部的 ActiveMatch 继续使用 Sender 接口,同时对外统一使用 http.RoundTripper 301 func roundTripperToSender(transport http.RoundTripper, baseURL string) Sender { 302 return func(data []byte) ([]byte, bool) { 303 // data 是路径,例如 "/admin" 或 "/api/version" 304 path := string(data) 305 306 // 构造完整 URL 307 fullURL := baseURL + path 308 309 // 创建 http.Request 310 req, err := http.NewRequest("GET", fullURL, nil) 311 if err != nil { 312 return nil, false 313 } 314 315 // 通过 RoundTripper 发送请求 316 resp, err := transport.RoundTrip(req) 317 if err != nil { 318 return nil, false 319 } 320 defer resp.Body.Close() 321 322 // 读取响应并序列化为字节流 323 var buf bytes.Buffer 324 325 // 写入状态行 326 fmt.Fprintf(&buf, "%s %s\r\n", resp.Proto, resp.Status) 327 328 // 写入 headers 329 for key, values := range resp.Header { 330 for _, value := range values { 331 fmt.Fprintf(&buf, "%s: %s\r\n", key, value) 332 } 333 } 334 335 // 空行分隔 headers 和 body 336 buf.WriteString("\r\n") 337 338 // 写入 body 339 if resp.Body != nil { 340 io.Copy(&buf, resp.Body) 341 } 342 343 return buf.Bytes(), true 344 } 345 }