github.com/chainreactors/fingers@v1.2.1/wappalyzer/fingerprint_body.go (about) 1 package wappalyzer 2 3 import ( 4 "bytes" 5 "github.com/chainreactors/fingers/common" 6 "unsafe" 7 8 "golang.org/x/net/html" 9 ) 10 11 // checkBody checks for fingerprints in the HTML body 12 func (engine *Wappalyze) checkBody(body []byte) common.Frameworks { 13 technologies := make(common.Frameworks) 14 bodyString := unsafeToString(body) 15 technologies.Merge(engine.fingerprints.matchString(bodyString, htmlPart)) 16 17 // Tokenize the HTML document and check for fingerprints as required 18 tokenizer := html.NewTokenizer(bytes.NewReader(body)) 19 20 for { 21 tt := tokenizer.Next() 22 switch tt { 23 case html.ErrorToken: 24 return technologies 25 case html.StartTagToken: 26 token := tokenizer.Token() 27 switch token.Data { 28 case "script": 29 // Check if the script tag has a source file to check 30 source, found := getScriptSource(token) 31 if found { 32 // Check the script tags for script fingerprints 33 technologies.Merge(engine.fingerprints.matchString(source, scriptPart)) 34 continue 35 } 36 37 // Check the text attribute of the tag for javascript based technologies. 38 // The next token should be the contents of the script tag 39 if tokenType := tokenizer.Next(); tokenType != html.TextToken { 40 continue 41 } 42 43 // TODO: JS requires a running VM, for checking properties. Only 44 // possible with headless for now :( 45 46 // data := tokenizer.Token().Data 47 // technologies = append( 48 // technologies, 49 // s.fingerprints.matchString(data, jsPart)..., 50 // ) 51 case "meta": 52 // For meta tag, we are only interested in name and content attributes. 53 name, content, found := getMetaNameAndContent(token) 54 if !found { 55 continue 56 } 57 technologies.Merge(engine.fingerprints.matchKeyValueString(name, content, metaPart)) 58 } 59 case html.SelfClosingTagToken: 60 token := tokenizer.Token() 61 if token.Data != "meta" { 62 continue 63 } 64 65 // Parse the meta tag and check for tech 66 name, content, found := getMetaNameAndContent(token) 67 if !found { 68 continue 69 } 70 technologies.Merge(engine.fingerprints.matchKeyValueString(name, content, metaPart)) 71 } 72 } 73 } 74 75 func (engine *Wappalyze) getTitle(body []byte) string { 76 var title string 77 78 // Tokenize the HTML document and check for fingerprints as required 79 tokenizer := html.NewTokenizer(bytes.NewReader(body)) 80 81 for { 82 tt := tokenizer.Next() 83 switch tt { 84 case html.ErrorToken: 85 return title 86 case html.StartTagToken: 87 token := tokenizer.Token() 88 switch token.Data { 89 case "title": 90 // Next text token will be the actual title of the page 91 if tokenType := tokenizer.Next(); tokenType != html.TextToken { 92 continue 93 } 94 title = tokenizer.Token().Data 95 } 96 } 97 } 98 } 99 100 // getMetaNameAndContent gets name and content attributes from meta html token 101 func getMetaNameAndContent(token html.Token) (string, string, bool) { 102 if len(token.Attr) < keyValuePairLength { 103 return "", "", false 104 } 105 106 var name, content string 107 for _, attr := range token.Attr { 108 switch attr.Key { 109 case "name": 110 name = attr.Val 111 case "content": 112 content = attr.Val 113 } 114 } 115 return name, content, true 116 } 117 118 // getScriptSource gets src tag from a script tag 119 func getScriptSource(token html.Token) (string, bool) { 120 if len(token.Attr) < 1 { 121 return "", false 122 } 123 124 var source string 125 for _, attr := range token.Attr { 126 switch attr.Key { 127 case "src": 128 source = attr.Val 129 } 130 } 131 return source, true 132 } 133 134 // unsafeToString converts a byte slice to string and does it with 135 // zero allocations. 136 // 137 // NOTE: This function should only be used if its certain that the underlying 138 // array has not been manipulated. 139 // 140 // Reference - https://github.com/golang/go/issues/25484 141 func unsafeToString(data []byte) string { 142 return *(*string)(unsafe.Pointer(&data)) 143 }