github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/templates/compile.go (about)

     1  package templates
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"reflect"
     7  
     8  	"github.com/pkg/errors"
     9  	"gopkg.in/yaml.v2"
    10  
    11  	"github.com/projectdiscovery/nuclei/v2/pkg/operators"
    12  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
    13  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/executer"
    14  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/offlinehttp"
    15  	"github.com/projectdiscovery/nuclei/v2/pkg/templates/cache"
    16  	"github.com/projectdiscovery/nuclei/v2/pkg/utils"
    17  	"github.com/projectdiscovery/retryablehttp-go"
    18  	stringsutil "github.com/projectdiscovery/utils/strings"
    19  )
    20  
    21  var (
    22  	ErrCreateTemplateExecutor          = errors.New("cannot create template executer")
    23  	ErrIncompatibleWithOfflineMatching = errors.New("template can't be used for offline matching")
    24  )
    25  
    26  var parsedTemplatesCache *cache.Templates
    27  
    28  func init() {
    29  	parsedTemplatesCache = cache.New()
    30  }
    31  
    32  // Parse parses a yaml request template file
    33  // TODO make sure reading from the disk the template parsing happens once: see parsers.ParseTemplate vs templates.Parse
    34  //
    35  //nolint:gocritic // this cannot be passed by pointer
    36  func Parse(filePath string, preprocessor Preprocessor, options protocols.ExecutorOptions) (*Template, error) {
    37  	if !options.DoNotCache {
    38  		if value, err := parsedTemplatesCache.Has(filePath); value != nil {
    39  			return value.(*Template), err
    40  		}
    41  	}
    42  
    43  	var reader io.ReadCloser
    44  	if utils.IsURL(filePath) {
    45  		// use retryablehttp (tls verification is enabled by default in the standard library)
    46  		resp, err := retryablehttp.DefaultClient().Get(filePath)
    47  		if err != nil {
    48  			return nil, err
    49  		}
    50  		reader = resp.Body
    51  	} else {
    52  		var err error
    53  		reader, err = options.Catalog.OpenFile(filePath)
    54  		if err != nil {
    55  			return nil, err
    56  		}
    57  	}
    58  	defer reader.Close()
    59  	options.TemplatePath = filePath
    60  	template, err := ParseTemplateFromReader(reader, preprocessor, options)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	// Compile the workflow request
    65  	if len(template.Workflows) > 0 {
    66  		compiled := &template.Workflow
    67  
    68  		compileWorkflow(filePath, preprocessor, &options, compiled, options.WorkflowLoader)
    69  		template.CompiledWorkflow = compiled
    70  		template.CompiledWorkflow.Options = &options
    71  	}
    72  	template.Path = filePath
    73  	if !options.DoNotCache {
    74  		parsedTemplatesCache.Store(filePath, template, err)
    75  	}
    76  	return template, nil
    77  }
    78  
    79  // parseSelfContainedRequests parses the self contained template requests.
    80  func (template *Template) parseSelfContainedRequests() {
    81  	if template.Signature.Value.String() != "" {
    82  		for _, request := range template.RequestsHTTP {
    83  			request.Signature = template.Signature
    84  		}
    85  	}
    86  	if !template.SelfContained {
    87  		return
    88  	}
    89  	for _, request := range template.RequestsHTTP {
    90  		request.SelfContained = true
    91  	}
    92  	for _, request := range template.RequestsNetwork {
    93  		request.SelfContained = true
    94  	}
    95  }
    96  
    97  // Requests returns the total request count for the template
    98  func (template *Template) Requests() int {
    99  	return len(template.RequestsDNS) +
   100  		len(template.RequestsHTTP) +
   101  		len(template.RequestsFile) +
   102  		len(template.RequestsNetwork) +
   103  		len(template.RequestsHeadless) +
   104  		len(template.Workflows) +
   105  		len(template.RequestsSSL) +
   106  		len(template.RequestsWebsocket) +
   107  		len(template.RequestsWHOIS)
   108  }
   109  
   110  // compileProtocolRequests compiles all the protocol requests for the template
   111  func (template *Template) compileProtocolRequests(options protocols.ExecutorOptions) error {
   112  	templateRequests := template.Requests()
   113  
   114  	if templateRequests == 0 {
   115  		return fmt.Errorf("no requests defined for %s", template.ID)
   116  	}
   117  
   118  	if options.Options.OfflineHTTP {
   119  		return template.compileOfflineHTTPRequest(options)
   120  	}
   121  
   122  	var requests []protocols.Request
   123  
   124  	if len(template.RequestsDNS) > 0 {
   125  		requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsDNS)...)
   126  	}
   127  	if len(template.RequestsFile) > 0 {
   128  		requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsFile)...)
   129  	}
   130  	if len(template.RequestsNetwork) > 0 {
   131  		requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsNetwork)...)
   132  	}
   133  	if len(template.RequestsHTTP) > 0 {
   134  		requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsHTTP)...)
   135  	}
   136  	if len(template.RequestsHeadless) > 0 && options.Options.Headless {
   137  		requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsHeadless)...)
   138  	}
   139  	if len(template.RequestsSSL) > 0 {
   140  		requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsSSL)...)
   141  	}
   142  	if len(template.RequestsWebsocket) > 0 {
   143  		requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsWebsocket)...)
   144  	}
   145  	if len(template.RequestsWHOIS) > 0 {
   146  		requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsWHOIS)...)
   147  	}
   148  	template.Executer = executer.NewExecuter(requests, &options)
   149  	return nil
   150  }
   151  
   152  // convertRequestToProtocolsRequest is a convenience wrapper to convert
   153  // arbitrary interfaces which are slices of requests from the template to a
   154  // slice of protocols.Request interface items.
   155  func (template *Template) convertRequestToProtocolsRequest(requests interface{}) []protocols.Request {
   156  	switch reflect.TypeOf(requests).Kind() {
   157  	case reflect.Slice:
   158  		s := reflect.ValueOf(requests)
   159  
   160  		requestSlice := make([]protocols.Request, s.Len())
   161  		for i := 0; i < s.Len(); i++ {
   162  			value := s.Index(i)
   163  			valueInterface := value.Interface()
   164  			requestSlice[i] = valueInterface.(protocols.Request)
   165  		}
   166  		return requestSlice
   167  	}
   168  	return nil
   169  }
   170  
   171  // compileOfflineHTTPRequest iterates all requests if offline http mode is
   172  // specified and collects all matchers for all the base request templates
   173  // (those with URL {{BaseURL}} and it's slash variation.)
   174  func (template *Template) compileOfflineHTTPRequest(options protocols.ExecutorOptions) error {
   175  	operatorsList := []*operators.Operators{}
   176  
   177  mainLoop:
   178  	for _, req := range template.RequestsHTTP {
   179  		hasPaths := len(req.Path) > 0
   180  		if !hasPaths {
   181  			break mainLoop
   182  		}
   183  		for _, path := range req.Path {
   184  			pathIsBaseURL := stringsutil.EqualFoldAny(path, "{{BaseURL}}", "{{BaseURL}}/", "/")
   185  			if !pathIsBaseURL {
   186  				break mainLoop
   187  			}
   188  		}
   189  		operatorsList = append(operatorsList, &req.Operators)
   190  	}
   191  	if len(operatorsList) > 0 {
   192  		options.Operators = operatorsList
   193  		template.Executer = executer.NewExecuter([]protocols.Request{&offlinehttp.Request{}}, &options)
   194  		return nil
   195  	}
   196  
   197  	return ErrIncompatibleWithOfflineMatching
   198  }
   199  
   200  // ParseTemplateFromReader reads the template from reader
   201  // returns the parsed template
   202  func ParseTemplateFromReader(reader io.Reader, preprocessor Preprocessor, options protocols.ExecutorOptions) (*Template, error) {
   203  	template := &Template{}
   204  	data, err := io.ReadAll(reader)
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  
   209  	data = template.expandPreprocessors(data)
   210  	if preprocessor != nil {
   211  		data = preprocessor.Process(data)
   212  	}
   213  
   214  	if err := yaml.Unmarshal(data, template); err != nil {
   215  		return nil, err
   216  	}
   217  
   218  	if utils.IsBlank(template.Info.Name) {
   219  		return nil, errors.New("no template name field provided")
   220  	}
   221  	if template.Info.Authors.IsEmpty() {
   222  		return nil, errors.New("no template author field provided")
   223  	}
   224  
   225  	// Setting up variables regarding template metadata
   226  	options.TemplateID = template.ID
   227  	options.TemplateInfo = template.Info
   228  	options.StopAtFirstMatch = template.StopAtFirstMatch
   229  
   230  	if template.Variables.Len() > 0 {
   231  		options.Variables = template.Variables
   232  	}
   233  
   234  	options.Constants = template.Constants
   235  
   236  	// If no requests, and it is also not a workflow, return error.
   237  	if template.Requests() == 0 {
   238  		return nil, fmt.Errorf("no requests defined for %s", template.ID)
   239  	}
   240  
   241  	if err := template.compileProtocolRequests(options); err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	if template.Executer != nil {
   246  		if err := template.Executer.Compile(); err != nil {
   247  			return nil, errors.Wrap(err, "could not compile request")
   248  		}
   249  		template.TotalRequests = template.Executer.Requests()
   250  	}
   251  	if template.Executer == nil && template.CompiledWorkflow == nil {
   252  		return nil, ErrCreateTemplateExecutor
   253  	}
   254  	template.parseSelfContainedRequests()
   255  
   256  	return template, nil
   257  }