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  }