Skip to content

Commit

Permalink
Workflows restructured to work with filters + tag support
Browse files Browse the repository at this point in the history
  • Loading branch information
Ice3man543 committed Jul 4, 2021
1 parent d45d1c9 commit 0726acc
Show file tree
Hide file tree
Showing 12 changed files with 461 additions and 229 deletions.
4 changes: 2 additions & 2 deletions v2/internal/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ func (r *Runner) RunEnumeration() {
ProjectFile: r.projectFile,
Browser: r.browser,
}
loaderConfig := &loader.Config{
loaderConfig := loader.Config{
Templates: r.options.Templates,
Workflows: r.options.Workflows,
ExcludeTemplates: r.options.ExcludedTemplates,
Expand All @@ -285,7 +285,7 @@ func (r *Runner) RunEnumeration() {
Catalog: r.catalog,
ExecutorOptions: executerOpts,
}
store, err := loader.New(loaderConfig)
store, err := loader.New(&loaderConfig)
if err != nil {
gologger.Fatal().Msgf("Could not load templates from config: %s\n", err)
}
Expand Down
2 changes: 1 addition & 1 deletion v2/internal/runner/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ func (r *Runner) downloadReleaseAndUnzip(ctx context.Context, version, downloadU
return nil, fmt.Errorf("failed to write templates: %s", err)
}

if !r.options.Silent {
if r.options.Verbose {
r.printUpdateChangelog(results, version)
}
checksumFile := path.Join(r.templatesConfig.TemplatesDirectory, ".checksum")
Expand Down
45 changes: 45 additions & 0 deletions v2/pkg/catalog/loader/filter/path_filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package filter

import "github.com/projectdiscovery/nuclei/v2/pkg/catalog"

// PathFilter is a path based template filter
type PathFilter struct {
excludedTemplates []string
alwaysIncludedTemplatesMap map[string]struct{}
}

// PathFilterConfig contains configuraton options for Path based templates Filter
type PathFilterConfig struct {
IncludedTemplates []string
ExcludedTemplates []string
}

// NewPathFilter creates a new path filter from provided config
func NewPathFilter(config *PathFilterConfig, catalog *catalog.Catalog) *PathFilter {
filter := &PathFilter{
excludedTemplates: catalog.GetTemplatesPath(config.ExcludedTemplates),
alwaysIncludedTemplatesMap: make(map[string]struct{}),
}

alwaysIncludeTemplates := catalog.GetTemplatesPath(config.IncludedTemplates)
for _, tpl := range alwaysIncludeTemplates {
filter.alwaysIncludedTemplatesMap[tpl] = struct{}{}
}
return filter
}

// Match performs match for path filter on templates and returns final list
func (p *PathFilter) Match(templates []string) map[string]struct{} {
templatesMap := make(map[string]struct{})
for _, tpl := range templates {
templatesMap[tpl] = struct{}{}
}
for _, template := range p.excludedTemplates {
if _, ok := p.alwaysIncludedTemplatesMap[template]; ok {
continue
} else {
delete(templatesMap, template)
}
}
return templatesMap
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package loader
package filter

import (
"errors"
"strings"
)

// tagFilter is used to filter nuclei tag based execution
type tagFilter struct {
// TagFilter is used to filter nuclei templates for tag based execution
type TagFilter struct {
allowedTags map[string]struct{}
severities map[string]struct{}
authors map[string]struct{}
Expand All @@ -17,15 +17,15 @@ type tagFilter struct {
// ErrExcluded is returned for execluded templates
var ErrExcluded = errors.New("the template was excluded")

// match takes a tag and whether the template was matched from user
// Match takes a tag and whether the template was matched from user
// input and returns true or false using a tag filter.
//
// If the tag was specified in deny list, it will not return true
// unless it is explicitly specified by user in includeTags which is the
// matchAllows section.
//
// It returns true if the tag is specified, or false.
func (t *tagFilter) match(tag, author, severity string) (bool, error) {
func (t *TagFilter) Match(tag, author, severity string) (bool, error) {
matchedAny := false
if len(t.allowedTags) > 0 {
_, ok := t.allowedTags[tag]
Expand Down Expand Up @@ -61,11 +61,66 @@ func (t *tagFilter) match(tag, author, severity string) (bool, error) {
return matchedAny, nil
}

// createTagFilter returns a tag filter for nuclei tag based execution
// MatchWithAllowedTags takes an addition list of allowed tags
// and returns true if the match was successful.
func (t *TagFilter) MatchWithAllowedTags(allowed []string, tag, author, severity string) (bool, error) {
matchedAny := false

allowedMap := make(map[string]struct{})
for _, tag := range allowed {
for _, val := range splitCommaTrim(tag) {
if _, ok := allowedMap[val]; !ok {
allowedMap[val] = struct{}{}
}
}
}
if len(allowedMap) > 0 {
_, ok := allowedMap[tag]
if !ok {
return false, nil
}
matchedAny = true
}
_, ok := t.block[tag]
if ok {
if _, allowOk := t.matchAllows[tag]; allowOk {
return true, nil
}
return false, ErrExcluded
}
if len(t.authors) > 0 {
_, ok = t.authors[author]
if !ok {
return false, nil
}
matchedAny = true
}
if len(t.severities) > 0 {
_, ok = t.severities[severity]
if !ok {
return false, nil
}
matchedAny = true
}
if len(allowedMap) == 0 && len(t.authors) == 0 && len(t.severities) == 0 {
return true, nil
}
return matchedAny, nil
}

type Config struct {
Tags []string
ExcludeTags []string
Authors []string
Severities []string
IncludeTags []string
}

// New returns a tag filter for nuclei tag based execution
//
// It takes into account Tags, Severities, Authors, IncludeTags, ExcludeTags.
func (config *Config) createTagFilter() *tagFilter {
filter := &tagFilter{
func New(config *Config) *TagFilter {
filter := &TagFilter{
allowedTags: make(map[string]struct{}),
authors: make(map[string]struct{}),
severities: make(map[string]struct{}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package loader
package filter

import (
"testing"
Expand All @@ -10,22 +10,22 @@ func TestTagBasedFilter(t *testing.T) {
config := &Config{
Tags: []string{"cves", "2021", "jira"},
}
filter := config.createTagFilter()
filter := New(config)

t.Run("true", func(t *testing.T) {
matched, _ := filter.match("jira", "pdteam", "low")
matched, _ := filter.Match("jira", "pdteam", "low")
require.True(t, matched, "could not get correct match")
})
t.Run("false", func(t *testing.T) {
matched, _ := filter.match("consul", "pdteam", "low")
matched, _ := filter.Match("consul", "pdteam", "low")
require.False(t, matched, "could not get correct match")
})
t.Run("not-match-excludes", func(t *testing.T) {
config := &Config{
ExcludeTags: []string{"dos"},
}
filter := config.createTagFilter()
matched, err := filter.match("dos", "pdteam", "low")
filter := New(config)
matched, err := filter.Match("dos", "pdteam", "low")
require.False(t, matched, "could not get correct match")
require.Equal(t, ErrExcluded, err, "could not get correct error")
})
Expand All @@ -35,9 +35,8 @@ func TestTagBasedFilter(t *testing.T) {
ExcludeTags: []string{"dos", "fuzz"},
IncludeTags: []string{"fuzz"},
}

filter := config.createTagFilter()
matched, err := filter.match("fuzz", "pdteam", "low")
filter := New(config)
matched, err := filter.Match("fuzz", "pdteam", "low")
require.Nil(t, err, "could not get match")
require.True(t, matched, "could not get correct match")
})
Expand All @@ -46,25 +45,25 @@ func TestTagBasedFilter(t *testing.T) {
Tags: []string{"fuzz"},
ExcludeTags: []string{"fuzz"},
}
filter := config.createTagFilter()
matched, err := filter.match("fuzz", "pdteam", "low")
filter := New(config)
matched, err := filter.Match("fuzz", "pdteam", "low")
require.Nil(t, err, "could not get match")
require.True(t, matched, "could not get correct match")
})
t.Run("match-author", func(t *testing.T) {
config := &Config{
Authors: []string{"pdteam"},
}
filter := config.createTagFilter()
matched, _ := filter.match("fuzz", "pdteam", "low")
filter := New(config)
matched, _ := filter.Match("fuzz", "pdteam", "low")
require.True(t, matched, "could not get correct match")
})
t.Run("match-severity", func(t *testing.T) {
config := &Config{
Severities: []string{"high"},
}
filter := config.createTagFilter()
matched, _ := filter.match("fuzz", "pdteam", "high")
filter := New(config)
matched, _ := filter.Match("fuzz", "pdteam", "high")
require.True(t, matched, "could not get correct match")
})
t.Run("match-conditions", func(t *testing.T) {
Expand All @@ -73,14 +72,14 @@ func TestTagBasedFilter(t *testing.T) {
Tags: []string{"jira"},
Severities: []string{"high"},
}
filter := config.createTagFilter()
matched, _ := filter.match("jira", "pdteam", "high")
filter := New(config)
matched, _ := filter.Match("jira", "pdteam", "high")
require.True(t, matched, "could not get correct match")
matched, _ = filter.match("jira", "pdteam", "low")
matched, _ = filter.Match("jira", "pdteam", "low")
require.False(t, matched, "could not get correct match")
matched, _ = filter.match("jira", "random", "low")
matched, _ = filter.Match("jira", "random", "low")
require.False(t, matched, "could not get correct match")
matched, _ = filter.match("consul", "random", "low")
matched, _ = filter.Match("consul", "random", "low")
require.False(t, matched, "could not get correct match")
})
}
95 changes: 95 additions & 0 deletions v2/pkg/catalog/loader/load/load.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package load

import (
"bytes"
"errors"
"io/ioutil"
"os"
"strings"

"github.com/projectdiscovery/nuclei/v2/pkg/catalog/loader/filter"
"github.com/projectdiscovery/nuclei/v2/pkg/types"
"gopkg.in/yaml.v2"
)

// Load loads a template by parsing metadata and running
// all tag and path based filters on the template.
func Load(templatePath string, workflow bool, customTags []string, tagFilter *filter.TagFilter) (bool, error) {
f, err := os.Open(templatePath)
if err != nil {
return false, err
}
defer f.Close()

data, err := ioutil.ReadAll(f)
if err != nil {
return false, err
}

template := make(map[string]interface{})
err = yaml.NewDecoder(bytes.NewReader(data)).Decode(template)
if err != nil {
return false, err
}

info, ok := template["info"]
if !ok {
return false, errors.New("no template info field provided")
}
infoMap := info.(map[interface{}]interface{})

if _, ok := infoMap["name"]; !ok {
return false, errors.New("no template name field provided")
}
author, ok := infoMap["author"]
if !ok {
return false, errors.New("no template author field provided")
}
severity, ok := infoMap["severity"]
if !ok {
severity = ""
}

templateTags, ok := infoMap["tags"]
if !ok {
templateTags = ""
}
tagStr := types.ToString(templateTags)

tags := strings.Split(tagStr, ",")
severityStr := types.ToString(severity)
authors := strings.Split(types.ToString(author), ",")

matched := false

for _, tag := range tags {
for _, author := range authors {
var match bool
var err error

if len(customTags) > 0 {
match, err = tagFilter.Match(strings.TrimSpace(tag), strings.TrimSpace(author), severityStr)
} else {
match, err = tagFilter.MatchWithAllowedTags(customTags, strings.TrimSpace(tag), strings.TrimSpace(author), severityStr)
}
if err == filter.ErrExcluded {
return false, filter.ErrExcluded
}
if !matched && match && err == nil {
matched = true
}
}
}
if !matched {
return false, nil
}
_, workflowsFound := template["workflows"]

if !workflowsFound && workflow {
return false, nil
}
if workflowsFound && !workflow {
return false, nil
}
return true, nil
}
Loading

0 comments on commit 0726acc

Please sign in to comment.