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  }