mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-17 21:25:27 +00:00
Optimize template validation
This commit is contained in:
parent
095e78e431
commit
36b1c08edc
@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/projectdiscovery/gologger"
|
"github.com/projectdiscovery/gologger"
|
||||||
@ -91,6 +92,11 @@ func validateOptions(options *types.Options) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if options.Validate {
|
||||||
|
validateTemplatePaths(options.TemplatesDirectory, options.Templates, options.Workflows)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,3 +155,21 @@ func loadResolvers(options *types.Options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateTemplatePaths(templatesDirectory string, templatePaths, workflowPaths []string) {
|
||||||
|
allGivenTemplatePaths := append(templatePaths, workflowPaths...)
|
||||||
|
|
||||||
|
for _, templatePath := range allGivenTemplatePaths {
|
||||||
|
if templatesDirectory != templatePath && filepath.IsAbs(templatePath) {
|
||||||
|
fileInfo, err := os.Stat(templatePath)
|
||||||
|
if err == nil && fileInfo.IsDir() {
|
||||||
|
relativizedPath, err2 := filepath.Rel(templatesDirectory, templatePath)
|
||||||
|
if err2 != nil || (len(relativizedPath) >= 2 && relativizedPath[:2] == "..") {
|
||||||
|
gologger.Warning().Msgf("The given path (%s) is outside the default template directory path (%s)! "+
|
||||||
|
"Referenced sub-templates with relative paths in workflows will be resolved against the default template directory.", templatePath, templatesDirectory)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -340,8 +340,8 @@ func (r *Runner) RunEnumeration() error {
|
|||||||
return errors.Wrap(err, "could not load templates from config")
|
return errors.Wrap(err, "could not load templates from config")
|
||||||
}
|
}
|
||||||
if r.options.Validate {
|
if r.options.Validate {
|
||||||
if !store.ValidateTemplates(r.options.Templates, r.options.Workflows) {
|
if err := store.ValidateTemplates(r.options.Templates, r.options.Workflows); err != nil {
|
||||||
return errors.New("an error occurred during templates validation")
|
return err
|
||||||
}
|
}
|
||||||
gologger.Info().Msgf("All templates validated successfully\n")
|
gologger.Info().Msgf("All templates validated successfully\n")
|
||||||
return nil // exit
|
return nil // exit
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package loader
|
package loader
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/projectdiscovery/gologger"
|
"github.com/projectdiscovery/gologger"
|
||||||
@ -93,49 +94,66 @@ func (store *Store) Load() {
|
|||||||
|
|
||||||
// ValidateTemplates takes a list of templates and validates them
|
// ValidateTemplates takes a list of templates and validates them
|
||||||
// erroring out on discovering any faulty templates.
|
// erroring out on discovering any faulty templates.
|
||||||
func (store *Store) ValidateTemplates(templatesList, workflowsList []string) bool {
|
func (store *Store) ValidateTemplates(templatesList, workflowsList []string) error {
|
||||||
templatePaths := store.config.Catalog.GetTemplatesPath(templatesList)
|
templatePaths := store.config.Catalog.GetTemplatesPath(templatesList)
|
||||||
workflowPaths := store.config.Catalog.GetTemplatesPath(workflowsList)
|
workflowPaths := store.config.Catalog.GetTemplatesPath(workflowsList)
|
||||||
|
|
||||||
filteredTemplatePaths := store.pathFilter.Match(templatePaths)
|
filteredTemplatePaths := store.pathFilter.Match(templatePaths)
|
||||||
filteredWorkflowPaths := store.pathFilter.Match(workflowPaths)
|
filteredWorkflowPaths := store.pathFilter.Match(workflowPaths)
|
||||||
|
|
||||||
notErrored := true
|
if areTemplatesValid(store, filteredTemplatePaths) && areWorkflowsValid(store, filteredWorkflowPaths) {
|
||||||
errorValidationFunc := func(message string, template string, err error) {
|
return nil
|
||||||
if strings.Contains(err.Error(), "cannot create template executer") {
|
}
|
||||||
return
|
return errors.New("an error occurred during templates validation")
|
||||||
|
}
|
||||||
|
|
||||||
|
func areWorkflowsValid(store *Store, filteredWorkflowPaths map[string]struct{}) bool {
|
||||||
|
areWorkflowsValid := true
|
||||||
|
for workflowPath := range filteredWorkflowPaths {
|
||||||
|
if _, err := parsers.LoadWorkflow(workflowPath, store.tagFilter); err != nil {
|
||||||
|
if isParsingError("Error occurred loading workflow %s: %s\n", workflowPath, err) {
|
||||||
|
areWorkflowsValid = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := templates.Parse(workflowPath, store.preprocessor, store.config.ExecutorOptions); err != nil {
|
||||||
|
if isParsingError("Error occurred parsing workflow %s: %s\n", workflowPath, err) {
|
||||||
|
areWorkflowsValid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return areWorkflowsValid
|
||||||
|
}
|
||||||
|
|
||||||
|
func areTemplatesValid(store *Store, filteredTemplatePaths map[string]struct{}) bool {
|
||||||
|
areTemplatesValid := true
|
||||||
|
for templatePath := range filteredTemplatePaths {
|
||||||
|
if _, err := parsers.LoadTemplate(templatePath, store.tagFilter, nil); err != nil {
|
||||||
|
if isParsingError("Error occurred loading template %s: %s\n", templatePath, err) {
|
||||||
|
areTemplatesValid = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := templates.Parse(templatePath, store.preprocessor, store.config.ExecutorOptions); err != nil {
|
||||||
|
if isParsingError("Error occurred parsing template %s: %s\n", templatePath, err) {
|
||||||
|
areTemplatesValid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return areTemplatesValid
|
||||||
|
}
|
||||||
|
|
||||||
|
func isParsingError(message string, template string, err error) bool {
|
||||||
|
if strings.Contains(err.Error(), templates.TemplateExecuterCreationErrorMessage) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
if err == filter.ErrExcluded {
|
if err == filter.ErrExcluded {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
notErrored = false
|
|
||||||
gologger.Error().Msgf(message, template, err)
|
gologger.Error().Msgf(message, template, err)
|
||||||
}
|
return true
|
||||||
for templatePath := range filteredTemplatePaths {
|
|
||||||
_, err := parsers.LoadTemplate(templatePath, store.tagFilter, nil)
|
|
||||||
if err != nil {
|
|
||||||
errorValidationFunc("Error occurred loading template %s: %s\n", templatePath, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, err = templates.Parse(templatePath, store.preprocessor, store.config.ExecutorOptions)
|
|
||||||
if err != nil {
|
|
||||||
errorValidationFunc("Error occurred parsing template %s: %s\n", templatePath, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for workflowPath := range filteredWorkflowPaths {
|
|
||||||
_, err := parsers.LoadWorkflow(workflowPath, store.tagFilter)
|
|
||||||
if err != nil {
|
|
||||||
errorValidationFunc("Error occurred loading workflow %s: %s\n", workflowPath, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, err = templates.Parse(workflowPath, store.preprocessor, store.config.ExecutorOptions)
|
|
||||||
if err != nil {
|
|
||||||
errorValidationFunc("Error occurred parsing workflow %s: %s\n", workflowPath, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return notErrored
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadTemplates takes a list of templates and returns paths for them
|
// LoadTemplates takes a list of templates and returns paths for them
|
||||||
|
|||||||
@ -87,8 +87,19 @@ func validateMandatoryInfoFields(info *model.Info) error {
|
|||||||
|
|
||||||
var fieldErrorRegexp = regexp.MustCompile(`not found in`)
|
var fieldErrorRegexp = regexp.MustCompile(`not found in`)
|
||||||
|
|
||||||
|
var parsedTemplatesCache = make(map[string]*parsedTemplateErrHolder, 2500)
|
||||||
|
|
||||||
|
type parsedTemplateErrHolder struct {
|
||||||
|
template *templates.Template
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
// ParseTemplate parses a template and returns a *templates.Template structure
|
// ParseTemplate parses a template and returns a *templates.Template structure
|
||||||
func ParseTemplate(templatePath string) (*templates.Template, error) {
|
func ParseTemplate(templatePath string) (*templates.Template, error) {
|
||||||
|
if value, found := parsedTemplatesCache[templatePath]; found {
|
||||||
|
return value.template, value.err
|
||||||
|
}
|
||||||
|
|
||||||
f, err := os.Open(templatePath)
|
f, err := os.Open(templatePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -102,12 +113,15 @@ func ParseTemplate(templatePath string) (*templates.Template, error) {
|
|||||||
|
|
||||||
template := &templates.Template{}
|
template := &templates.Template{}
|
||||||
err = yaml.UnmarshalStrict(data, template)
|
err = yaml.UnmarshalStrict(data, template)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if fieldErrorRegexp.MatchString(err.Error()) {
|
if fieldErrorRegexp.MatchString(err.Error()) {
|
||||||
gologger.Warning().Msgf("Unrecognized fields in template %s: %s", templatePath, err)
|
gologger.Warning().Msgf("Unrecognized fields in template %s: %s", templatePath, err)
|
||||||
return template, nil
|
parsedTemplatesCache[templatePath] = &parsedTemplateErrHolder{template: template, err: err}
|
||||||
|
return template, err
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
parsedTemplatesCache[templatePath] = &parsedTemplateErrHolder{template: template, err: nil}
|
||||||
return template, nil
|
return template, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,13 +29,21 @@ func NewLoader(options *protocols.ExecuterOptions) (model.WorkflowLoader, error)
|
|||||||
return &workflowLoader{pathFilter: pathFilter, tagFilter: tagFilter, options: options}, nil
|
return &workflowLoader{pathFilter: pathFilter, tagFilter: tagFilter, options: options}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var loadedWorkflowTemplateCache = make(map[string]struct{})
|
||||||
|
|
||||||
func (w *workflowLoader) GetTemplatePathsByTags(templateTags []string) []string {
|
func (w *workflowLoader) GetTemplatePathsByTags(templateTags []string) []string {
|
||||||
includedTemplates := w.options.Catalog.GetTemplatesPath([]string{w.options.Options.TemplatesDirectory})
|
includedTemplates := w.options.Catalog.GetTemplatesPath([]string{w.options.Options.TemplatesDirectory})
|
||||||
templatePathMap := w.pathFilter.Match(includedTemplates)
|
templatePathMap := w.pathFilter.Match(includedTemplates)
|
||||||
|
|
||||||
loadedTemplates := make([]string, 0, len(templatePathMap))
|
loadedTemplates := make([]string, 0, len(templatePathMap))
|
||||||
for templatePath := range templatePathMap {
|
for templatePath := range templatePathMap {
|
||||||
|
|
||||||
|
if _, found := loadedWorkflowTemplateCache[templatePath]; found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
loaded, err := LoadTemplate(templatePath, w.tagFilter, templateTags)
|
loaded, err := LoadTemplate(templatePath, w.tagFilter, templateTags)
|
||||||
|
loadedWorkflowTemplateCache[templatePath] = struct{}{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gologger.Warning().Msgf("Could not load template %s: %s\n", templatePath, err)
|
gologger.Warning().Msgf("Could not load template %s: %s\n", templatePath, err)
|
||||||
} else if loaded {
|
} else if loaded {
|
||||||
|
|||||||
@ -17,9 +17,18 @@ import (
|
|||||||
"github.com/projectdiscovery/nuclei/v2/pkg/utils"
|
"github.com/projectdiscovery/nuclei/v2/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const TemplateExecuterCreationErrorMessage = "cannot create template executer"
|
||||||
|
|
||||||
|
var parsedTemplatesCache = make(map[string]*Template, 2500)
|
||||||
|
|
||||||
// Parse parses a yaml request template file
|
// Parse parses a yaml request template file
|
||||||
//nolint:gocritic // this cannot be passed by pointer
|
//nolint:gocritic // this cannot be passed by pointer
|
||||||
|
// TODO make sure reading from the disk the template parsing happens once: see parsers.ParseTemplate vs templates.Parse
|
||||||
func Parse(filePath string, preprocessor Preprocessor, options protocols.ExecuterOptions) (*Template, error) {
|
func Parse(filePath string, preprocessor Preprocessor, options protocols.ExecuterOptions) (*Template, error) {
|
||||||
|
if value, found := parsedTemplatesCache[filePath]; found {
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
template := &Template{}
|
template := &Template{}
|
||||||
|
|
||||||
f, err := os.Open(filePath)
|
f, err := os.Open(filePath)
|
||||||
@ -127,8 +136,10 @@ func Parse(filePath string, preprocessor Preprocessor, options protocols.Execute
|
|||||||
template.TotalRequests += template.Executer.Requests()
|
template.TotalRequests += template.Executer.Requests()
|
||||||
}
|
}
|
||||||
if template.Executer == nil && template.CompiledWorkflow == nil {
|
if template.Executer == nil && template.CompiledWorkflow == nil {
|
||||||
return nil, errors.New("cannot create template executer")
|
return nil, errors.New(TemplateExecuterCreationErrorMessage)
|
||||||
}
|
}
|
||||||
template.Path = filePath
|
template.Path = filePath
|
||||||
|
|
||||||
|
parsedTemplatesCache[filePath] = template
|
||||||
return template, nil
|
return template, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user