github.com/chainreactors/fingers@v1.2.1/fingers/rules.go (about)

     1  package fingers
     2  
     3  import (
     4  	"bytes"
     5  	"strings"
     6  
     7  	"github.com/chainreactors/fingers/common"
     8  	"github.com/chainreactors/utils/encode"
     9  )
    10  
    11  type Regexps struct {
    12  	Body                  []string         `yaml:"body,omitempty" json:"body,omitempty" jsonschema:"title=Body Patterns,description=String patterns to match in HTTP response body,nullable,example=<title>nginx</title>"`
    13  	MD5                   []string         `yaml:"md5,omitempty" json:"md5,omitempty" jsonschema:"title=MD5 Hashes,description=MD5 hashes of response bodies to match,nullable,pattern=^[a-f0-9]{32}$,example=d41d8cd98f00b204e9800998ecf8427e"`
    14  	MMH3                  []string         `yaml:"mmh3,omitempty" json:"mmh3,omitempty" jsonschema:"title=MMH3 Hashes,description=MurmurHash3 hashes for favicon matching,nullable,example=116323821"`
    15  	Regexp                []string         `yaml:"regexp,omitempty" json:"regexp,omitempty" jsonschema:"title=Regular Expressions,description=Regex patterns for advanced matching,nullable,example=nginx/([\\d\\.]+)"`
    16  	Version               []string         `yaml:"version,omitempty" json:"version,omitempty" jsonschema:"title=Version Patterns,description=Regex patterns to extract version information,nullable,example=([\\d\\.]+)"`
    17  	Cert                  []string         `yaml:"cert,omitempty" json:"cert,omitempty" jsonschema:"title=Certificate Patterns,description=Patterns to match in SSL certificates,nullable,example=nginx"`
    18  	CompliedRegexp        []CompiledRegexp `yaml:"-" json:"-"`
    19  	CompiledVulnRegexp    []CompiledRegexp `yaml:"-" json:"-"`
    20  	CompiledVersionRegexp []CompiledRegexp `yaml:"-" json:"-"`
    21  	FingerName            string           `yaml:"-" json:"-"`
    22  	Header                []string         `yaml:"header,omitempty" json:"header,omitempty" jsonschema:"title=Header Patterns,description=Patterns to match in HTTP headers,nullable,example=Server: nginx"`
    23  	Vuln                  []string         `yaml:"vuln,omitempty" json:"vuln,omitempty" jsonschema:"title=Vulnerability Patterns,description=Regex patterns indicating security vulnerabilities,nullable,example=admin/config.php"`
    24  }
    25  
    26  func (r *Regexps) Compile(caseSensitive bool) error {
    27  	for _, reg := range r.Regexp {
    28  		creg, err := compileRegexp("(?i)" + reg)
    29  		if err != nil {
    30  			return err
    31  		}
    32  		r.CompliedRegexp = append(r.CompliedRegexp, creg)
    33  	}
    34  
    35  	for _, reg := range r.Vuln {
    36  		creg, err := compileRegexp("(?i)" + reg)
    37  		if err != nil {
    38  			return err
    39  		}
    40  		r.CompiledVulnRegexp = append(r.CompiledVulnRegexp, creg)
    41  	}
    42  
    43  	for _, reg := range r.Version {
    44  		creg, err := compileRegexp(reg)
    45  		if err != nil {
    46  			return err
    47  		}
    48  		r.CompiledVersionRegexp = append(r.CompiledVersionRegexp, creg)
    49  	}
    50  
    51  	for i, b := range r.Body {
    52  		if !caseSensitive {
    53  			r.Body[i] = strings.ToLower(b)
    54  		}
    55  	}
    56  
    57  	for i, h := range r.Header {
    58  		if !caseSensitive {
    59  			r.Header[i] = strings.ToLower(h)
    60  		}
    61  	}
    62  	return nil
    63  }
    64  
    65  type Favicons struct {
    66  	Mmh3 []string `yaml:"mmh3,omitempty" json:"mmh3,omitempty" jsonschema:"title=MMH3 Hashes,description=MurmurHash3 hashes of favicon content,nullable,example=116323821"`
    67  	Md5  []string `yaml:"md5,omitempty" json:"md5,omitempty" jsonschema:"title=MD5 Hashes,description=MD5 hashes of favicon content,nullable,pattern=^[a-f0-9]{32}$,example=d41d8cd98f00b204e9800998ecf8427e"`
    68  }
    69  
    70  type Rule struct {
    71  	Version     string    `yaml:"version,omitempty" json:"version,omitempty" jsonschema:"title=Version,description=Version string or extraction pattern,nullable,example=1.18.0"`
    72  	Favicon     *Favicons `yaml:"favicon,omitempty" json:"favicon,omitempty" jsonschema:"title=Favicon Rules,description=Favicon-based matching rules,nullable"`
    73  	Regexps     *Regexps  `yaml:"regexps,omitempty" json:"regexps,omitempty" jsonschema:"title=Regex Rules,description=Regular expression matching rules,nullable"`
    74  	SendDataStr string    `yaml:"send_data,omitempty" json:"send_data,omitempty" jsonschema:"title=Send Data,description=Data to send for active probing,nullable,example=GET /admin HTTP/1.1\\r\\nHost: {{Hostname}}\\r\\n\\r\\n"`
    75  	SendData    senddata  `yaml:"-" json:"-"`
    76  	Info        string    `yaml:"info,omitempty" json:"info,omitempty" jsonschema:"title=Information,description=Additional information about the detection,nullable,example=Admin panel detected"`
    77  	Vuln        string    `yaml:"vuln,omitempty" json:"vuln,omitempty" jsonschema:"title=Vulnerability,description=Vulnerability information if detected,nullable,example=Default admin credentials"`
    78  	Level       int       `yaml:"level,omitempty" json:"level,omitempty" jsonschema:"title=Detection Level,description=Active probing level (0=passive 1+=active),minimum=0,maximum=5,default=0,example=1"`
    79  	FingerName  string    `yaml:"-" json:"-"`
    80  	IsActive    bool      `yaml:"-" json:"-"`
    81  }
    82  
    83  func (r *Rule) Compile(name string, caseSensitive bool) error {
    84  	if r.Version == "" {
    85  		r.Version = "_"
    86  	}
    87  	r.FingerName = name
    88  	if r.SendDataStr != "" {
    89  		r.SendData, _ = encode.DSLParser(r.SendDataStr)
    90  		if r.Level == 0 {
    91  			r.Level = 1
    92  		}
    93  		r.IsActive = true
    94  	}
    95  
    96  	if r.Regexps != nil {
    97  		err := r.Regexps.Compile(caseSensitive)
    98  		if err != nil {
    99  			return err
   100  		}
   101  	}
   102  
   103  	return nil
   104  }
   105  
   106  // ActiveSendDataList selects the active probing payloads based on level:
   107  // level 0: passive only (no sender)
   108  // level 1: finger-level send_data
   109  // level 2+: finger-level send_data AND rule-level send_data
   110  func (r *Rule) ActiveSendDataList(level int, fingerSendData senddata) []senddata {
   111  	if level <= 0 {
   112  		return nil
   113  	}
   114  	if r.Level > 0 && level < r.Level {
   115  		return nil
   116  	}
   117  
   118  	var payloads []senddata
   119  	if level >= 1 && !fingerSendData.IsNull() {
   120  		payloads = append(payloads, fingerSendData)
   121  	}
   122  	if level >= 2 && !r.SendData.IsNull() {
   123  		payloads = append(payloads, r.SendData)
   124  	}
   125  	return payloads
   126  }
   127  
   128  // ActiveSendData returns the most specific payload for backward compatibility.
   129  func (r *Rule) ActiveSendData(level int, fingerSendData senddata) (senddata, bool) {
   130  	payloads := r.ActiveSendDataList(level, fingerSendData)
   131  	if len(payloads) == 0 {
   132  		return nil, false
   133  	}
   134  	return payloads[len(payloads)-1], true
   135  }
   136  
   137  // ActiveSendDataStr returns the most specific active probing payload string.
   138  func (r *Rule) ActiveSendDataStr(fingerSendDataStr string) string {
   139  	if r.SendDataStr != "" {
   140  		return r.SendDataStr
   141  	}
   142  	return fingerSendDataStr
   143  }
   144  
   145  // RefreshActive recomputes whether the rule is active based on send_data presence.
   146  func (r *Rule) RefreshActive() {
   147  	r.IsActive = r.SendDataStr != ""
   148  }
   149  
   150  type Rules []*Rule
   151  
   152  func (rs Rules) Compile(name string, caseSensitive bool) error {
   153  	for _, r := range rs {
   154  		err := r.Compile(name, caseSensitive)
   155  		if err != nil {
   156  			return err
   157  		}
   158  	}
   159  	return nil
   160  }
   161  
   162  func (r *Rule) Match(content, header, body []byte) (bool, bool, string, *common.MatchDetail) {
   163  	newDetail := func(matcherType string, matcherIndex int, matcherValue string) *common.MatchDetail {
   164  		return &common.MatchDetail{
   165  			MatcherType:  matcherType,
   166  			MatcherIndex: matcherIndex,
   167  			MatcherValue: matcherValue,
   168  		}
   169  	}
   170  	// 漏洞匹配优先
   171  	for i, reg := range r.Regexps.CompiledVulnRegexp {
   172  		res, ok := compiledMatch(reg, content)
   173  		if ok {
   174  			return true, true, res, newDetail("regexp_vuln", i, reg.String())
   175  		}
   176  	}
   177  
   178  	// 正则匹配
   179  	for i, reg := range r.Regexps.CompliedRegexp {
   180  		res, ok := compiledMatch(reg, content)
   181  		if ok {
   182  			FingerLog.Debugf("%s finger hit, regexp: %q", r.FingerName, reg.String())
   183  			return true, false, res, newDetail("regexp", i, reg.String())
   184  		}
   185  	}
   186  
   187  	// http头匹配, http协议特有的匹配
   188  	if header != nil {
   189  		for i, headerStr := range r.Regexps.Header {
   190  			if bytes.Contains(header, []byte(headerStr)) {
   191  				FingerLog.Debugf("%s finger hit, header: %s", r.FingerName, headerStr)
   192  				return true, false, "", newDetail("header", i, headerStr)
   193  			}
   194  		}
   195  	}
   196  
   197  	if body == nil && header == nil {
   198  		body = content
   199  	}
   200  
   201  	// body匹配
   202  	for i, bodyReg := range r.Regexps.Body {
   203  		if bytes.Contains(body, []byte(bodyReg)) {
   204  			FingerLog.Debugf("%s finger hit, body: %q", r.FingerName, bodyReg)
   205  			return true, false, "", newDetail("body", i, bodyReg)
   206  		}
   207  	}
   208  
   209  	// MD5 匹配
   210  	for i, md5s := range r.Regexps.MD5 {
   211  		if md5s == encode.Md5Hash(body) {
   212  			FingerLog.Debugf("%s finger hit, md5: %s", r.FingerName, md5s)
   213  			return true, false, "", newDetail("md5", i, md5s)
   214  		}
   215  	}
   216  
   217  	// mmh3 匹配
   218  	for i, mmh3s := range r.Regexps.MMH3 {
   219  		if mmh3s == encode.Mmh3Hash32(body) {
   220  			FingerLog.Debugf("%s finger hit, mmh3: %s", r.FingerName, mmh3s)
   221  			return true, false, "", newDetail("mmh3", i, mmh3s)
   222  		}
   223  	}
   224  
   225  	return false, false, "", nil
   226  }
   227  
   228  func (r *Rule) MatchCert(content string) bool {
   229  	for _, cert := range r.Regexps.Cert {
   230  		if strings.Contains(content, cert) {
   231  			return true
   232  		}
   233  	}
   234  	return false
   235  }