diff --git a/cmd/nuclei/main.go b/cmd/nuclei/main.go index e1a3bfb1c..13fb0d69d 100644 --- a/cmd/nuclei/main.go +++ b/cmd/nuclei/main.go @@ -18,6 +18,7 @@ import ( "github.com/projectdiscovery/utils/env" _ "github.com/projectdiscovery/utils/pprof" stringsutil "github.com/projectdiscovery/utils/strings" + "gopkg.in/yaml.v2" "github.com/projectdiscovery/goflags" "github.com/projectdiscovery/gologger" @@ -261,6 +262,7 @@ on extensive configurability, massive extensibility and ease of use.`) flagSet.BoolVarP(&options.FormatUseRequiredOnly, "required-only", "ro", false, "use only required fields in input format when generating requests"), flagSet.BoolVarP(&options.SkipFormatValidation, "skip-format-validation", "sfv", false, "skip format validation (like missing vars) when parsing input file"), flagSet.BoolVarP(&options.VarsTextTemplating, "vars-text-templating", "vtt", false, "enable text templating for vars in input file (only for yaml input mode)"), + flagSet.StringSliceVarP(&options.VarsFilePaths, "var-file-paths", "vfp", nil, "list of yaml file contained vars to inject into yaml input", goflags.CommaSeparatedStringSliceOptions), ) flagSet.CreateGroup("templates", "Templates", @@ -569,6 +571,7 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started config.DefaultConfig.SetConfigDir(customConfigDir) readFlagsConfig(flagSet) } + if cfgFile != "" { if !fileutil.FileExists(cfgFile) { gologger.Fatal().Msgf("given config file '%s' does not exist", cfgFile) @@ -577,6 +580,34 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started if err := flagSet.MergeConfigFile(cfgFile); err != nil { gologger.Fatal().Msgf("Could not read config: %s\n", err) } + + if !options.Vars.IsEmpty() { + // Maybe we should add vars to the config file as well? + file, err := os.Open(cfgFile) + if err != nil { + gologger.Fatal().Msgf("Could not open config file: %s\n", err) + } + defer file.Close() + data := make(map[string]interface{}) + err = yaml.NewDecoder(file).Decode(&data) + if err != nil { + gologger.Fatal().Msgf("Could not decode config file: %s\n", err) + } + + variables := data["var"] + if variables != nil { + for _, value := range variables.([]interface{}) { + if strVal, ok := value.(string); ok { + options.Vars.Set(strVal) + } else { + gologger.Warning().Msgf("Skipping non-string variable in config: %#v", value) + } + } + } else { + gologger.Warning().Msgf("No 'var' section found in config file: %s", cfgFile) + } + + } } if options.NewTemplatesDirectory != "" { config.DefaultConfig.SetTemplatesDir(options.NewTemplatesDirectory) diff --git a/integration_tests/fuzz/fuzz-body.yaml b/integration_tests/fuzz/fuzz-body.yaml new file mode 100644 index 000000000..01a3006f6 --- /dev/null +++ b/integration_tests/fuzz/fuzz-body.yaml @@ -0,0 +1,38 @@ +id: fuzz-body + +info: + name: fuzzing error sqli payloads in http req body + author: pdteam + severity: info + description: | + This template attempts to find SQL injection vulnerabilities by fuzzing http body + It automatically handles and parses json,xml,multipart form and x-www-form-urlencoded data + and performs fuzzing on the value of every key + +http: + - pre-condition: + - type: dsl + dsl: + - method != "GET" + - method != "HEAD" + condition: and + + payloads: + injection: + - "'" + - "\"" + - ";" + + fuzzing: + - part: body + type: postfix + mode: single + fuzz: + - '{{injection}}' + + stop-at-first-match: true + matchers: + - type: word + words: + - "unrecognized token:" + - "null" diff --git a/pkg/input/formats/formats.go b/pkg/input/formats/formats.go index a23ff5d06..acc6c31d4 100644 --- a/pkg/input/formats/formats.go +++ b/pkg/input/formats/formats.go @@ -32,6 +32,8 @@ type InputFormatOptions struct { // this is used for text templating of variables based on carvel ytt // Only available for Yaml formats VarsTextTemplating bool + // VarsFilePaths is the path to the file containing variables + VarsFilePaths []string } // Format is an interface implemented by all input formats diff --git a/pkg/input/formats/testdata/ginandjuice.ytt.yaml b/pkg/input/formats/testdata/ytt/ginandjuice.ytt.yaml similarity index 71% rename from pkg/input/formats/testdata/ginandjuice.ytt.yaml rename to pkg/input/formats/testdata/ytt/ginandjuice.ytt.yaml index f1889a67f..a43bca070 100644 --- a/pkg/input/formats/testdata/ginandjuice.ytt.yaml +++ b/pkg/input/formats/testdata/ytt/ginandjuice.ytt.yaml @@ -1,10 +1,9 @@ #@ load("@ytt:data", "data") -#@ load("@ytt:assert", "assert") +#@ load("@ytt:json", "json") #@ def get_value(key, default=""): #@ if hasattr(data.values, key): -#@ value = getattr(data.values, key) -#@ return str(value) +#@ return str(getattr(data.values, key)) #@ else: #@ return default #@ end @@ -17,10 +16,10 @@ request: raw: |+ POST /users/3 HTTP/1.1 Host: ginandjuice.shop - Authorization: Bearer 3x4mpl3t0k3n + Authorization: Bearer (@= get_value("token") @) Accept-Encoding: gzip Content-Type: application/x-www-form-urlencoded Connection: close User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 - foo=(@= get_value("foo") @)&bar=(@= get_value("bar") @) \ No newline at end of file + foo=(@= get_value("foo") @)&bar=(@= get_value("bar") @)&debug=(@= get_value("debug", "false") @) \ No newline at end of file diff --git a/pkg/input/formats/testdata/ytt/ytt-profile.yaml b/pkg/input/formats/testdata/ytt/ytt-profile.yaml new file mode 100644 index 000000000..2cad498bc --- /dev/null +++ b/pkg/input/formats/testdata/ytt/ytt-profile.yaml @@ -0,0 +1,10 @@ +list: pkg/input/formats/testdata/ytt/ginandjuice.ytt.yaml +input-mode: yaml +templates: + - integration_tests/fuzz/fuzz-body.yaml +var: + - debug=true +vars-text-templating: true +var-file-paths: + - pkg/input/formats/testdata/ytt/ytt-vars.yaml +dast: true \ No newline at end of file diff --git a/pkg/input/formats/testdata/ytt/ytt-vars.yaml b/pkg/input/formats/testdata/ytt/ytt-vars.yaml new file mode 100644 index 000000000..be973bee8 --- /dev/null +++ b/pkg/input/formats/testdata/ytt/ytt-vars.yaml @@ -0,0 +1,3 @@ +foo: tata +bar: foo +token: TOKEN-BEARER \ No newline at end of file diff --git a/pkg/input/formats/yaml/multidoc.go b/pkg/input/formats/yaml/multidoc.go index 679707672..1140d5509 100644 --- a/pkg/input/formats/yaml/multidoc.go +++ b/pkg/input/formats/yaml/multidoc.go @@ -56,7 +56,7 @@ func (j *YamlMultiDocFormat) Parse(input io.Reader, resultsCb formats.ParseReqRe } tpl := []string{string(data)} dvs := mapToKeyValueSlice(j.opts.Variables) - finalInput, err = ytt(tpl, dvs) + finalInput, err = ytt(tpl, dvs, j.opts.VarsFilePaths) if err != nil { return errors.Wrap(err, "could not apply ytt templating") } diff --git a/pkg/input/formats/yaml/ytt.go b/pkg/input/formats/yaml/ytt.go index c075119ca..3ccf5f6d2 100644 --- a/pkg/input/formats/yaml/ytt.go +++ b/pkg/input/formats/yaml/ytt.go @@ -12,7 +12,7 @@ import ( "gopkg.in/yaml.v2" ) -func ytt(tpl, dvs []string) (io.Reader, error) { +func ytt(tpl, dvs []string, varFiles []string) (io.Reader, error) { // create and invoke ytt "template" command templatingOptions := yttcmd.NewOptions() @@ -21,6 +21,11 @@ func ytt(tpl, dvs []string) (io.Reader, error) { return nil, err } + if len(varFiles) > 0 { + // Load vaarFiles into the templating options. + templatingOptions.DataValuesFlags.FromFiles = varFiles + } + // equivalent to `--data-value-yaml` templatingOptions.DataValuesFlags.KVsFromYAML = dvs diff --git a/pkg/input/provider/interface.go b/pkg/input/provider/interface.go index 769af60fb..c98214dd3 100644 --- a/pkg/input/provider/interface.go +++ b/pkg/input/provider/interface.go @@ -117,6 +117,7 @@ func NewInputProvider(opts InputOptions) (InputProvider, error) { SkipFormatValidation: opts.Options.SkipFormatValidation, RequiredOnly: opts.Options.FormatUseRequiredOnly, VarsTextTemplating: opts.Options.VarsTextTemplating, + VarsFilePaths: opts.Options.VarsFilePaths, }, }) } diff --git a/pkg/types/types.go b/pkg/types/types.go index 5b71225cb..47c434072 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -421,6 +421,8 @@ type Options struct { SkipFormatValidation bool // VarsTextTemplating is used to inject variables into yaml input files VarsTextTemplating bool + // VarsFilePaths is used to inject variables into yaml input files from a file + VarsFilePaths goflags.StringSlice // PayloadConcurrency is the number of concurrent payloads to run per template PayloadConcurrency int // ProbeConcurrency is the number of concurrent http probes to run with httpx