From 12d116b8cd15195f564728b113a240eef5fd3786 Mon Sep 17 00:00:00 2001 From: Ice3man Date: Tue, 7 May 2024 19:31:40 +0530 Subject: [PATCH] feat: added configurable fuzzing aggression level for payloads --- cmd/nuclei/main.go | 1 + pkg/protocols/common/generators/generators.go | 69 ++++++++++++++++++- .../common/generators/generators_test.go | 48 +++++++++++++ pkg/types/types.go | 2 + 4 files changed, 118 insertions(+), 2 deletions(-) diff --git a/cmd/nuclei/main.go b/cmd/nuclei/main.go index 4a6106a8d1..fb03825c2a 100644 --- a/cmd/nuclei/main.go +++ b/cmd/nuclei/main.go @@ -325,6 +325,7 @@ on extensive configurability, massive extensibility and ease of use.`) flagSet.BoolVar(&options.DAST, "dast", false, "enable / run dast (fuzz) nuclei templates"), flagSet.BoolVarP(&options.DisplayFuzzPoints, "display-fuzz-points", "dfp", false, "display fuzz points in the output for debugging"), flagSet.IntVar(&options.FuzzParamFrequency, "fuzz-param-frequency", 10, "frequency of uninteresting parameters for fuzzing before skipping"), + flagSet.StringVarP(&options.FuzzAggressionLevel, "fuzz-aggression", "fa", "low", "fuzzing aggression level controls payload count for fuzz (low, medium, high)"), ) flagSet.CreateGroup("uncover", "Uncover", diff --git a/pkg/protocols/common/generators/generators.go b/pkg/protocols/common/generators/generators.go index b99de24c0b..5711b445f5 100644 --- a/pkg/protocols/common/generators/generators.go +++ b/pkg/protocols/common/generators/generators.go @@ -25,8 +25,19 @@ func New(payloads map[string]interface{}, attackType AttackType, templatePath st // Resolve payload paths if they are files. payloadsFinal := make(map[string]interface{}) - for name, payload := range payloads { - payloadsFinal[name] = payload + for payloadName, v := range payloads { + switch value := v.(type) { + case map[interface{}]interface{}: + values, err := parsePayloadsWithAggression(payloadName, value, opts.FuzzAggressionLevel) + if err != nil { + return nil, errors.Wrap(err, "could not parse payloads with aggression") + } + for k, v := range values { + payloadsFinal[k] = v + } + default: + payloadsFinal[payloadName] = v + } } generator := &PayloadGenerator{catalog: catalog, options: opts} @@ -57,6 +68,60 @@ func New(payloads map[string]interface{}, attackType AttackType, templatePath st return generator, nil } +type aggressionLevelToPayloads struct { + Low []interface{} + Medium []interface{} + High []interface{} +} + +// parsePayloadsWithAggression parses the payloads with the aggression level +// +// Three agression are supported - +// - low +// - medium +// - high +// +// low is the default level. If medium is specified, all templates from +// low and medium are executed. Similarly with high, including all templates +// from low, medium, high. +func parsePayloadsWithAggression(name string, v map[interface{}]interface{}, agression string) (map[string]interface{}, error) { + payloadsLevels := &aggressionLevelToPayloads{} + + for k, v := range v { + if _, ok := v.([]interface{}); !ok { + return nil, errors.Errorf("only lists are supported for aggression levels payloads") + } + var ok bool + switch k { + case "low": + payloadsLevels.Low, ok = v.([]interface{}) + case "medium": + payloadsLevels.Medium, ok = v.([]interface{}) + case "high": + payloadsLevels.High, ok = v.([]interface{}) + default: + return nil, errors.Errorf("invalid aggression level %s specified for %s", k, name) + } + if !ok { + return nil, errors.Errorf("invalid aggression level %s specified for %s", k, name) + } + } + + payloads := make(map[string]interface{}) + switch agression { + case "low": + payloads[name] = payloadsLevels.Low + case "medium": + payloads[name] = append(payloadsLevels.Low, payloadsLevels.Medium...) + case "high": + payloads[name] = append(payloadsLevels.Low, payloadsLevels.Medium...) + payloads[name] = append(payloads[name].([]interface{}), payloadsLevels.High...) + default: + return nil, errors.Errorf("invalid aggression level %s specified for %s", agression, name) + } + return payloads, nil +} + // Iterator is a single instance of an iterator for a generator structure type Iterator struct { Type AttackType diff --git a/pkg/protocols/common/generators/generators_test.go b/pkg/protocols/common/generators/generators_test.go index a55e8e51ee..c478995525 100644 --- a/pkg/protocols/common/generators/generators_test.go +++ b/pkg/protocols/common/generators/generators_test.go @@ -1,9 +1,11 @@ package generators import ( + "strings" "testing" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk" "github.com/projectdiscovery/nuclei/v3/pkg/types" @@ -90,3 +92,49 @@ func getOptions(allowLocalFileAccess bool) *types.Options { opts.AllowLocalFileAccess = allowLocalFileAccess return opts } + +func TestParsePayloadsWithAggression(t *testing.T) { + testPayload := `linux_path: + low: + - /etc/passwd + medium: + - ../etc/passwd + - ../../etc/passwd + high: + - ../../../etc/passwd + - ../../../../etc/passwd + - ../../../../../etc/passwd` + + var payloads map[string]interface{} + err := yaml.NewDecoder(strings.NewReader(testPayload)).Decode(&payloads) + require.Nil(t, err, "could not unmarshal yaml") + + aggressionsToValues := map[string][]string{ + "low": { + "/etc/passwd", + }, + "medium": { + "/etc/passwd", + "../etc/passwd", + "../../etc/passwd", + }, + "high": { + "/etc/passwd", + "../etc/passwd", + "../../etc/passwd", + "../../../etc/passwd", + "../../../../etc/passwd", + "../../../../../etc/passwd", + }, + } + + for k, v := range payloads { + for aggression, values := range aggressionsToValues { + parsed, err := parsePayloadsWithAggression(k, v.(map[interface{}]interface{}), aggression) + require.Nil(t, err, "could not parse payloads with aggression") + + gotValues := parsed[k].([]interface{}) + require.Equal(t, len(values), len(gotValues), "could not get correct number of values") + } + } +} diff --git a/pkg/types/types.go b/pkg/types/types.go index 511da3e002..c2ff0c876a 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -365,6 +365,8 @@ type Options struct { TlsImpersonate bool // DisplayFuzzPoints enables display of fuzz points for fuzzing DisplayFuzzPoints bool + // FuzzAggressionLevel is the level of fuzzing aggression (low, medium, high.) + FuzzAggressionLevel string // FuzzParamFrequency is the frequency of fuzzing parameters FuzzParamFrequency int // CodeTemplateSignaturePublicKey is the custom public key used to verify the template signature (algorithm is automatically inferred from the length)