Adding support for constants (#3692)

* adding support for constants

* fixing typo

* adding integration test

* fixing lint issues

* fixing template syntax
dev
Mzack9999 2023-05-25 18:32:35 +02:00 committed by GitHub
parent afaf850c89
commit 0d2d510689
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 64 additions and 17 deletions

View File

@ -0,0 +1,18 @@
id: cli-with-constants
info:
name: Cli Var with Constants
author: pdteam
severity: info
constants:
test: test-in-template
requests:
- method: GET
path:
- "{{BaseURL}}?p={{test}}"
matchers:
- type: word
words:
- "test-in-template"

View File

@ -77,6 +77,7 @@ var httpTestcases = map[string]testutils.TestCase{
"http/cl-body-without-header.yaml": &httpCLBodyWithoutHeader{}, "http/cl-body-without-header.yaml": &httpCLBodyWithoutHeader{},
"http/cl-body-with-header.yaml": &httpCLBodyWithHeader{}, "http/cl-body-with-header.yaml": &httpCLBodyWithHeader{},
"http/save-extractor-values-to-file.yaml": &httpSaveExtractorValuesToFile{}, "http/save-extractor-values-to-file.yaml": &httpSaveExtractorValuesToFile{},
"http/cli-with-constants.yaml": &ConstantWithCliVar{},
} }
type httpInteractshRequest struct{} type httpInteractshRequest struct{}
@ -1403,3 +1404,22 @@ func (h *httpSaveExtractorValuesToFile) Execute(filePath string) error {
} }
return expectResultsCount(results, 1) return expectResultsCount(results, 1)
} }
// constant shouldn't be overwritten by cli var with same name
type ConstantWithCliVar struct{}
// Execute executes a test case and returns an error if occurred
func (h *ConstantWithCliVar) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, r.URL.Query().Get("p"))
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-V", "test=fromcli")
if err != nil {
return err
}
return expectResultsCount(got, 1)
}

View File

@ -6,6 +6,7 @@ package variables
// 3. OptionsMap - Variables passed using CLI Options (+ Env) (available at generators.BuildPayloadFromOptions) // 3. OptionsMap - Variables passed using CLI Options (+ Env) (available at generators.BuildPayloadFromOptions)
// 4. DynamicMap - Variables Obtained by extracting data from templates (available at Request.ExecuteWithResults + merged with previous internalEvent) // 4. DynamicMap - Variables Obtained by extracting data from templates (available at Request.ExecuteWithResults + merged with previous internalEvent)
// 5. ProtocolMap - Variables generated by Evaluation Request / Responses of xyz protocol (available in Request.Make) // 5. ProtocolMap - Variables generated by Evaluation Request / Responses of xyz protocol (available in Request.Make)
// 6. ConstantsMap - Constants defined in the template (available at Request.options.Constants in protocols)
// As we can tell , all variables sources are not linear i.e why they need to re-evaluated // As we can tell , all variables sources are not linear i.e why they need to re-evaluated
// consider example // consider example
@ -22,3 +23,5 @@ package variables
// 1. VariablesMap // 1. VariablesMap
// 2. PayloadsMap // 2. PayloadsMap
// Everytime Linear Sources are updated , Non-Linear Sources need to be re-evaluated // Everytime Linear Sources are updated , Non-Linear Sources need to be re-evaluated
// Constants (no need to re-evaluate, should contain only scalars)

View File

@ -55,7 +55,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata,
// merge with metadata (eg. from workflow context) // merge with metadata (eg. from workflow context)
vars = generators.MergeMaps(vars, metadata, optionVars) vars = generators.MergeMaps(vars, metadata, optionVars)
variablesMap := request.options.Variables.Evaluate(vars) variablesMap := request.options.Variables.Evaluate(vars)
vars = generators.MergeMaps(vars, variablesMap) vars = generators.MergeMaps(vars, variablesMap, request.options.Constants)
if request.generator != nil { if request.generator != nil {
iterator := request.generator.NewIterator() iterator := request.generator.NewIterator()

View File

@ -64,10 +64,8 @@ func newHttpClient(options *types.Options) (*http.Client, error) {
dc := dialer.(interface { dc := dialer.(interface {
DialContext(ctx context.Context, network, addr string) (net.Conn, error) DialContext(ctx context.Context, network, addr string) (net.Conn, error)
}) })
if proxyErr == nil {
transport.DialContext = dc.DialContext transport.DialContext = dc.DialContext
} }
}
jar, _ := cookiejar.New(nil) jar, _ := cookiejar.New(nil)

View File

@ -41,7 +41,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata,
payloads := generators.BuildPayloadFromOptions(request.options.Options) payloads := generators.BuildPayloadFromOptions(request.options.Options)
values := generators.MergeMaps(vars, metadata, payloads) values := generators.MergeMaps(vars, metadata, payloads)
variablesMap := request.options.Variables.Evaluate(values) variablesMap := request.options.Variables.Evaluate(values)
payloads = generators.MergeMaps(variablesMap, payloads) payloads = generators.MergeMaps(variablesMap, payloads, request.options.Constants)
// check for operator matches by wrapping callback // check for operator matches by wrapping callback
gotmatches := false gotmatches := false

View File

@ -112,7 +112,7 @@ func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context,
r.interactshURLs = append(r.interactshURLs, interactURLs...) r.interactshURLs = append(r.interactshURLs, interactURLs...)
} }
// allVars contains all variables from all sources // allVars contains all variables from all sources
allVars := generators.MergeMaps(dynamicValues, defaultReqVars, optionVars, variablesMap) allVars := generators.MergeMaps(dynamicValues, defaultReqVars, optionVars, variablesMap, r.options.Constants)
// Evaluate payload variables // Evaluate payload variables
// eg: payload variables can be username: jon.doe@{{Hostname}} // eg: payload variables can be username: jon.doe@{{Hostname}}
@ -170,10 +170,10 @@ func (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data st
signerVars := GetDefaultSignerVars(r.request.Signature.Value) signerVars := GetDefaultSignerVars(r.request.Signature.Value)
// this will ensure that default signer variables are overwritten by other variables // this will ensure that default signer variables are overwritten by other variables
values = generators.MergeMaps(signerVars, values) values = generators.MergeMaps(signerVars, values, r.options.Constants)
// priority of variables is as follows (from low to high) for self contained templates // priority of variables is as follows (from low to high) for self contained templates
// default signer vars < variables < cli vars < payload < dynamic values // default signer vars < variables < cli vars < payload < dynamic values < constants
// evaluate request // evaluate request
data, err := expressions.Evaluate(data, values) data, err := expressions.Evaluate(data, values)

View File

@ -47,7 +47,7 @@ func (rule *Rule) Execute(input *ExecuteRuleInput) error {
baseValues := input.Values baseValues := input.Values
if rule.generator == nil { if rule.generator == nil {
evaluatedValues, interactURLs := rule.options.Variables.EvaluateWithInteractsh(baseValues, rule.options.Interactsh) evaluatedValues, interactURLs := rule.options.Variables.EvaluateWithInteractsh(baseValues, rule.options.Interactsh)
input.Values = generators.MergeMaps(evaluatedValues, baseValues) input.Values = generators.MergeMaps(evaluatedValues, baseValues, rule.options.Constants)
input.InteractURLs = interactURLs input.InteractURLs = interactURLs
err := rule.executeRuleValues(input) err := rule.executeRuleValues(input)
return err return err
@ -60,7 +60,7 @@ func (rule *Rule) Execute(input *ExecuteRuleInput) error {
} }
evaluatedValues, interactURLs := rule.options.Variables.EvaluateWithInteractsh(generators.MergeMaps(values, baseValues), rule.options.Interactsh) evaluatedValues, interactURLs := rule.options.Variables.EvaluateWithInteractsh(generators.MergeMaps(values, baseValues), rule.options.Interactsh)
input.InteractURLs = interactURLs input.InteractURLs = interactURLs
input.Values = generators.MergeMaps(values, evaluatedValues, baseValues) input.Values = generators.MergeMaps(values, evaluatedValues, baseValues, rule.options.Constants)
if err := rule.executeRuleValues(input); err != nil { if err := rule.executeRuleValues(input); err != nil {
return err return err

View File

@ -318,7 +318,7 @@ func (request *Request) executeFuzzingRule(input *contextargs.Context, previous
func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error { func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
if request.Pipeline || request.Race && request.RaceNumberRequests > 0 || request.Threads > 0 { if request.Pipeline || request.Race && request.RaceNumberRequests > 0 || request.Threads > 0 {
variablesMap := request.options.Variables.Evaluate(generators.MergeMaps(dynamicValues, previous)) variablesMap := request.options.Variables.Evaluate(generators.MergeMaps(dynamicValues, previous))
dynamicValues = generators.MergeMaps(variablesMap, dynamicValues) dynamicValues = generators.MergeMaps(variablesMap, dynamicValues, request.options.Constants)
} }
// verify if pipeline was requested // verify if pipeline was requested
if request.Pipeline { if request.Pipeline {
@ -638,7 +638,7 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
if !request.Unsafe && resp != nil && generatedRequest.request != nil && resp.Request != nil && !request.Race { if !request.Unsafe && resp != nil && generatedRequest.request != nil && resp.Request != nil && !request.Race {
bodyBytes, _ := generatedRequest.request.BodyBytes() bodyBytes, _ := generatedRequest.request.BodyBytes()
resp.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes)) resp.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes))
command, _ := http2curl.GetCurlCommand(resp.Request) command, err := http2curl.GetCurlCommand(resp.Request)
if err == nil && command != nil { if err == nil && command != nil {
curlCommand = command.String() curlCommand = command.String()
} }

View File

@ -38,7 +38,7 @@ func (request *Request) Type() templateTypes.ProtocolType {
} }
// ExecuteWithResults executes the protocol requests and returns results instead of writing them. // ExecuteWithResults executes the protocol requests and returns results instead of writing them.
func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata /*TODO review unused parameter*/, previous output.InternalEvent, callback protocols.OutputEventCallback) error { func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
var address string var address string
var err error var err error
@ -54,7 +54,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata
} }
variables := protocolutils.GenerateVariables(address, false, nil) variables := protocolutils.GenerateVariables(address, false, nil)
variablesMap := request.options.Variables.Evaluate(variables) variablesMap := request.options.Variables.Evaluate(variables)
variables = generators.MergeMaps(variablesMap, variables) variables = generators.MergeMaps(variablesMap, variables, request.options.Constants)
for _, kv := range request.addresses { for _, kv := range request.addresses {
actualAddress := replacer.Replace(kv.address, variables) actualAddress := replacer.Replace(kv.address, variables)

View File

@ -70,6 +70,8 @@ type ExecuterOptions struct {
StopAtFirstMatch bool StopAtFirstMatch bool
// Variables is a list of variables from template // Variables is a list of variables from template
Variables variables.Variable Variables variables.Variable
// Constants is a list of constants from template
Constants map[string]interface{}
// ExcludeMatchers is the list of matchers to exclude // ExcludeMatchers is the list of matchers to exclude
ExcludeMatchers *excludematchers.ExcludeMatchers ExcludeMatchers *excludematchers.ExcludeMatchers
// InputHelper is a helper for input normalization // InputHelper is a helper for input normalization

View File

@ -190,7 +190,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
hostnameVariables := protocolutils.GenerateDNSVariables(hostname) hostnameVariables := protocolutils.GenerateDNSVariables(hostname)
values := generators.MergeMaps(payloadValues, hostnameVariables) values := generators.MergeMaps(payloadValues, hostnameVariables)
variablesMap := request.options.Variables.Evaluate(values) variablesMap := request.options.Variables.Evaluate(values)
payloadValues = generators.MergeMaps(variablesMap, payloadValues) payloadValues = generators.MergeMaps(variablesMap, payloadValues, request.options.Constants)
if vardump.EnableVarDump { if vardump.EnableVarDump {
gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(payloadValues)) gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(payloadValues))

View File

@ -176,7 +176,7 @@ func (request *Request) executeRequestWithPayloads(input, hostname string, dynam
defaultVars := protocolutils.GenerateVariables(parsed, false, nil) defaultVars := protocolutils.GenerateVariables(parsed, false, nil)
optionVars := generators.BuildPayloadFromOptions(request.options.Options) optionVars := generators.BuildPayloadFromOptions(request.options.Options)
variables := request.options.Variables.Evaluate(generators.MergeMaps(defaultVars, optionVars, dynamicValues)) variables := request.options.Variables.Evaluate(generators.MergeMaps(defaultVars, optionVars, dynamicValues))
payloadValues := generators.MergeMaps(variables, defaultVars, optionVars, dynamicValues) payloadValues := generators.MergeMaps(variables, defaultVars, optionVars, dynamicValues, request.options.Constants)
requestOptions := request.options requestOptions := request.options
for key, value := range request.Headers { for key, value := range request.Headers {

View File

@ -92,7 +92,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
optionVars := generators.BuildPayloadFromOptions(request.options.Options) optionVars := generators.BuildPayloadFromOptions(request.options.Options)
vars := request.options.Variables.Evaluate(generators.MergeMaps(defaultVars, optionVars, dynamicValues)) vars := request.options.Variables.Evaluate(generators.MergeMaps(defaultVars, optionVars, dynamicValues))
variables := generators.MergeMaps(vars, defaultVars, optionVars, dynamicValues) variables := generators.MergeMaps(vars, defaultVars, optionVars, dynamicValues, request.options.Constants)
if vardump.EnableVarDump { if vardump.EnableVarDump {
gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(variables)) gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(variables))

View File

@ -232,6 +232,8 @@ func ParseTemplateFromReader(reader io.Reader, preprocessor Preprocessor, option
options.Variables = template.Variables options.Variables = template.Variables
} }
options.Constants = template.Constants
// If no requests, and it is also not a workflow, return error. // If no requests, and it is also not a workflow, return error.
if template.Requests() == 0 { if template.Requests() == 0 {
return nil, fmt.Errorf("no requests defined for %s", template.ID) return nil, fmt.Errorf("no requests defined for %s", template.ID)

View File

@ -113,6 +113,10 @@ type Template struct {
// Variables contains any variables for the current request. // Variables contains any variables for the current request.
Variables variables.Variable `yaml:"variables,omitempty" json:"variables,omitempty" jsonschema:"title=variables for the http request,description=Variables contains any variables for the current request"` Variables variables.Variable `yaml:"variables,omitempty" json:"variables,omitempty" jsonschema:"title=variables for the http request,description=Variables contains any variables for the current request"`
// description: |
// Constants contains any scalar costant for the current template
Constants map[string]interface{} `yaml:"constants,omitempty" json:"constants,omitempty" jsonschema:"title=constant for the template,description=constants contains any constant for the template"`
// TotalRequests is the total number of requests for the template. // TotalRequests is the total number of requests for the template.
TotalRequests int `yaml:"-" json:"-"` TotalRequests int `yaml:"-" json:"-"`
// Executer is the actual template executor for running template requests // Executer is the actual template executor for running template requests