mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2026-11-10 04:01:31 -10:00
A step whose subtemplates loaded applied every exclusion, -es included. Most workflows start from a detection template with info severity, so -es info, often used to hide low-value results, left them running nothing, while -s low,medium,high,critical, which selects the same results, still ran them. A step with subtemplates reports no results of its own beyond late out-of-band matches, so excluding its severity mostly keeps the checks below it from running. Such a step now skips -es as it skips -s. Signed-off-by: Dwi Siswanto <[email protected]>
156 lines
6.0 KiB
Go
156 lines
6.0 KiB
Go
package workflow
|
|
|
|
import (
|
|
"maps"
|
|
"slices"
|
|
"testing"
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/model"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/dns"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
|
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/utils/stats"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/workflows"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type countingCatalog struct {
|
|
catalog.Catalog
|
|
paths []string
|
|
walks int
|
|
}
|
|
|
|
func (c *countingCatalog) GetTemplatesPath([]string) ([]string, map[string]error) {
|
|
c.walks++
|
|
return c.paths, nil
|
|
}
|
|
|
|
type countingParser struct {
|
|
templates map[string]*templates.Template
|
|
parses map[string]int
|
|
}
|
|
|
|
func (p *countingParser) ParseTemplate(path string, _ catalog.Catalog) (any, error) {
|
|
p.parses[path]++
|
|
return p.templates[path], nil
|
|
}
|
|
|
|
func (p *countingParser) LoadTemplate(string, any, []string, catalog.Catalog) (bool, error) {
|
|
panic("tag lookups must not reparse through LoadTemplate")
|
|
}
|
|
|
|
func (p *countingParser) LoadWorkflow(string, catalog.Catalog) (bool, error) {
|
|
return false, nil
|
|
}
|
|
|
|
func TestGetTemplatePathsByTagsReadsTemplatesDirectoryOnce(t *testing.T) {
|
|
template := func(id, tags string) *templates.Template {
|
|
return &templates.Template{ID: id, Info: model.Info{
|
|
Name: id,
|
|
Authors: stringslice.StringSlice{Value: "author"},
|
|
Tags: stringslice.StringSlice{Value: tags},
|
|
SeverityHolder: severity.Holder{Severity: severity.Info},
|
|
}}
|
|
}
|
|
parser := &countingParser{
|
|
templates: map[string]*templates.Template{
|
|
"wordpress.yaml": template("wordpress", "wordpress"),
|
|
"jira.yaml": template("jira", "jira"),
|
|
},
|
|
parses: make(map[string]int),
|
|
}
|
|
cat := &countingCatalog{paths: []string{"wordpress.yaml", "jira.yaml"}}
|
|
loader, err := NewLoader(&protocols.ExecutorOptions{Options: types.DefaultOptions(), Catalog: cat, Parser: parser})
|
|
require.NoError(t, err)
|
|
walksBeforeLookups := cat.walks
|
|
|
|
require.Equal(t, []string{"wordpress.yaml"}, loader.GetTemplatePathsByTags([]string{"wordpress"}, false))
|
|
require.Equal(t, []string{"jira.yaml"}, loader.GetTemplatePathsByTags([]string{"jira"}, false))
|
|
|
|
require.Equal(t, 1, cat.walks-walksBeforeLookups, "templates directory walked per lookup")
|
|
require.Equal(t, map[string]int{"wordpress.yaml": 1, "jira.yaml": 1}, parser.parses, "templates parsed per lookup")
|
|
}
|
|
|
|
func TestGetTemplatePathsByTagsNoValidateAppliesExclusionsButSeverity(t *testing.T) {
|
|
template := func(id string, tags ...string) *templates.Template {
|
|
return &templates.Template{ID: id, Info: model.Info{
|
|
Name: id,
|
|
Authors: stringslice.StringSlice{Value: "author"},
|
|
Tags: stringslice.StringSlice{Value: tags},
|
|
SeverityHolder: severity.Holder{Severity: severity.Info},
|
|
}}
|
|
}
|
|
excludedSeverity := template("excluded-severity", "step")
|
|
excludedSeverity.Info.SeverityHolder.Severity = severity.Low
|
|
excludedProtocol := template("excluded-protocol", "step")
|
|
excludedProtocol.RequestsDNS = []*dns.Request{{}}
|
|
workflow := template("workflow", "step")
|
|
workflow.Workflows = []*workflows.WorkflowTemplate{{Template: "selected.yaml"}}
|
|
parser := &countingParser{
|
|
templates: map[string]*templates.Template{
|
|
"selected.yaml": template("selected", "step"),
|
|
"included.yaml": template("included", "step", "included"),
|
|
"excluded-tag.yaml": template("excluded-tag", "step", "excluded"),
|
|
"excluded-id.yaml": template("excluded-id", "step"),
|
|
"excluded-severity.yaml": excludedSeverity,
|
|
"excluded-protocol.yaml": excludedProtocol,
|
|
"untagged.yaml": template("untagged", "other"),
|
|
"workflow.yaml": workflow,
|
|
},
|
|
parses: make(map[string]int),
|
|
}
|
|
options := types.DefaultOptions()
|
|
options.IncludeIds = []string{"untagged"}
|
|
options.ExcludeTags = []string{"excluded", "included"}
|
|
options.IncludeTags = []string{"included"}
|
|
options.ExcludeIds = []string{"excluded-id"}
|
|
options.ExcludeSeverities = severity.Severities{severity.Low}
|
|
options.ExcludeProtocols = templateTypes.ProtocolTypes{templateTypes.DNSProtocol}
|
|
cat := &countingCatalog{paths: slices.Collect(maps.Keys(parser.templates))}
|
|
loader, err := NewLoader(&protocols.ExecutorOptions{Options: options, Catalog: cat, Parser: parser})
|
|
require.NoError(t, err)
|
|
|
|
require.Empty(t, loader.GetTemplatePathsByTags([]string{"step"}, false))
|
|
loaded := loader.GetTemplatePathsByTags([]string{"step"}, true)
|
|
require.ElementsMatch(t, []string{"selected.yaml", "included.yaml", "excluded-severity.yaml"}, loaded)
|
|
}
|
|
|
|
func TestGetTemplatePathsByTagsCountsSyntaxWarningsOfLoadedTemplates(t *testing.T) {
|
|
// a template without severity has a syntax warning
|
|
unrated := &templates.Template{ID: "unrated", Info: model.Info{
|
|
Name: "unrated",
|
|
Authors: stringslice.StringSlice{Value: "author"},
|
|
Tags: stringslice.StringSlice{Value: "step"},
|
|
}}
|
|
tests := []struct {
|
|
name string
|
|
ids []string
|
|
warnings int64
|
|
}{
|
|
{name: "loaded", warnings: 1},
|
|
{name: "filtered out", ids: []string{"other"}, warnings: 0},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
parser := &countingParser{
|
|
templates: map[string]*templates.Template{"unrated.yaml": unrated},
|
|
parses: make(map[string]int),
|
|
}
|
|
options := types.DefaultOptions()
|
|
options.IncludeIds = tc.ids
|
|
cat := &countingCatalog{paths: []string{"unrated.yaml"}}
|
|
loader, err := NewLoader(&protocols.ExecutorOptions{Options: options, Catalog: cat, Parser: parser})
|
|
require.NoError(t, err)
|
|
|
|
before := stats.GetValue(templates.TemplateSyntaxWarningStats)
|
|
loader.GetTemplatePathsByTags([]string{"step"}, false)
|
|
require.Equal(t, tc.warnings, stats.GetValue(templates.TemplateSyntaxWarningStats)-before)
|
|
})
|
|
}
|
|
}
|