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 }