github.com/chainreactors/fingers@v1.2.1/wappalyzer/cmd/update-fingerprints/main.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"flag"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"net/http"
    11  	"os"
    12  	"reflect"
    13  	"sort"
    14  	"strings"
    15  )
    16  
    17  var fingerprints = flag.String("fingerprints", "../../fingerprints_data.json", "File to write wappalyzer fingerprints to")
    18  
    19  // Fingerprints contains a map of fingerprints for tech detection
    20  type Fingerprints struct {
    21  	// Apps is organized as <name, fingerprint>
    22  	Apps map[string]Fingerprint `json:"technologies"`
    23  }
    24  
    25  // Fingerprint is a single piece of information about a tech
    26  type Fingerprint struct {
    27  	Cats        []int                  `json:"cats"`
    28  	CSS         interface{}            `json:"css"`
    29  	Cookies     map[string]string      `json:"cookies"`
    30  	JS          map[string]string      `json:"js"`
    31  	Headers     map[string]string      `json:"headers"`
    32  	HTML        interface{}            `json:"html"`
    33  	Script      interface{}            `json:"scripts"`
    34  	ScriptSrc   interface{}            `json:"scriptSrc"`
    35  	Meta        map[string]interface{} `json:"meta"`
    36  	Implies     interface{}            `json:"implies"`
    37  	Description string                 `json:"description"`
    38  	Website     string                 `json:"website"`
    39  	CPE         string                 `json:"cpe"`
    40  }
    41  
    42  // OutputFingerprints contains a map of fingerprints for tech detection
    43  // optimized and validated for the tech detection package
    44  type OutputFingerprints struct {
    45  	// Apps is organized as <name, fingerprint>
    46  	Apps map[string]OutputFingerprint `json:"apps"`
    47  }
    48  
    49  // OutputFingerprint is a single piece of information about a tech validated and normalized
    50  type OutputFingerprint struct {
    51  	Cats        []int               `json:"cats,omitempty"`
    52  	CSS         []string            `json:"css,omitempty"`
    53  	Cookies     map[string]string   `json:"cookies,omitempty"`
    54  	JS          []string            `json:"js,omitempty"`
    55  	Headers     map[string]string   `json:"headers,omitempty"`
    56  	HTML        []string            `json:"html,omitempty"`
    57  	Script      []string            `json:"scripts,omitempty"`
    58  	ScriptSrc   []string            `json:"scriptSrc,omitempty"`
    59  	Meta        map[string][]string `json:"meta,omitempty"`
    60  	Implies     []string            `json:"implies,omitempty"`
    61  	Description string              `json:"description,omitempty"`
    62  	Website     string              `json:"website,omitempty"`
    63  	CPE         string              `json:"cpe,omitempty"`
    64  }
    65  
    66  const fingerprintURL = "https://raw.githubusercontent.com/Lissy93/wapalyzer/master/src/technologies/%s.json"
    67  
    68  func makeFingerprintURLs() []string {
    69  	files := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "_"}
    70  
    71  	fingerprints := make([]string, 0, len(files))
    72  	for _, item := range files {
    73  		fingerprints = append(fingerprints, fmt.Sprintf(fingerprintURL, item))
    74  	}
    75  	return fingerprints
    76  }
    77  
    78  func main() {
    79  	flag.Parse()
    80  
    81  	fingerprintURLs := makeFingerprintURLs()
    82  
    83  	fingerprintsOld := &Fingerprints{
    84  		Apps: make(map[string]Fingerprint),
    85  	}
    86  	for _, fingerprintItem := range fingerprintURLs {
    87  		if err := gatherFingerprintsFromURL(fingerprintItem, fingerprintsOld); err != nil {
    88  			log.Fatalf("Could not gather fingerprints %s: %v\n", fingerprintItem, err)
    89  		}
    90  	}
    91  
    92  	log.Printf("Read fingerprints from the server\n")
    93  	log.Printf("Starting normalizing of %d fingerprints...\n", len(fingerprintsOld.Apps))
    94  
    95  	outputFingerprints := normalizeFingerprints(fingerprintsOld)
    96  
    97  	log.Printf("Got %d valid fingerprints\n", len(outputFingerprints.Apps))
    98  
    99  	fingerprintsFile, err := os.OpenFile(*fingerprints, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o666)
   100  	if err != nil {
   101  		log.Fatalf("Could not open fingerprints file %s: %s\n", *fingerprints, err)
   102  	}
   103  
   104  	// sort map keys and pretty print the json to make git diffs useful
   105  
   106  	data, err := json.MarshalIndent(outputFingerprints, "", "    ")
   107  	if err != nil {
   108  		log.Fatalf("Could not marshal fingerprints: %s\n", err)
   109  	}
   110  	_, err = fingerprintsFile.Write(data)
   111  	if err != nil {
   112  		log.Fatalf("Could not write fingerprints file: %s\n", err)
   113  	}
   114  	err = fingerprintsFile.Close()
   115  	if err != nil {
   116  		log.Fatalf("Could not close fingerprints file: %s\n", err)
   117  	}
   118  }
   119  
   120  func gatherFingerprintsFromURL(URL string, fingerprints *Fingerprints) error {
   121  	req, err := http.NewRequest(http.MethodGet, URL, nil)
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	resp, err := http.DefaultClient.Do(req)
   127  	if err != nil {
   128  		return err
   129  	}
   130  	defer resp.Body.Close()
   131  
   132  	data, err := io.ReadAll(resp.Body)
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	fingerprintsOld := &Fingerprints{}
   138  	err = json.NewDecoder(bytes.NewReader(data)).Decode(&fingerprintsOld.Apps)
   139  	if err != nil {
   140  		return err
   141  	}
   142  
   143  	for k, v := range fingerprintsOld.Apps {
   144  		fingerprints.Apps[k] = v
   145  	}
   146  	return nil
   147  }
   148  
   149  func normalizeFingerprints(fingerprints *Fingerprints) *OutputFingerprints {
   150  	outputFingerprints := &OutputFingerprints{Apps: make(map[string]OutputFingerprint)}
   151  
   152  	for app, fingerprint := range fingerprints.Apps {
   153  		output := OutputFingerprint{
   154  			Cats:        fingerprint.Cats,
   155  			Cookies:     make(map[string]string),
   156  			Headers:     make(map[string]string),
   157  			Meta:        make(map[string][]string),
   158  			Description: fingerprint.Description,
   159  			Website:     fingerprint.Website,
   160  			CPE:         fingerprint.CPE,
   161  		}
   162  
   163  		for cookie, value := range fingerprint.Cookies {
   164  			output.Cookies[strings.ToLower(cookie)] = strings.ToLower(value)
   165  		}
   166  		for js := range fingerprint.JS {
   167  			output.JS = append(output.JS, strings.ToLower(js))
   168  		}
   169  		sort.Strings(output.JS)
   170  
   171  		for header, pattern := range fingerprint.Headers {
   172  			output.Headers[strings.ToLower(header)] = strings.ToLower(pattern)
   173  		}
   174  
   175  		// Use reflection type switch for determining HTML tag type
   176  		if fingerprint.HTML != nil {
   177  			v := reflect.ValueOf(fingerprint.HTML)
   178  
   179  			switch v.Kind() {
   180  			case reflect.String:
   181  				data := v.Interface().(string)
   182  				output.HTML = append(output.HTML, strings.ToLower(data))
   183  			case reflect.Slice:
   184  				data := v.Interface().([]interface{})
   185  
   186  				for _, pattern := range data {
   187  					pat := pattern.(string)
   188  					output.HTML = append(output.HTML, strings.ToLower(pat))
   189  				}
   190  			}
   191  
   192  			sort.Strings(output.HTML)
   193  		}
   194  
   195  		// Use reflection type switch for determining Script type
   196  		if fingerprint.Script != nil {
   197  			v := reflect.ValueOf(fingerprint.Script)
   198  
   199  			switch v.Kind() {
   200  			case reflect.String:
   201  				data := v.Interface().(string)
   202  				output.Script = append(output.Script, strings.ToLower(data))
   203  			case reflect.Slice:
   204  				data := v.Interface().([]interface{})
   205  				for _, pattern := range data {
   206  					pat := pattern.(string)
   207  					output.Script = append(output.Script, strings.ToLower(pat))
   208  				}
   209  			}
   210  
   211  			sort.Strings(output.Script)
   212  		}
   213  
   214  		// Use reflection type switch for determining ScriptSrc type
   215  		if fingerprint.ScriptSrc != nil {
   216  			v := reflect.ValueOf(fingerprint.ScriptSrc)
   217  
   218  			switch v.Kind() {
   219  			case reflect.String:
   220  				data := v.Interface().(string)
   221  				output.ScriptSrc = append(output.ScriptSrc, strings.ToLower(data))
   222  			case reflect.Slice:
   223  				data := v.Interface().([]interface{})
   224  				for _, pattern := range data {
   225  					pat := pattern.(string)
   226  					output.ScriptSrc = append(output.ScriptSrc, strings.ToLower(pat))
   227  				}
   228  			}
   229  
   230  			sort.Strings(output.ScriptSrc)
   231  		}
   232  
   233  		for header, pattern := range fingerprint.Meta {
   234  			v := reflect.ValueOf(pattern)
   235  
   236  			switch v.Kind() {
   237  			case reflect.String:
   238  				data := strings.ToLower(v.Interface().(string))
   239  				if data == "" {
   240  					output.Meta[strings.ToLower(header)] = []string{}
   241  				} else {
   242  					output.Meta[strings.ToLower(header)] = []string{data}
   243  				}
   244  			case reflect.Slice:
   245  				data := v.Interface().([]interface{})
   246  
   247  				final := []string{}
   248  				for _, pattern := range data {
   249  					pat := pattern.(string)
   250  					final = append(final, strings.ToLower(pat))
   251  				}
   252  				sort.Strings(final)
   253  				output.Meta[strings.ToLower(header)] = final
   254  			}
   255  		}
   256  
   257  		// Use reflection type switch for determining "Implies" tag type
   258  		if fingerprint.Implies != nil {
   259  			v := reflect.ValueOf(fingerprint.Implies)
   260  
   261  			switch v.Kind() {
   262  			case reflect.String:
   263  				data := v.Interface().(string)
   264  				output.Implies = append(output.Implies, data)
   265  			case reflect.Slice:
   266  				data := v.Interface().([]interface{})
   267  				for _, pattern := range data {
   268  					pat := pattern.(string)
   269  					output.Implies = append(output.Implies, pat)
   270  				}
   271  			}
   272  
   273  			sort.Strings(output.Implies)
   274  		}
   275  
   276  		// Use reflection type switch for determining CSS tag type
   277  		if fingerprint.CSS != nil {
   278  			v := reflect.ValueOf(fingerprint.CSS)
   279  
   280  			switch v.Kind() {
   281  			case reflect.String:
   282  				data := v.Interface().(string)
   283  				output.CSS = append(output.CSS, data)
   284  			case reflect.Slice:
   285  				data := v.Interface().([]interface{})
   286  				for _, pattern := range data {
   287  					pat := pattern.(string)
   288  					output.CSS = append(output.CSS, pat)
   289  				}
   290  			}
   291  
   292  			sort.Strings(output.CSS)
   293  		}
   294  
   295  		// Only add if the fingerprint is valid
   296  		outputFingerprints.Apps[app] = output
   297  	}
   298  	return outputFingerprints
   299  }