github.com/chainreactors/fingers@v1.2.1/wappalyzer/fingerprints.go (about) 1 package wappalyzer 2 3 import ( 4 "github.com/chainreactors/fingers/common" 5 "regexp" 6 "strconv" 7 "strings" 8 ) 9 10 // Fingerprints contains a map of fingerprints for tech detection 11 type Fingerprints struct { 12 // Apps is organized as <name, fingerprint> 13 Apps map[string]*Fingerprint `json:"apps"` 14 } 15 16 // Fingerprint is a single piece of information about a tech validated and normalized 17 type Fingerprint struct { 18 Cats []int `json:"cats"` 19 CSS []string `json:"css"` 20 Cookies map[string]string `json:"cookies"` 21 JS map[string]string `json:"js"` 22 Headers map[string]string `json:"headers"` 23 HTML []string `json:"html"` 24 Script []string `json:"scripts"` 25 ScriptSrc []string `json:"scriptSrc"` 26 Meta map[string][]string `json:"meta"` 27 Implies []string `json:"implies"` 28 Description string `json:"description"` 29 Website string `json:"website"` 30 CPE string `json:"cpe"` 31 } 32 33 // CompiledFingerprints contains a map of fingerprints for tech detection 34 type CompiledFingerprints struct { 35 // Apps is organized as <name, fingerprint> 36 Apps map[string]*CompiledFingerprint 37 } 38 39 // CompiledFingerprint contains the compiled fingerprints from the tech json 40 type CompiledFingerprint struct { 41 name string 42 // cats contain categories that are implicit with this tech 43 cats []int 44 // implies contains technologies that are implicit with this tech 45 implies []string 46 // description contains fingerprint description 47 description string 48 // website contains a URL associated with the fingerprint 49 website string 50 // cookies contains fingerprints for target cookies 51 cookies map[string]*versionRegex 52 // js contains fingerprints for the js file 53 js []*versionRegex 54 // headers contains fingerprints for target headers 55 headers map[string]*versionRegex 56 // html contains fingerprints for the target HTML 57 html []*versionRegex 58 // script contains fingerprints for scripts 59 script []*versionRegex 60 // scriptSrc contains fingerprints for script srcs 61 scriptSrc []*versionRegex 62 // meta contains fingerprints for meta tags 63 meta map[string][]*versionRegex 64 // cpe contains the cpe for a fingerpritn 65 cpe string 66 } 67 68 func (finger *CompiledFingerprint) NewFrame(version string) *common.Framework { 69 frame := common.NewFrameworkWithVersion(finger.name, common.FrameFromWappalyzer, version) 70 for _, tag := range finger.implies { 71 frame.AddTag(tag) 72 } 73 if finger.cpe != "" { 74 frame.Attributes = common.NewAttributesWithCPE(finger.cpe) 75 } 76 return frame 77 } 78 79 // AppInfo contains basic information about an App. 80 type AppInfo struct { 81 Description string 82 Website string 83 CPE string 84 } 85 86 // CatsInfo contains basic information about an App. 87 type CatsInfo struct { 88 Cats []int 89 } 90 91 type versionRegex struct { 92 regex *regexp.Regexp 93 skipRegex bool 94 group int 95 } 96 97 const versionPrefix = "version:\\" 98 99 // newVersionRegex creates a new version matching regex 100 // TODO: handles simple group cases only as of now (no ternary) 101 func newVersionRegex(value string) (*versionRegex, error) { 102 value = strings.ToLower(value) 103 splitted := strings.Split(value, "\\;") 104 if len(splitted) == 0 { 105 return nil, nil 106 } 107 108 compiled, err := regexp.Compile(splitted[0]) 109 if err != nil { 110 return nil, err 111 } 112 skipRegex := splitted[0] == "" 113 regex := &versionRegex{regex: compiled, skipRegex: skipRegex} 114 for _, part := range splitted { 115 if strings.HasPrefix(part, versionPrefix) { 116 group := strings.TrimPrefix(part, versionPrefix) 117 if parsed, err := strconv.Atoi(group); err == nil { 118 regex.group = parsed 119 } 120 } 121 } 122 return regex, nil 123 } 124 125 // MatchString returns true if a version regex matched. 126 // The found version is also returned if any. 127 func (v *versionRegex) MatchString(value string) (bool, string) { 128 if v.skipRegex { 129 return true, "" 130 } 131 matches := v.regex.FindAllStringSubmatch(value, -1) 132 if len(matches) == 0 { 133 return false, "" 134 } 135 136 var version string 137 if v.group > 0 { 138 for _, match := range matches { 139 version = match[v.group] 140 } 141 } 142 return true, version 143 } 144 145 // part is the part of the fingerprint to match 146 type part int 147 148 // parts that can be matched 149 const ( 150 cookiesPart part = iota + 1 151 jsPart 152 headersPart 153 htmlPart 154 scriptPart 155 metaPart 156 ) 157 158 // loadPatterns loads the fingerprint patterns and compiles regexes 159 func compileFingerprint(app string, fingerprint *Fingerprint) *CompiledFingerprint { 160 compiled := &CompiledFingerprint{ 161 name: app, 162 cats: fingerprint.Cats, 163 implies: fingerprint.Implies, 164 description: fingerprint.Description, 165 website: fingerprint.Website, 166 cookies: make(map[string]*versionRegex), 167 js: make([]*versionRegex, 0, len(fingerprint.JS)), 168 headers: make(map[string]*versionRegex), 169 html: make([]*versionRegex, 0, len(fingerprint.HTML)), 170 script: make([]*versionRegex, 0, len(fingerprint.Script)), 171 scriptSrc: make([]*versionRegex, 0, len(fingerprint.ScriptSrc)), 172 meta: make(map[string][]*versionRegex), 173 cpe: fingerprint.CPE, 174 } 175 176 for header, pattern := range fingerprint.Cookies { 177 fingerprint, err := newVersionRegex(pattern) 178 if err != nil { 179 continue 180 } 181 compiled.cookies[header] = fingerprint 182 } 183 184 for _, pattern := range fingerprint.JS { 185 if pattern == "" { 186 continue 187 } 188 fingerprint, err := newVersionRegex(pattern) 189 if err != nil { 190 continue 191 } 192 compiled.js = append(compiled.js, fingerprint) 193 } 194 195 for header, pattern := range fingerprint.Headers { 196 fingerprint, err := newVersionRegex(pattern) 197 if err != nil { 198 continue 199 } 200 compiled.headers[header] = fingerprint 201 } 202 203 for _, pattern := range fingerprint.HTML { 204 fingerprint, err := newVersionRegex(pattern) 205 if err != nil { 206 continue 207 } 208 compiled.html = append(compiled.html, fingerprint) 209 } 210 211 for _, pattern := range fingerprint.Script { 212 fingerprint, err := newVersionRegex(pattern) 213 if err != nil { 214 continue 215 } 216 compiled.script = append(compiled.script, fingerprint) 217 } 218 219 for _, pattern := range fingerprint.ScriptSrc { 220 fingerprint, err := newVersionRegex(pattern) 221 if err != nil { 222 continue 223 } 224 compiled.scriptSrc = append(compiled.scriptSrc, fingerprint) 225 } 226 227 for meta, patterns := range fingerprint.Meta { 228 var compiledList []*versionRegex 229 230 for _, pattern := range patterns { 231 fingerprint, err := newVersionRegex(pattern) 232 if err != nil { 233 continue 234 } 235 compiledList = append(compiledList, fingerprint) 236 } 237 compiled.meta[meta] = compiledList 238 } 239 return compiled 240 } 241 242 // matchString matches a string for the fingerprints 243 func (f *CompiledFingerprints) matchString(data string, part part) common.Frameworks { 244 var matched bool 245 technologies := make(common.Frameworks) 246 for _, fingerprint := range f.Apps { 247 var version string 248 249 switch part { 250 case jsPart: 251 for _, pattern := range fingerprint.js { 252 if valid, versionString := pattern.MatchString(data); valid { 253 matched = true 254 version = versionString 255 } 256 } 257 case scriptPart: 258 for _, pattern := range fingerprint.scriptSrc { 259 if valid, versionString := pattern.MatchString(data); valid { 260 matched = true 261 version = versionString 262 } 263 } 264 case htmlPart: 265 for _, pattern := range fingerprint.html { 266 if valid, versionString := pattern.MatchString(data); valid { 267 matched = true 268 version = versionString 269 } 270 } 271 default: 272 continue 273 } 274 275 // If no match, continue with the next fingerprint 276 if !matched { 277 continue 278 } 279 280 frame := fingerprint.NewFrame(version) 281 technologies.Add(frame) 282 matched = false 283 } 284 return technologies 285 } 286 287 // matchKeyValue matches a key-value store map for the fingerprints 288 func (f *CompiledFingerprints) matchKeyValueString(key, value string, part part) common.Frameworks { 289 var matched bool 290 var technologies = make(common.Frameworks) 291 292 for _, fingerprint := range f.Apps { 293 var version string 294 295 switch part { 296 case cookiesPart: 297 for data, pattern := range fingerprint.cookies { 298 if data != key { 299 continue 300 } 301 302 if valid, versionString := pattern.MatchString(value); valid { 303 matched = true 304 version = versionString 305 break 306 } 307 } 308 case headersPart: 309 for data, pattern := range fingerprint.headers { 310 if data != key { 311 continue 312 } 313 314 if valid, versionString := pattern.MatchString(value); valid { 315 matched = true 316 version = versionString 317 break 318 } 319 } 320 case metaPart: 321 for data, patterns := range fingerprint.meta { 322 if data != key { 323 continue 324 } 325 326 for _, pattern := range patterns { 327 if valid, versionString := pattern.MatchString(value); valid { 328 matched = true 329 version = versionString 330 break 331 } 332 } 333 } 334 } 335 336 // If no match, continue with the next fingerprint 337 if !matched { 338 continue 339 } 340 frame := fingerprint.NewFrame(version) 341 technologies.Add(frame) 342 matched = false 343 } 344 return technologies 345 } 346 347 // matchMapString matches a key-value store map for the fingerprints 348 func (f *CompiledFingerprints) matchMapString(keyValue map[string]string, part part) common.Frameworks { 349 var matched bool 350 technologies := make(common.Frameworks) 351 352 for _, fingerprint := range f.Apps { 353 var version string 354 355 switch part { 356 case cookiesPart: 357 for data, pattern := range fingerprint.cookies { 358 value, ok := keyValue[data] 359 if !ok { 360 continue 361 } 362 if pattern == nil { 363 matched = true 364 } 365 if valid, versionString := pattern.MatchString(value); valid { 366 matched = true 367 version = versionString 368 break 369 } 370 } 371 case headersPart: 372 for data, pattern := range fingerprint.headers { 373 value, ok := keyValue[data] 374 if !ok { 375 continue 376 } 377 378 if valid, versionString := pattern.MatchString(value); valid { 379 matched = true 380 version = versionString 381 break 382 } 383 } 384 case metaPart: 385 for data, patterns := range fingerprint.meta { 386 value, ok := keyValue[data] 387 if !ok { 388 continue 389 } 390 391 for _, pattern := range patterns { 392 if valid, versionString := pattern.MatchString(value); valid { 393 matched = true 394 version = versionString 395 break 396 } 397 } 398 } 399 } 400 401 // If no match, continue with the next fingerprint 402 if !matched { 403 continue 404 } 405 406 // Append the technologies as well as implied ones 407 408 frame := fingerprint.NewFrame(version) 409 technologies.Add(frame) 410 matched = false 411 } 412 return technologies 413 }