From f2c20dda12832a1849049d7928fce32b6a1709ab Mon Sep 17 00:00:00 2001 From: Ice3man543 Date: Thu, 4 Feb 2021 01:09:29 +0530 Subject: [PATCH] More tests + tag based execution + misc --- v2/cmd/nuclei/main.go | 1 + v2/internal/runner/templates.go | 6 +- v2/internal/testutils/testutils.go | 2 +- v2/pkg/output/format_screen.go | 2 +- v2/pkg/output/output.go | 2 +- v2/pkg/protocols/common/clusterer/executer.go | 2 +- v2/pkg/protocols/dns/dns_test.go | 2 +- v2/pkg/protocols/dns/operators.go | 2 +- v2/pkg/protocols/dns/operators_test.go | 8 +-- v2/pkg/protocols/dns/request_test.go | 2 +- v2/pkg/protocols/file/file_test.go | 2 +- v2/pkg/protocols/file/find_test.go | 2 +- v2/pkg/protocols/file/operators.go | 2 +- v2/pkg/protocols/file/operators_test.go | 8 +-- v2/pkg/protocols/file/request_test.go | 2 +- v2/pkg/protocols/http/build_request.go | 66 ----------------- v2/pkg/protocols/http/build_request_test.go | 69 ------------------ v2/pkg/protocols/http/cluster_test.go | 1 + v2/pkg/protocols/http/operators.go | 2 +- v2/pkg/protocols/http/request_generator.go | 69 ++++++++++++++++++ .../protocols/http/request_generator_test.go | 70 +++++++++++++++++++ v2/pkg/protocols/network/network_test.go | 2 +- v2/pkg/protocols/network/operators.go | 2 +- v2/pkg/protocols/network/operators_test.go | 8 +-- v2/pkg/protocols/network/request_test.go | 6 +- v2/pkg/protocols/protocols.go | 2 +- v2/pkg/templates/compile.go | 54 ++++++++++++++ v2/pkg/templates/compile_test.go | 25 +++++++ v2/pkg/templates/templates.go | 4 +- v2/pkg/types/types.go | 4 ++ 30 files changed, 257 insertions(+), 172 deletions(-) create mode 100644 v2/pkg/protocols/http/cluster_test.go create mode 100644 v2/pkg/protocols/http/request_generator.go create mode 100644 v2/pkg/protocols/http/request_generator_test.go create mode 100644 v2/pkg/templates/compile_test.go diff --git a/v2/cmd/nuclei/main.go b/v2/cmd/nuclei/main.go index a4436a581..972507758 100644 --- a/v2/cmd/nuclei/main.go +++ b/v2/cmd/nuclei/main.go @@ -74,6 +74,7 @@ based on templates offering massive extensibility and ease of use.`) set.BoolVarP(&options.NoMeta, "no-meta", "nm", false, "Don't display metadata for the matches") set.BoolVarP(&options.TemplatesVersion, "templates-version", "tv", false, "Shows the installed nuclei-templates version") set.StringVarP(&options.BurpCollaboratorBiid, "burp-collaborator-biid", "biid", "", "Burp Collaborator BIID") + set.StringSliceVar(&options.Tags, "tags", []string{}, "Tags to execute templates for") _ = set.Parse() if cfgFile != "" { diff --git a/v2/internal/runner/templates.go b/v2/internal/runner/templates.go index 15620d11d..7051385ac 100644 --- a/v2/internal/runner/templates.go +++ b/v2/internal/runner/templates.go @@ -29,10 +29,10 @@ func (r *Runner) getParsedTemplatesFor(templatePaths []string, severities []stri if len(t.Workflows) > 0 { workflowCount++ } - sev := strings.ToLower(t.Info["severity"]) + sev := strings.ToLower(t.Info["severity"].(string)) if !filterBySeverity || hasMatchingSeverity(sev, severities) { parsedTemplates[t.ID] = t - gologger.Info().Msgf("%s\n", r.templateLogMsg(t.ID, t.Info["name"], t.Info["author"], t.Info["severity"])) + gologger.Info().Msgf("%s\n", r.templateLogMsg(t.ID, t.Info["name"].(string), t.Info["author"].(string), t.Info["severity"].(string))) } else { gologger.Error().Msgf("Excluding template %s due to severity filter (%s not in [%s])", t.ID, sev, severities) } @@ -74,7 +74,7 @@ func (r *Runner) logAvailableTemplate(tplPath string) { if err != nil { gologger.Error().Msgf("Could not parse file '%s': %s\n", tplPath, err) } else { - gologger.Print().Msgf("%s\n", r.templateLogMsg(t.ID, t.Info["name"], t.Info["author"], t.Info["severity"])) + gologger.Print().Msgf("%s\n", r.templateLogMsg(t.ID, t.Info["name"].(string), t.Info["author"].(string), t.Info["severity"].(string))) } } diff --git a/v2/internal/testutils/testutils.go b/v2/internal/testutils/testutils.go index c89f17df8..fd652e880 100644 --- a/v2/internal/testutils/testutils.go +++ b/v2/internal/testutils/testutils.go @@ -96,7 +96,7 @@ func (m *MockOutputWriter) Request(templateID, url, requestType string, err erro // TemplateInfo contains info for a mock executed template. type TemplateInfo struct { ID string - Info map[string]string + Info map[string]interface{} Path string } diff --git a/v2/pkg/output/format_screen.go b/v2/pkg/output/format_screen.go index 09db03b4a..a81bacfbc 100644 --- a/v2/pkg/output/format_screen.go +++ b/v2/pkg/output/format_screen.go @@ -27,7 +27,7 @@ func (w *StandardWriter) formatScreen(output *ResultEvent) ([]byte, error) { builder.WriteString("] ") builder.WriteString("[") - builder.WriteString(w.severityColors.Data[output.Info["severity"]]) + builder.WriteString(w.severityColors.Data[output.Info["severity"].(string)]) builder.WriteString("] ") } builder.WriteString(output.Matched) diff --git a/v2/pkg/output/output.go b/v2/pkg/output/output.go index 9a4e0e5ac..7493060b6 100644 --- a/v2/pkg/output/output.go +++ b/v2/pkg/output/output.go @@ -54,7 +54,7 @@ type ResultEvent struct { // TemplateID is the ID of the template for the result. TemplateID string `json:"templateID"` // Info contains information block of the template for the result. - Info map[string]string `json:"info,inline"` + Info map[string]interface{} `json:"info,inline"` // MatcherName is the name of the matcher matched if any. MatcherName string `json:"matcher_name,omitempty"` // ExtractorName is the name of the extractor matched if any. diff --git a/v2/pkg/protocols/common/clusterer/executer.go b/v2/pkg/protocols/common/clusterer/executer.go index cef218a31..2b6856763 100644 --- a/v2/pkg/protocols/common/clusterer/executer.go +++ b/v2/pkg/protocols/common/clusterer/executer.go @@ -21,7 +21,7 @@ type Executer struct { type clusteredOperator struct { templateID string - templateInfo map[string]string + templateInfo map[string]interface{} operator *operators.Operators } diff --git a/v2/pkg/protocols/dns/dns_test.go b/v2/pkg/protocols/dns/dns_test.go index a9c5c6fe5..d5bf7ce55 100644 --- a/v2/pkg/protocols/dns/dns_test.go +++ b/v2/pkg/protocols/dns/dns_test.go @@ -22,7 +22,7 @@ func TestDNSCompileMake(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile dns request") diff --git a/v2/pkg/protocols/dns/operators.go b/v2/pkg/protocols/dns/operators.go index c714d9b49..a17b5f472 100644 --- a/v2/pkg/protocols/dns/operators.go +++ b/v2/pkg/protocols/dns/operators.go @@ -136,7 +136,7 @@ func (r *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*outpu func (r *Request) makeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent { data := &output.ResultEvent{ TemplateID: wrapped.InternalEvent["template-id"].(string), - Info: wrapped.InternalEvent["template-info"].(map[string]string), + Info: wrapped.InternalEvent["template-info"].(map[string]interface{}), Type: "dns", Host: wrapped.InternalEvent["host"].(string), Matched: wrapped.InternalEvent["matched"].(string), diff --git a/v2/pkg/protocols/dns/operators_test.go b/v2/pkg/protocols/dns/operators_test.go index 646768172..c8de0ddea 100644 --- a/v2/pkg/protocols/dns/operators_test.go +++ b/v2/pkg/protocols/dns/operators_test.go @@ -29,7 +29,7 @@ func TestResponseToDSLMap(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile dns request") @@ -61,7 +61,7 @@ func TestDNSOperatorMatch(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile dns request") @@ -144,7 +144,7 @@ func TestDNSOperatorExtract(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile dns request") @@ -214,7 +214,7 @@ func TestDNSMakeResult(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile dns request") diff --git a/v2/pkg/protocols/dns/request_test.go b/v2/pkg/protocols/dns/request_test.go index efc03602e..50158a6f2 100644 --- a/v2/pkg/protocols/dns/request_test.go +++ b/v2/pkg/protocols/dns/request_test.go @@ -39,7 +39,7 @@ func TestDNSExecuteWithResults(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile dns request") diff --git a/v2/pkg/protocols/file/file_test.go b/v2/pkg/protocols/file/file_test.go index a2ede0f5b..ca27c7418 100644 --- a/v2/pkg/protocols/file/file_test.go +++ b/v2/pkg/protocols/file/file_test.go @@ -22,7 +22,7 @@ func TestFileCompile(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile file request") diff --git a/v2/pkg/protocols/file/find_test.go b/v2/pkg/protocols/file/find_test.go index 2cfaefd70..c8539f73a 100644 --- a/v2/pkg/protocols/file/find_test.go +++ b/v2/pkg/protocols/file/find_test.go @@ -25,7 +25,7 @@ func TestFindInputPaths(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile file request") diff --git a/v2/pkg/protocols/file/operators.go b/v2/pkg/protocols/file/operators.go index d01782c24..1c999c6ff 100644 --- a/v2/pkg/protocols/file/operators.go +++ b/v2/pkg/protocols/file/operators.go @@ -103,7 +103,7 @@ func (r *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*outpu func (r *Request) makeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent { data := &output.ResultEvent{ TemplateID: wrapped.InternalEvent["template-id"].(string), - Info: wrapped.InternalEvent["template-info"].(map[string]string), + Info: wrapped.InternalEvent["template-info"].(map[string]interface{}), Type: "file", Host: wrapped.InternalEvent["host"].(string), Matched: wrapped.InternalEvent["matched"].(string), diff --git a/v2/pkg/protocols/file/operators_test.go b/v2/pkg/protocols/file/operators_test.go index f8947c34c..0b11fa453 100644 --- a/v2/pkg/protocols/file/operators_test.go +++ b/v2/pkg/protocols/file/operators_test.go @@ -26,7 +26,7 @@ func TestResponseToDSLMap(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile file request") @@ -52,7 +52,7 @@ func TestFileOperatorMatch(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile file request") @@ -118,7 +118,7 @@ func TestFileOperatorExtract(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile file request") @@ -184,7 +184,7 @@ func TestFileMakeResult(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile file request") diff --git a/v2/pkg/protocols/file/request_test.go b/v2/pkg/protocols/file/request_test.go index 3b512be47..fca5cec65 100644 --- a/v2/pkg/protocols/file/request_test.go +++ b/v2/pkg/protocols/file/request_test.go @@ -42,7 +42,7 @@ func TestFileExecuteWithResults(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile file request") diff --git a/v2/pkg/protocols/http/build_request.go b/v2/pkg/protocols/http/build_request.go index 958dd0778..dbcbac0f3 100644 --- a/v2/pkg/protocols/http/build_request.go +++ b/v2/pkg/protocols/http/build_request.go @@ -27,72 +27,6 @@ var ( templateExpressionRegex = regexp.MustCompile(`(?m)\{\{[^}]+\}\}`) ) -// requestGenerator generates requests sequentially based on various -// configurations for a http request template. -// -// If payload values are present, an iterator is created for the payload -// values. Paths and Raw requests are supported as base input, so -// it will automatically select between them based on the template. -type requestGenerator struct { - currentIndex int - request *Request - payloadIterator *generators.Iterator -} - -// newGenerator creates a new request generator instance -func (r *Request) newGenerator() *requestGenerator { - generator := &requestGenerator{request: r} - - if len(r.Payloads) > 0 { - generator.payloadIterator = r.generator.NewIterator() - } - return generator -} - -// nextValue returns the next path or the next raw request depending on user input -// It returns false if all the inputs have been exhausted by the generator instance. -func (r *requestGenerator) nextValue() (string, map[string]interface{}, bool) { - // If we have paths, return the next path. - if len(r.request.Path) > 0 && r.currentIndex < len(r.request.Path) { - if item := r.request.Path[r.currentIndex]; item != "" { - r.currentIndex++ - return item, nil, true - } - } - - // If we have raw requests, start with the request at current index. - // If we are not at the start, then check if the iterator for payloads - // has finished if there are any. - // - // If the iterator has finished for the current raw request - // then reset it and move on to the next value, otherwise use the last request. - if len(r.request.Raw) > 0 && r.currentIndex < len(r.request.Raw) { - if r.payloadIterator != nil { - payload, ok := r.payloadIterator.Value() - if !ok { - r.currentIndex++ - r.payloadIterator.Reset() - - // No more payloads request for us now. - if len(r.request.Raw) == r.currentIndex { - return "", nil, false - } - if item := r.request.Raw[r.currentIndex]; item != "" { - newPayload, ok := r.payloadIterator.Value() - return item, newPayload, ok - } - return "", nil, false - } - return r.request.Raw[r.currentIndex], payload, true - } - if item := r.request.Raw[r.currentIndex]; item != "" { - r.currentIndex++ - return item, nil, true - } - } - return "", nil, false -} - // generatedRequest is a single wrapped generated request for a template request type generatedRequest struct { original *Request diff --git a/v2/pkg/protocols/http/build_request_test.go b/v2/pkg/protocols/http/build_request_test.go index b16f5c98b..d02cfda64 100644 --- a/v2/pkg/protocols/http/build_request_test.go +++ b/v2/pkg/protocols/http/build_request_test.go @@ -1,70 +1 @@ package http - -import ( - "testing" - - "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" - "github.com/stretchr/testify/require" -) - -func TestRequestGeneratorPaths(t *testing.T) { - req := &Request{ - Path: []string{"{{BaseURL}}/test", "{{BaseURL}}/test.php"}, - } - generator := req.newGenerator() - var payloads []string - for { - raw, _, ok := generator.nextValue() - if !ok { - break - } - payloads = append(payloads, raw) - } - require.Equal(t, req.Path, payloads, "Could not get correct paths") -} - -func TestRequestGeneratorClusterSingle(t *testing.T) { - var err error - - req := &Request{ - Payloads: map[string]interface{}{"username": []string{"admin", "tomcat", "manager"}, "password": []string{"password", "test", "secret"}}, - attackType: generators.ClusterBomb, - Raw: []string{`GET /{{username}}:{{password}} HTTP/1.1`}, - } - req.generator, err = generators.New(req.Payloads, req.attackType, "") - require.Nil(t, err, "could not create generator") - - generator := req.newGenerator() - var payloads []map[string]interface{} - for { - _, data, ok := generator.nextValue() - if !ok { - break - } - payloads = append(payloads, data) - } - require.Equal(t, 9, len(payloads), "Could not get correct number of payloads") -} - -func TestRequestGeneratorClusterMultipleRaw(t *testing.T) { - var err error - - req := &Request{ - Payloads: map[string]interface{}{"username": []string{"admin", "tomcat", "manager"}, "password": []string{"password", "test", "secret"}}, - attackType: generators.ClusterBomb, - Raw: []string{`GET /{{username}}:{{password}} HTTP/1.1`, `GET /{{username}}@{{password}} HTTP/1.1`}, - } - req.generator, err = generators.New(req.Payloads, req.attackType, "") - require.Nil(t, err, "could not create generator") - - generator := req.newGenerator() - var payloads []map[string]interface{} - for { - _, data, ok := generator.nextValue() - if !ok { - break - } - payloads = append(payloads, data) - } - require.Equal(t, 18, len(payloads), "Could not get correct number of payloads") -} diff --git a/v2/pkg/protocols/http/cluster_test.go b/v2/pkg/protocols/http/cluster_test.go new file mode 100644 index 000000000..d02cfda64 --- /dev/null +++ b/v2/pkg/protocols/http/cluster_test.go @@ -0,0 +1 @@ +package http diff --git a/v2/pkg/protocols/http/operators.go b/v2/pkg/protocols/http/operators.go index 988f72204..7e6a387f0 100644 --- a/v2/pkg/protocols/http/operators.go +++ b/v2/pkg/protocols/http/operators.go @@ -136,7 +136,7 @@ func (r *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*outpu func (r *Request) makeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent { data := &output.ResultEvent{ TemplateID: wrapped.InternalEvent["template-id"].(string), - Info: wrapped.InternalEvent["template-info"].(map[string]string), + Info: wrapped.InternalEvent["template-info"].(map[string]interface{}), Type: "http", Host: wrapped.InternalEvent["host"].(string), Matched: wrapped.InternalEvent["matched"].(string), diff --git a/v2/pkg/protocols/http/request_generator.go b/v2/pkg/protocols/http/request_generator.go new file mode 100644 index 000000000..ba3416783 --- /dev/null +++ b/v2/pkg/protocols/http/request_generator.go @@ -0,0 +1,69 @@ +package http + +import "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" + +// requestGenerator generates requests sequentially based on various +// configurations for a http request template. +// +// If payload values are present, an iterator is created for the payload +// values. Paths and Raw requests are supported as base input, so +// it will automatically select between them based on the template. +type requestGenerator struct { + currentIndex int + request *Request + payloadIterator *generators.Iterator +} + +// newGenerator creates a new request generator instance +func (r *Request) newGenerator() *requestGenerator { + generator := &requestGenerator{request: r} + + if len(r.Payloads) > 0 { + generator.payloadIterator = r.generator.NewIterator() + } + return generator +} + +// nextValue returns the next path or the next raw request depending on user input +// It returns false if all the inputs have been exhausted by the generator instance. +func (r *requestGenerator) nextValue() (string, map[string]interface{}, bool) { + // If we have paths, return the next path. + if len(r.request.Path) > 0 && r.currentIndex < len(r.request.Path) { + if item := r.request.Path[r.currentIndex]; item != "" { + r.currentIndex++ + return item, nil, true + } + } + + // If we have raw requests, start with the request at current index. + // If we are not at the start, then check if the iterator for payloads + // has finished if there are any. + // + // If the iterator has finished for the current raw request + // then reset it and move on to the next value, otherwise use the last request. + if len(r.request.Raw) > 0 && r.currentIndex < len(r.request.Raw) { + if r.payloadIterator != nil { + payload, ok := r.payloadIterator.Value() + if !ok { + r.currentIndex++ + r.payloadIterator.Reset() + + // No more payloads request for us now. + if len(r.request.Raw) == r.currentIndex { + return "", nil, false + } + if item := r.request.Raw[r.currentIndex]; item != "" { + newPayload, ok := r.payloadIterator.Value() + return item, newPayload, ok + } + return "", nil, false + } + return r.request.Raw[r.currentIndex], payload, true + } + if item := r.request.Raw[r.currentIndex]; item != "" { + r.currentIndex++ + return item, nil, true + } + } + return "", nil, false +} diff --git a/v2/pkg/protocols/http/request_generator_test.go b/v2/pkg/protocols/http/request_generator_test.go new file mode 100644 index 000000000..53064cca0 --- /dev/null +++ b/v2/pkg/protocols/http/request_generator_test.go @@ -0,0 +1,70 @@ +package http + +import ( + "testing" + + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" + "github.com/stretchr/testify/require" +) + +func TestRequestGeneratorPaths(t *testing.T) { + req := &Request{ + Path: []string{"{{BaseURL}}/test", "{{BaseURL}}/test.php"}, + } + generator := req.newGenerator() + var payloads []string + for { + raw, _, ok := generator.nextValue() + if !ok { + break + } + payloads = append(payloads, raw) + } + require.Equal(t, req.Path, payloads, "Could not get correct paths") +} + +func TestRequestGeneratorClusterBombSingle(t *testing.T) { + var err error + + req := &Request{ + Payloads: map[string]interface{}{"username": []string{"admin", "tomcat", "manager"}, "password": []string{"password", "test", "secret"}}, + attackType: generators.ClusterBomb, + Raw: []string{`GET /{{username}}:{{password}} HTTP/1.1`}, + } + req.generator, err = generators.New(req.Payloads, req.attackType, "") + require.Nil(t, err, "could not create generator") + + generator := req.newGenerator() + var payloads []map[string]interface{} + for { + _, data, ok := generator.nextValue() + if !ok { + break + } + payloads = append(payloads, data) + } + require.Equal(t, 9, len(payloads), "Could not get correct number of payloads") +} + +func TestRequestGeneratorClusterBombMultipleRaw(t *testing.T) { + var err error + + req := &Request{ + Payloads: map[string]interface{}{"username": []string{"admin", "tomcat", "manager"}, "password": []string{"password", "test", "secret"}}, + attackType: generators.ClusterBomb, + Raw: []string{`GET /{{username}}:{{password}} HTTP/1.1`, `GET /{{username}}@{{password}} HTTP/1.1`}, + } + req.generator, err = generators.New(req.Payloads, req.attackType, "") + require.Nil(t, err, "could not create generator") + + generator := req.newGenerator() + var payloads []map[string]interface{} + for { + _, data, ok := generator.nextValue() + if !ok { + break + } + payloads = append(payloads, data) + } + require.Equal(t, 18, len(payloads), "Could not get correct number of payloads") +} diff --git a/v2/pkg/protocols/network/network_test.go b/v2/pkg/protocols/network/network_test.go index 3e29f5d02..e2c910fa2 100644 --- a/v2/pkg/protocols/network/network_test.go +++ b/v2/pkg/protocols/network/network_test.go @@ -20,7 +20,7 @@ func TestNetworkCompileMake(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile network request") diff --git a/v2/pkg/protocols/network/operators.go b/v2/pkg/protocols/network/operators.go index 699ba035b..794d04464 100644 --- a/v2/pkg/protocols/network/operators.go +++ b/v2/pkg/protocols/network/operators.go @@ -104,7 +104,7 @@ func (r *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*outpu func (r *Request) makeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent { data := &output.ResultEvent{ TemplateID: wrapped.InternalEvent["template-id"].(string), - Info: wrapped.InternalEvent["template-info"].(map[string]string), + Info: wrapped.InternalEvent["template-info"].(map[string]interface{}), Type: "network", Host: wrapped.InternalEvent["host"].(string), Matched: wrapped.InternalEvent["matched"].(string), diff --git a/v2/pkg/protocols/network/operators_test.go b/v2/pkg/protocols/network/operators_test.go index c0f03c59e..a8fc82d3c 100644 --- a/v2/pkg/protocols/network/operators_test.go +++ b/v2/pkg/protocols/network/operators_test.go @@ -24,7 +24,7 @@ func TestResponseToDSLMap(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile network request") @@ -49,7 +49,7 @@ func TestNetworkOperatorMatch(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile network request") @@ -112,7 +112,7 @@ func TestNetworkOperatorExtract(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile network request") @@ -175,7 +175,7 @@ func TestNetworkMakeResult(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile network request") diff --git a/v2/pkg/protocols/network/request_test.go b/v2/pkg/protocols/network/request_test.go index bd30ced98..fdb67f628 100644 --- a/v2/pkg/protocols/network/request_test.go +++ b/v2/pkg/protocols/network/request_test.go @@ -38,7 +38,7 @@ func TestNetworkExecuteWithResults(t *testing.T) { } executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ ID: templateID, - Info: map[string]string{"severity": "low", "name": "test"}, + Info: map[string]interface{}{"severity": "low", "name": "test"}, }) err := request.Compile(executerOpts) require.Nil(t, err, "could not compile network request") @@ -90,8 +90,4 @@ func TestNetworkExecuteWithResults(t *testing.T) { require.Equal(t, "test", finalEvent.Results[0].MatcherName, "could not get correct matcher name of results") require.Equal(t, 1, len(finalEvent.Results[0].ExtractedResults), "could not get correct number of extracted results") require.Equal(t, "

400 - Bad Request

", finalEvent.Results[0].ExtractedResults[0], "could not get correct extracted results") - finalEvent = nil - - request.Inputs[0].Type = "" - request.Inputs[0].Data = "GET / HTTP/1.1\r\n\r\n" } diff --git a/v2/pkg/protocols/protocols.go b/v2/pkg/protocols/protocols.go index 5a40a54dd..d6836fdb4 100644 --- a/v2/pkg/protocols/protocols.go +++ b/v2/pkg/protocols/protocols.go @@ -30,7 +30,7 @@ type ExecuterOptions struct { // TemplatePath is the path of the template for the request TemplatePath string // TemplateInfo contains information block of the template request - TemplateInfo map[string]string + TemplateInfo map[string]interface{} // Output is a writer interface for writing output events from executer. Output output.Writer // Options contains configuration options for the executer. diff --git a/v2/pkg/templates/compile.go b/v2/pkg/templates/compile.go index e62a01713..094bcc2de 100644 --- a/v2/pkg/templates/compile.go +++ b/v2/pkg/templates/compile.go @@ -3,10 +3,12 @@ package templates import ( "fmt" "os" + "strings" "github.com/pkg/errors" "github.com/projectdiscovery/nuclei/v2/pkg/protocols" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/executer" + "github.com/projectdiscovery/nuclei/v2/pkg/types" "github.com/projectdiscovery/nuclei/v2/pkg/workflows" "gopkg.in/yaml.v2" ) @@ -26,6 +28,21 @@ func Parse(filePath string, options *protocols.ExecuterOptions) (*Template, erro } defer f.Close() + if _, ok := template.Info["name"]; !ok { + return nil, errors.New("no template name field provided") + } + if _, ok := template.Info["author"]; !ok { + return nil, errors.New("no template author field provided") + } + if _, ok := template.Info["severity"]; !ok { + return nil, errors.New("no template severity field provided") + } + if templateTags, ok := template.Info["tags"]; ok && len(options.Options.Tags) > 0 { + if err := matchTemplateWithTags(templateTags.([]interface{}), options.Options); err != nil { + return nil, err + } + } + // Setting up variables regarding template metadata options.TemplateID = template.ID options.TemplateInfo = template.Info @@ -140,3 +157,40 @@ func (t *Template) parseWorkflowTemplate(workflow *workflows.WorkflowTemplate, o } return nil } + +// matchTemplateWithTags matches if the template matches a tag +func matchTemplateWithTags(tags []interface{}, options *types.Options) error { + matched := false +mainLoop: + for _, tag := range options.Tags { + key, value := getKeyValue(tag) + + for _, templTag := range tags { + tKey, tValue := getKeyValue(templTag.(string)) + if strings.EqualFold(key, tKey) && strings.EqualFold(value, tValue) { + matched = true + break mainLoop + } + } + } + if !matched { + return errors.New("could not match template tags with input") + } + return nil +} + +// getKeyValue returns key value pair for a data string +func getKeyValue(data string) (string, string) { + + var key, value string + if strings.Contains(data, ":") { + parts := strings.SplitN(data, ":", 1) + if len(parts) > 2 { + key, value = parts[0], parts[1] + } + } + if value == "" { + value = data + } + return key, value +} diff --git a/v2/pkg/templates/compile_test.go b/v2/pkg/templates/compile_test.go new file mode 100644 index 000000000..c27c17634 --- /dev/null +++ b/v2/pkg/templates/compile_test.go @@ -0,0 +1,25 @@ +package templates + +import ( + "testing" + + "github.com/projectdiscovery/nuclei/v2/pkg/types" + "github.com/stretchr/testify/require" +) + +func TestMatchTemplateWithTags(t *testing.T) { + templateTags := []interface{}{"php", "linux", "symfony"} + + err := matchTemplateWithTags(templateTags, &types.Options{Tags: []string{"php"}}) + require.Nil(t, err, "could not get php tag from input slice") + + templateTags = []interface{}{"lang:php", "os:linux", "cms:symfony"} + + err = matchTemplateWithTags(templateTags, &types.Options{Tags: []string{"cms:symfony"}}) + require.Nil(t, err, "could not get php tag from input key value") + + templateTags = []interface{}{"lang:php", "os:linux", "symfony"} + + err = matchTemplateWithTags(templateTags, &types.Options{Tags: []string{"cms:symfony"}}) + require.NotNil(t, err, "could get key value tag from input key value") +} diff --git a/v2/pkg/templates/templates.go b/v2/pkg/templates/templates.go index 243d78aef..2e55bfc1c 100644 --- a/v2/pkg/templates/templates.go +++ b/v2/pkg/templates/templates.go @@ -14,7 +14,7 @@ type Template struct { // ID is the unique id for the template ID string `yaml:"id"` // Info contains information about the template - Info map[string]string `yaml:"info"` + Info map[string]interface{} `yaml:"info"` // RequestsHTTP contains the http request to make in the template RequestsHTTP []*http.Request `yaml:"requests,omitempty"` // RequestsDNS contains the dns request to make in the template @@ -23,7 +23,7 @@ type Template struct { RequestsFile []*file.Request `yaml:"file,omitempty"` // RequestsNetwork contains the network request to make in the template RequestsNetwork []*network.Request `yaml:"network,omitempty"` - + // Workflows is a yaml based workflow declaration code. workflows.Workflow `yaml:",inline"` CompiledWorkflow *workflows.Workflow diff --git a/v2/pkg/types/types.go b/v2/pkg/types/types.go index aa1c87cdf..68f8186e0 100644 --- a/v2/pkg/types/types.go +++ b/v2/pkg/types/types.go @@ -80,4 +80,8 @@ type Options struct { ExcludedTemplates goflags.StringSlice // CustomHeaders is the list of custom global headers to send with each request. CustomHeaders goflags.StringSlice + // Tags contains a list of tags to execute templates for. Multiple paths + // can be specified with -l flag and -tags can be used in combination with + // the -l flag. + Tags goflags.StringSlice }