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  }