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 }