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 }