Path: blob/dev/pkg/protocols/http/request_fuzz.go
2070 views
package http12// === Fuzzing Documentation (Scoped to this File) =====3// -> request.executeFuzzingRule [iterates over payloads(+requests) and executes]4// -> request.executePayloadUsingRules [executes single payload on all rules (if more than 1)]5// -> request.executeGeneratedFuzzingRequest [execute final generated fuzzing request and get result]67import (8"context"9"fmt"10"net/http"11"strings"1213"github.com/pkg/errors"14"github.com/projectdiscovery/gologger"15"github.com/projectdiscovery/nuclei/v3/pkg/fuzz"16"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/analyzers"17"github.com/projectdiscovery/nuclei/v3/pkg/operators"18"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"19"github.com/projectdiscovery/nuclei/v3/pkg/output"20"github.com/projectdiscovery/nuclei/v3/pkg/protocols"21"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"22"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"23"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"24"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"25protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"26"github.com/projectdiscovery/nuclei/v3/pkg/types"27"github.com/projectdiscovery/retryablehttp-go"28"github.com/projectdiscovery/useragent"29urlutil "github.com/projectdiscovery/utils/url"30)3132// executeFuzzingRule executes fuzzing request for a URL33// TODO:34// 1. use SPMHandler and rewrite stop at first match logic here35// 2. use scanContext instead of contextargs.Context36func (request *Request) executeFuzzingRule(input *contextargs.Context, previous output.InternalEvent, callback protocols.OutputEventCallback) error {37// methdology:38// to check applicablity of rule, we first try to execute it with one value39// if it is applicable, we execute all requests40// if it is not applicable, we log and fail silently4142// check if target should be fuzzed or not43if !request.ShouldFuzzTarget(input) {44urlx, _ := input.MetaInput.URL()45if urlx != nil {46gologger.Verbose().Msgf("[%s] fuzz: target(%s) not applicable for fuzzing\n", request.options.TemplateID, urlx.String())47} else {48gologger.Verbose().Msgf("[%s] fuzz: target(%s) not applicable for fuzzing\n", request.options.TemplateID, input.MetaInput.Input)49}50return nil51}5253if input.MetaInput.Input == "" && input.MetaInput.ReqResp == nil {54return errors.New("empty input provided for fuzzing")55}5657// ==== fuzzing when full HTTP request is provided =====5859if input.MetaInput.ReqResp != nil {60baseRequest, err := input.MetaInput.ReqResp.BuildRequest()61if err != nil {62return errors.Wrap(err, "fuzz: could not build request obtained from target file")63}64request.addHeadersToRequest(baseRequest)65input.MetaInput.Input = baseRequest.String()66// execute with one value first to checks its applicability67err = request.executeAllFuzzingRules(input, previous, baseRequest, callback)68if err != nil {69// in case of any error, return it70if fuzz.IsErrRuleNotApplicable(err) {71// log and fail silently72gologger.Verbose().Msgf("[%s] fuzz: %s\n", request.options.TemplateID, err)73return nil74}75if errors.Is(err, ErrMissingVars) {76return err77}78gologger.Verbose().Msgf("[%s] fuzz: payload request execution failed: %s\n", request.options.TemplateID, err)79}80return nil81}8283// ==== fuzzing when only URL is provided =====8485// we need to use this url instead of input86inputx := input.Clone()87parsed, err := urlutil.ParseAbsoluteURL(input.MetaInput.Input, true)88if err != nil {89return errors.Wrap(err, "fuzz: could not parse input url")90}91baseRequest, err := retryablehttp.NewRequestFromURL(http.MethodGet, parsed, nil)92if err != nil {93return errors.Wrap(err, "fuzz: could not build request from url")94}95userAgent := useragent.PickRandom()96baseRequest.Header.Set("User-Agent", userAgent.Raw)97request.addHeadersToRequest(baseRequest)9899// execute with one value first to checks its applicability100err = request.executeAllFuzzingRules(inputx, previous, baseRequest, callback)101if err != nil {102// in case of any error, return it103if fuzz.IsErrRuleNotApplicable(err) {104// log and fail silently105gologger.Verbose().Msgf("[%s] fuzz: %s\n", request.options.TemplateID, err)106return nil107}108if errors.Is(err, ErrMissingVars) {109return err110}111gologger.Verbose().Msgf("[%s] fuzz: payload request execution failed: %s\n", request.options.TemplateID, err)112}113return nil114}115116func (request *Request) addHeadersToRequest(baseRequest *retryablehttp.Request) {117for k, v := range request.Headers {118baseRequest.Header.Set(k, v)119}120}121122// executeAllFuzzingRules executes all fuzzing rules defined in template for a given base request123func (request *Request) executeAllFuzzingRules(input *contextargs.Context, values map[string]interface{}, baseRequest *retryablehttp.Request, callback protocols.OutputEventCallback) error {124applicable := false125values = generators.MergeMaps(request.filterDataMap(input), values)126for _, rule := range request.Fuzzing {127select {128case <-input.Context().Done():129return input.Context().Err()130default:131}132133input := &fuzz.ExecuteRuleInput{134Input: input,135DisplayFuzzPoints: request.options.Options.DisplayFuzzPoints,136Callback: func(gr fuzz.GeneratedRequest) bool {137select {138case <-input.Context().Done():139return false140default:141}142143// TODO: replace this after scanContext Refactor144return request.executeGeneratedFuzzingRequest(gr, input, callback)145},146Values: values,147BaseRequest: baseRequest.Clone(context.TODO()),148}149if request.Analyzer != nil {150analyzer := analyzers.GetAnalyzer(request.Analyzer.Name)151input.ApplyPayloadInitialTransformation = analyzer.ApplyInitialTransformation152input.AnalyzerParams = request.Analyzer.Parameters153}154err := rule.Execute(input)155if err == nil {156applicable = true157continue158}159if fuzz.IsErrRuleNotApplicable(err) {160gologger.Verbose().Msgf("[%s] fuzz: %s\n", request.options.TemplateID, err)161continue162}163if err == types.ErrNoMoreRequests {164return nil165}166return errors.Wrap(err, "could not execute rule")167}168169if !applicable {170return fmt.Errorf("no rule was applicable for this request: %v", input.MetaInput.Input)171}172173return nil174}175176// executeGeneratedFuzzingRequest executes a generated fuzzing request after building it using rules and payloads177func (request *Request) executeGeneratedFuzzingRequest(gr fuzz.GeneratedRequest, input *contextargs.Context, callback protocols.OutputEventCallback) bool {178hasInteractMatchers := interactsh.HasMatchers(request.CompiledOperators)179hasInteractMarkers := len(gr.InteractURLs) > 0180if request.options.HostErrorsCache != nil && request.options.HostErrorsCache.Check(request.options.ProtocolType.String(), input) {181return false182}183request.options.RateLimitTake()184req := &generatedRequest{185request: gr.Request,186dynamicValues: gr.DynamicValues,187interactshURLs: gr.InteractURLs,188original: request,189fuzzGeneratedRequest: gr,190}191var gotMatches bool192requestErr := request.executeRequest(input, req, gr.DynamicValues, hasInteractMatchers, func(event *output.InternalWrappedEvent) {193for _, result := range event.Results {194result.IsFuzzingResult = true195result.FuzzingMethod = gr.Request.Method196result.FuzzingParameter = gr.Parameter197result.FuzzingPosition = gr.Component.Name()198}199200setInteractshCallback := false201if hasInteractMarkers && hasInteractMatchers && request.options.Interactsh != nil {202requestData := &interactsh.RequestData{203MakeResultFunc: request.MakeResultEvent,204Event: event,205Operators: request.CompiledOperators,206MatchFunc: request.Match,207ExtractFunc: request.Extract,208Parameter: gr.Parameter,209Request: gr.Request,210}211setInteractshCallback = true212request.options.Interactsh.RequestEvent(gr.InteractURLs, requestData)213gotMatches = request.options.Interactsh.AlreadyMatched(requestData)214} else {215callback(event)216}217// Add the extracts to the dynamic values if any.218if event.OperatorsResult != nil {219gotMatches = event.OperatorsResult.Matched220}221if request.options.FuzzParamsFrequency != nil && !setInteractshCallback {222if !gotMatches {223request.options.FuzzParamsFrequency.MarkParameter(gr.Parameter, gr.Request.String(), request.options.TemplateID)224} else {225request.options.FuzzParamsFrequency.UnmarkParameter(gr.Parameter, gr.Request.String(), request.options.TemplateID)226}227}228}, 0)229// If a variable is unresolved, skip all further requests230if errors.Is(requestErr, ErrMissingVars) {231return false232}233if requestErr != nil {234gologger.Verbose().Msgf("[%s] Error occurred in request: %s\n", request.options.TemplateID, requestErr)235}236if request.options.HostErrorsCache != nil {237request.options.HostErrorsCache.MarkFailedOrRemove(request.options.ProtocolType.String(), input, requestErr)238}239request.options.Progress.IncrementRequests()240241// If this was a match, and we want to stop at first match, skip all further requests.242shouldStopAtFirstMatch := request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch243if shouldStopAtFirstMatch && gotMatches {244return false245}246return true247}248249// ShouldFuzzTarget checks if given target should be fuzzed or not using `filter` field in template250func (request *Request) ShouldFuzzTarget(input *contextargs.Context) bool {251if len(request.FuzzPreCondition) == 0 {252return true253}254status := []bool{}255for index, filter := range request.FuzzPreCondition {256dataMap := request.filterDataMap(input)257// dump if svd is enabled258if request.options.Options.ShowVarDump {259gologger.Debug().Msgf("Fuzz Filter Variables: \n%s\n", vardump.DumpVariables(dataMap))260}261isMatch, _ := request.Match(dataMap, filter)262status = append(status, isMatch)263if request.options.Options.MatcherStatus {264gologger.Debug().Msgf("[%s] [%s] Filter => %s : %v", input.MetaInput.Target(), request.options.TemplateID, operators.GetMatcherName(filter, index), isMatch)265}266}267if len(status) == 0 {268return true269}270var matched bool271if request.fuzzPreConditionOperator == matchers.ANDCondition {272matched = operators.EvalBoolSlice(status, true)273} else {274matched = operators.EvalBoolSlice(status, false)275}276if request.options.Options.MatcherStatus {277gologger.Debug().Msgf("[%s] [%s] Final Filter Status => %v", input.MetaInput.Target(), request.options.TemplateID, matched)278}279return matched280}281282// input data map returns map[string]interface{} from input283func (request *Request) filterDataMap(input *contextargs.Context) map[string]interface{} {284m := make(map[string]interface{})285parsed, err := input.MetaInput.URL()286if err != nil {287m["host"] = input.MetaInput.Input288return m289}290m = protocolutils.GenerateVariables(parsed, true, m)291for k, v := range m {292m[strings.ToLower(k)] = v293}294m["path"] = parsed.Path // override existing295m["query"] = parsed.RawQuery296// add request data like headers, body etc297if input.MetaInput.ReqResp != nil && input.MetaInput.ReqResp.Request != nil {298req := input.MetaInput.ReqResp.Request299m["method"] = req.Method300m["body"] = req.Body301302sb := &strings.Builder{}303req.Headers.Iterate(func(k, v string) bool {304k = strings.ToLower(strings.ReplaceAll(strings.TrimSpace(k), "-", "_"))305if strings.EqualFold(k, "Cookie") {306m["cookie"] = v307}308if strings.EqualFold(k, "User_Agent") {309m["user_agent"] = v310}311if strings.EqualFold(k, "content_type") {312m["content_type"] = v313}314_, _ = fmt.Fprintf(sb, "%s: %s\n", k, v)315return true316})317m["header"] = sb.String()318} else {319// add default method value320m["method"] = http.MethodGet321}322return m323}324325326