Path: blob/dev/pkg/protocols/common/variables/variables.go
2843 views
package variables12import (3"strings"45"github.com/Knetic/govaluate"6"github.com/invopop/jsonschema"7"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl"8"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"9"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"10"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"11"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/marker"12protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"13"github.com/projectdiscovery/nuclei/v3/pkg/types"14"github.com/projectdiscovery/nuclei/v3/pkg/utils"15"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"16stringsutil "github.com/projectdiscovery/utils/strings"17)1819// Variable is a key-value pair of strings that can be used20// throughout template.21type Variable struct {22// LazyEval is used to evaluate variables lazily if it using any expression23// or global variables.24LazyEval bool `yaml:"-" json:"-"`25utils.InsertionOrderedStringMap `yaml:"-" json:"-"`26}2728func (variables Variable) JSONSchema() *jsonschema.Schema {29gotType := &jsonschema.Schema{30Type: "object",31Title: "variables for the request",32Description: "Additional variables for the request",33AdditionalProperties: &jsonschema.Schema{},34}35return gotType36}3738func (variables *Variable) UnmarshalYAML(unmarshal func(interface{}) error) error {39variables.InsertionOrderedStringMap = utils.InsertionOrderedStringMap{}40if err := unmarshal(&variables.InsertionOrderedStringMap); err != nil {41return err42}4344if variables.LazyEval || variables.checkForLazyEval() {45return nil46}4748evaluated := variables.Evaluate(map[string]interface{}{})4950for k, v := range evaluated {51variables.Set(k, v)52}53return nil54}5556func (variables *Variable) UnmarshalJSON(data []byte) error {57variables.InsertionOrderedStringMap = utils.InsertionOrderedStringMap{}58if err := json.Unmarshal(data, &variables.InsertionOrderedStringMap); err != nil {59return err60}61evaluated := variables.Evaluate(map[string]interface{}{})6263for k, v := range evaluated {64variables.Set(k, v)65}66return nil67}6869// Evaluate returns a finished map of variables based on set values70func (variables *Variable) Evaluate(values map[string]interface{}) map[string]interface{} {71result := make(map[string]interface{}, variables.Len())72combined := make(map[string]interface{}, len(values)+variables.Len())73generators.MergeMapsInto(combined, values)7475variables.ForEach(func(key string, value interface{}) {76if sliceValue, ok := value.([]interface{}); ok {77// slices cannot be evaluated78result[key] = sliceValue79combined[key] = sliceValue80return81}82valueString := types.ToString(value)83if existingValue, ok := combined[key]; ok {84valueString = types.ToString(existingValue)85}86evaluated := evaluateVariableValueWithMap(valueString, combined)87result[key] = evaluated88combined[key] = evaluated89})90return result91}9293// GetAll returns all variables as a map94func (variables *Variable) GetAll() map[string]interface{} {95result := make(map[string]interface{}, variables.Len())96variables.ForEach(func(key string, value interface{}) {97result[key] = value98})99return result100}101102// EvaluateWithInteractsh returns evaluation results of variables with interactsh103func (variables *Variable) EvaluateWithInteractsh(values map[string]interface{}, interact *interactsh.Client) (map[string]interface{}, []string) {104result := make(map[string]interface{}, variables.Len())105combined := make(map[string]interface{}, len(values)+variables.Len())106generators.MergeMapsInto(combined, values)107108var interactURLs []string109variables.ForEach(func(key string, value interface{}) {110if sliceValue, ok := value.([]interface{}); ok {111// slices cannot be evaluated112result[key] = sliceValue113combined[key] = sliceValue114return115}116valueString := types.ToString(value)117if existingValue, ok := combined[key]; ok {118valueString = types.ToString(existingValue)119}120if strings.Contains(valueString, "interactsh-url") {121valueString, interactURLs = interact.Replace(valueString, interactURLs)122}123evaluated := evaluateVariableValueWithMap(valueString, combined)124result[key] = evaluated125combined[key] = evaluated126})127return result, interactURLs128}129130// evaluateVariableValue expression and returns final value.131//132// Deprecated: use evaluateVariableValueWithMap instead to avoid repeated map133// merging overhead.134func evaluateVariableValue(expression string, values, processing map[string]interface{}) string { // nolint135finalMap := generators.MergeMaps(values, processing)136result, err := expressions.Evaluate(expression, finalMap)137if err != nil {138return expression139}140141return result142}143144// evaluateVariableValueWithMap evaluates an expression with a pre-merged map.145func evaluateVariableValueWithMap(expression string, combinedMap map[string]interface{}) string {146result, err := expressions.Evaluate(expression, combinedMap)147if err != nil {148return expression149}150151return result152}153154// checkForLazyEval checks if the variables have any lazy evaluation i.e any dsl function155// and sets the flag accordingly.156func (variables *Variable) checkForLazyEval() bool {157var needsLazy bool158159variables.ForEach(func(key string, value interface{}) {160if needsLazy {161return162}163164for _, v := range protocolutils.KnownVariables {165if stringsutil.ContainsAny(types.ToString(value), v) {166needsLazy = true167return168}169}170171// this is a hotfix and not the best way to do it172// will be refactored once we move scan state to scanContext (see: https://github.com/projectdiscovery/nuclei/issues/4631)173if strings.Contains(types.ToString(value), "interactsh-url") {174needsLazy = true175return176}177178if hasUndefinedParams(types.ToString(value), variables) {179needsLazy = true180return181}182})183184variables.LazyEval = needsLazy185186return variables.LazyEval187}188189// hasUndefinedParams checks if a variable value contains expressions that ref190// parameters not defined in the current variable scope, indicating it needs191// runtime context.192func hasUndefinedParams(value string, variables *Variable) bool {193exprs := expressions.FindExpressions(value, marker.ParenthesisOpen, marker.ParenthesisClose, map[string]interface{}{})194if len(exprs) == 0 {195return false196}197198definedVars := make(map[string]struct{})199variables.ForEach(func(key string, _ interface{}) {200definedVars[key] = struct{}{}201})202203for _, expr := range exprs {204compiled, err := govaluate.NewEvaluableExpressionWithFunctions(expr, dsl.HelperFunctions)205if err != nil {206// NOTE(dwisiswant0): here, it might need runtime context.207return true208}209210vars := compiled.Vars()211for _, paramName := range vars {212// NOTE(dwisiswant0): also here, if it's not in our defined vars.213if _, exists := definedVars[paramName]; !exists {214return true215}216}217}218219return false220}221222223