github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/http/request.go (about)

     1  package http
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/hex"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"net/http/httputil"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/pkg/errors"
    17  	"github.com/remeh/sizedwaitgroup"
    18  	"go.uber.org/multierr"
    19  	"moul.io/http2curl"
    20  
    21  	"github.com/projectdiscovery/fastdialer/fastdialer"
    22  	"github.com/projectdiscovery/gologger"
    23  	"github.com/projectdiscovery/nuclei/v2/pkg/operators"
    24  	"github.com/projectdiscovery/nuclei/v2/pkg/output"
    25  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
    26  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs"
    27  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions"
    28  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/fuzz"
    29  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
    30  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator"
    31  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter"
    32  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh"
    33  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/tostring"
    34  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/httpclientpool"
    35  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/signer"
    36  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/signerpool"
    37  	templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
    38  	"github.com/projectdiscovery/nuclei/v2/pkg/types"
    39  	"github.com/projectdiscovery/rawhttp"
    40  	"github.com/projectdiscovery/utils/reader"
    41  	sliceutil "github.com/projectdiscovery/utils/slice"
    42  	stringsutil "github.com/projectdiscovery/utils/strings"
    43  	urlutil "github.com/projectdiscovery/utils/url"
    44  )
    45  
    46  const defaultMaxWorkers = 150
    47  
    48  // Type returns the type of the protocol request
    49  func (request *Request) Type() templateTypes.ProtocolType {
    50  	return templateTypes.HTTPProtocol
    51  }
    52  
    53  // executeRaceRequest executes race condition request for a URL
    54  func (request *Request) executeRaceRequest(input *contextargs.Context, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
    55  	reqURL := input.MetaInput.Input
    56  	var generatedRequests []*generatedRequest
    57  
    58  	// Requests within race condition should be dumped once and the output prefilled to allow DSL language to work
    59  	// This will introduce a delay and will populate in hacky way the field "request" of outputEvent
    60  	generator := request.newGenerator(false)
    61  
    62  	inputData, payloads, ok := generator.nextValue()
    63  	if !ok {
    64  		return nil
    65  	}
    66  	ctx := request.newContext(input)
    67  	requestForDump, err := generator.Make(ctx, input, inputData, payloads, nil)
    68  	if err != nil {
    69  		return err
    70  	}
    71  	request.setCustomHeaders(requestForDump)
    72  	dumpedRequest, err := dump(requestForDump, reqURL)
    73  	if err != nil {
    74  		return err
    75  	}
    76  	if request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse {
    77  		msg := fmt.Sprintf("[%s] Dumped HTTP request for %s\n\n", request.options.TemplateID, reqURL)
    78  		if request.options.Options.Debug || request.options.Options.DebugRequests {
    79  			gologger.Info().Msg(msg)
    80  			gologger.Print().Msgf("%s", string(dumpedRequest))
    81  		}
    82  		if request.options.Options.StoreResponse {
    83  			request.options.Output.WriteStoreDebugData(reqURL, request.options.TemplateID, request.Type().String(), fmt.Sprintf("%s\n%s", msg, dumpedRequest))
    84  		}
    85  	}
    86  	previous["request"] = string(dumpedRequest)
    87  
    88  	// Pre-Generate requests
    89  	for i := 0; i < request.RaceNumberRequests; i++ {
    90  		generator := request.newGenerator(false)
    91  		inputData, payloads, ok := generator.nextValue()
    92  		if !ok {
    93  			break
    94  		}
    95  		ctx := request.newContext(input)
    96  		generatedRequest, err := generator.Make(ctx, input, inputData, payloads, nil)
    97  		if err != nil {
    98  			return err
    99  		}
   100  		generatedRequests = append(generatedRequests, generatedRequest)
   101  	}
   102  
   103  	wg := sync.WaitGroup{}
   104  	var requestErr error
   105  	mutex := &sync.Mutex{}
   106  	for i := 0; i < request.RaceNumberRequests; i++ {
   107  		wg.Add(1)
   108  		go func(httpRequest *generatedRequest) {
   109  			defer wg.Done()
   110  			err := request.executeRequest(input, httpRequest, previous, false, callback, 0)
   111  			mutex.Lock()
   112  			if err != nil {
   113  				requestErr = multierr.Append(requestErr, err)
   114  			}
   115  			mutex.Unlock()
   116  		}(generatedRequests[i])
   117  		request.options.Progress.IncrementRequests()
   118  	}
   119  	wg.Wait()
   120  
   121  	return requestErr
   122  }
   123  
   124  // executeRaceRequest executes parallel requests for a template
   125  func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicValues output.InternalEvent, callback protocols.OutputEventCallback) error {
   126  	generator := request.newGenerator(false)
   127  	// Workers that keeps enqueuing new requests
   128  	maxWorkers := request.Threads
   129  	swg := sizedwaitgroup.New(maxWorkers)
   130  
   131  	var requestErr error
   132  	mutex := &sync.Mutex{}
   133  	for {
   134  		inputData, payloads, ok := generator.nextValue()
   135  		if !ok {
   136  			break
   137  		}
   138  		ctx := request.newContext(input)
   139  		generatedHttpRequest, err := generator.Make(ctx, input, inputData, payloads, dynamicValues)
   140  		if err != nil {
   141  			if err == types.ErrNoMoreRequests {
   142  				break
   143  			}
   144  			request.options.Progress.IncrementFailedRequestsBy(int64(generator.Total()))
   145  			return err
   146  		}
   147  		if input.MetaInput.Input == "" {
   148  			input.MetaInput.Input = generatedHttpRequest.URL()
   149  		}
   150  		swg.Add()
   151  		go func(httpRequest *generatedRequest) {
   152  			defer swg.Done()
   153  
   154  			request.options.RateLimiter.Take()
   155  
   156  			previous := make(map[string]interface{})
   157  			err := request.executeRequest(input, httpRequest, previous, false, callback, 0)
   158  			mutex.Lock()
   159  			if err != nil {
   160  				requestErr = multierr.Append(requestErr, err)
   161  			}
   162  			mutex.Unlock()
   163  		}(generatedHttpRequest)
   164  		request.options.Progress.IncrementRequests()
   165  	}
   166  	swg.Wait()
   167  	return requestErr
   168  }
   169  
   170  // executeTurboHTTP executes turbo http request for a URL
   171  func (request *Request) executeTurboHTTP(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
   172  	generator := request.newGenerator(false)
   173  
   174  	// need to extract the target from the url
   175  	URL, err := urlutil.Parse(input.MetaInput.Input)
   176  	if err != nil {
   177  		return err
   178  	}
   179  
   180  	pipeOptions := rawhttp.DefaultPipelineOptions
   181  	pipeOptions.Host = URL.Host
   182  	pipeOptions.MaxConnections = 1
   183  	if request.PipelineConcurrentConnections > 0 {
   184  		pipeOptions.MaxConnections = request.PipelineConcurrentConnections
   185  	}
   186  	if request.PipelineRequestsPerConnection > 0 {
   187  		pipeOptions.MaxPendingRequests = request.PipelineRequestsPerConnection
   188  	}
   189  	pipeClient := rawhttp.NewPipelineClient(pipeOptions)
   190  
   191  	// defaultMaxWorkers should be a sufficient value to keep queues always full
   192  	maxWorkers := defaultMaxWorkers
   193  	// in case the queue is bigger increase the workers
   194  	if pipeOptions.MaxPendingRequests > maxWorkers {
   195  		maxWorkers = pipeOptions.MaxPendingRequests
   196  	}
   197  	swg := sizedwaitgroup.New(maxWorkers)
   198  
   199  	var requestErr error
   200  	mutex := &sync.Mutex{}
   201  	for {
   202  		inputData, payloads, ok := generator.nextValue()
   203  		if !ok {
   204  			break
   205  		}
   206  		ctx := request.newContext(input)
   207  		generatedHttpRequest, err := generator.Make(ctx, input, inputData, payloads, dynamicValues)
   208  		if err != nil {
   209  			request.options.Progress.IncrementFailedRequestsBy(int64(generator.Total()))
   210  			return err
   211  		}
   212  		if input.MetaInput.Input == "" {
   213  			input.MetaInput.Input = generatedHttpRequest.URL()
   214  		}
   215  		generatedHttpRequest.pipelinedClient = pipeClient
   216  		swg.Add()
   217  		go func(httpRequest *generatedRequest) {
   218  			defer swg.Done()
   219  
   220  			err := request.executeRequest(input, httpRequest, previous, false, callback, 0)
   221  			mutex.Lock()
   222  			if err != nil {
   223  				requestErr = multierr.Append(requestErr, err)
   224  			}
   225  			mutex.Unlock()
   226  		}(generatedHttpRequest)
   227  		request.options.Progress.IncrementRequests()
   228  	}
   229  	swg.Wait()
   230  	return requestErr
   231  }
   232  
   233  // executeFuzzingRule executes fuzzing request for a URL
   234  func (request *Request) executeFuzzingRule(input *contextargs.Context, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
   235  	if _, err := urlutil.Parse(input.MetaInput.Input); err != nil {
   236  		return errors.Wrap(err, "could not parse url")
   237  	}
   238  	fuzzRequestCallback := func(gr fuzz.GeneratedRequest) bool {
   239  		hasInteractMatchers := interactsh.HasMatchers(request.CompiledOperators)
   240  		hasInteractMarkers := len(gr.InteractURLs) > 0
   241  		if request.options.HostErrorsCache != nil && request.options.HostErrorsCache.Check(input.MetaInput.Input) {
   242  			return false
   243  		}
   244  		request.options.RateLimiter.Take()
   245  		req := &generatedRequest{
   246  			request:        gr.Request,
   247  			dynamicValues:  gr.DynamicValues,
   248  			interactshURLs: gr.InteractURLs,
   249  			original:       request,
   250  		}
   251  		var gotMatches bool
   252  		requestErr := request.executeRequest(input, req, gr.DynamicValues, hasInteractMatchers, func(event *output.InternalWrappedEvent) {
   253  			if hasInteractMarkers && hasInteractMatchers && request.options.Interactsh != nil {
   254  				requestData := &interactsh.RequestData{
   255  					MakeResultFunc: request.MakeResultEvent,
   256  					Event:          event,
   257  					Operators:      request.CompiledOperators,
   258  					MatchFunc:      request.Match,
   259  					ExtractFunc:    request.Extract,
   260  				}
   261  				request.options.Interactsh.RequestEvent(gr.InteractURLs, requestData)
   262  				gotMatches = request.options.Interactsh.AlreadyMatched(requestData)
   263  			} else {
   264  				callback(event)
   265  			}
   266  			// Add the extracts to the dynamic values if any.
   267  			if event.OperatorsResult != nil {
   268  				gotMatches = event.OperatorsResult.Matched
   269  			}
   270  		}, 0)
   271  		// If a variable is unresolved, skip all further requests
   272  		if errors.Is(requestErr, errStopExecution) {
   273  			return false
   274  		}
   275  		if requestErr != nil {
   276  			if request.options.HostErrorsCache != nil {
   277  				request.options.HostErrorsCache.MarkFailed(input.MetaInput.Input, requestErr)
   278  			}
   279  		}
   280  		request.options.Progress.IncrementRequests()
   281  
   282  		// If this was a match, and we want to stop at first match, skip all further requests.
   283  		shouldStopAtFirstMatch := request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch
   284  		if shouldStopAtFirstMatch && gotMatches {
   285  			return false
   286  		}
   287  		return true
   288  	}
   289  
   290  	// Iterate through all requests for template and queue them for fuzzing
   291  	generator := request.newGenerator(true)
   292  	for {
   293  		value, payloads, result := generator.nextValue()
   294  		if !result {
   295  			break
   296  		}
   297  		generated, err := generator.Make(context.Background(), input, value, payloads, nil)
   298  		if err != nil {
   299  			continue
   300  		}
   301  		for _, rule := range request.Fuzzing {
   302  			err = rule.Execute(&fuzz.ExecuteRuleInput{
   303  				Input:       input,
   304  				Callback:    fuzzRequestCallback,
   305  				Values:      generated.dynamicValues,
   306  				BaseRequest: generated.request,
   307  			})
   308  			if err == types.ErrNoMoreRequests {
   309  				return nil
   310  			}
   311  			if err != nil {
   312  				return errors.Wrap(err, "could not execute rule")
   313  			}
   314  		}
   315  	}
   316  	return nil
   317  }
   318  
   319  // ExecuteWithResults executes the final request on a URL
   320  func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
   321  	if request.Pipeline || request.Race && request.RaceNumberRequests > 0 || request.Threads > 0 {
   322  		variablesMap := request.options.Variables.Evaluate(generators.MergeMaps(dynamicValues, previous))
   323  		dynamicValues = generators.MergeMaps(variablesMap, dynamicValues, request.options.Constants)
   324  	}
   325  	// verify if pipeline was requested
   326  	if request.Pipeline {
   327  		return request.executeTurboHTTP(input, dynamicValues, previous, callback)
   328  	}
   329  
   330  	// verify if a basic race condition was requested
   331  	if request.Race && request.RaceNumberRequests > 0 {
   332  		return request.executeRaceRequest(input, dynamicValues, callback)
   333  	}
   334  
   335  	// verify if parallel elaboration was requested
   336  	if request.Threads > 0 {
   337  		return request.executeParallelHTTP(input, dynamicValues, callback)
   338  	}
   339  
   340  	// verify if fuzz elaboration was requested
   341  	if len(request.Fuzzing) > 0 {
   342  		return request.executeFuzzingRule(input, dynamicValues, callback)
   343  	}
   344  
   345  	generator := request.newGenerator(false)
   346  
   347  	var gotDynamicValues map[string][]string
   348  	var requestErr error
   349  
   350  	for {
   351  		// returns two values, error and skip, which skips the execution for the request instance.
   352  		executeFunc := func(data string, payloads, dynamicValue map[string]interface{}) (bool, error) {
   353  			hasInteractMatchers := interactsh.HasMatchers(request.CompiledOperators)
   354  
   355  			request.options.RateLimiter.Take()
   356  
   357  			ctx := request.newContext(input)
   358  			ctxWithTimeout, cancel := context.WithTimeout(ctx, time.Duration(request.options.Options.Timeout)*time.Second)
   359  			defer cancel()
   360  			generatedHttpRequest, err := generator.Make(ctxWithTimeout, input, data, payloads, dynamicValue)
   361  			if err != nil {
   362  				if err == types.ErrNoMoreRequests {
   363  					return true, nil
   364  				}
   365  				request.options.Progress.IncrementFailedRequestsBy(int64(generator.Total()))
   366  				return true, err
   367  			}
   368  
   369  			if generatedHttpRequest.customCancelFunction != nil {
   370  				defer generatedHttpRequest.customCancelFunction()
   371  			}
   372  
   373  			hasInteractMarkers := interactsh.HasMarkers(data) || len(generatedHttpRequest.interactshURLs) > 0
   374  			if input.MetaInput.Input == "" {
   375  				input.MetaInput.Input = generatedHttpRequest.URL()
   376  			}
   377  			// Check if hosts keep erroring
   378  			if request.options.HostErrorsCache != nil && request.options.HostErrorsCache.Check(input.MetaInput.ID()) {
   379  				return true, nil
   380  			}
   381  			var gotMatches bool
   382  			err = request.executeRequest(input, generatedHttpRequest, previous, hasInteractMatchers, func(event *output.InternalWrappedEvent) {
   383  				// a special case where operators has interactsh matchers and multiple request are made
   384  				// ex: status_code_2 , interactsh_protocol (from 1st request) etc
   385  				needsRequestEvent := interactsh.HasMatchers(request.CompiledOperators) && request.NeedsRequestCondition()
   386  				if (hasInteractMarkers || needsRequestEvent) && request.options.Interactsh != nil {
   387  					requestData := &interactsh.RequestData{
   388  						MakeResultFunc: request.MakeResultEvent,
   389  						Event:          event,
   390  						Operators:      request.CompiledOperators,
   391  						MatchFunc:      request.Match,
   392  						ExtractFunc:    request.Extract,
   393  					}
   394  					allOASTUrls := getInteractshURLsFromEvent(event.InternalEvent)
   395  					allOASTUrls = append(allOASTUrls, generatedHttpRequest.interactshURLs...)
   396  					request.options.Interactsh.RequestEvent(sliceutil.Dedupe(allOASTUrls), requestData)
   397  					gotMatches = request.options.Interactsh.AlreadyMatched(requestData)
   398  				}
   399  				// Add the extracts to the dynamic values if any.
   400  				if event.OperatorsResult != nil {
   401  					gotMatches = event.OperatorsResult.Matched
   402  					gotDynamicValues = generators.MergeMapsMany(event.OperatorsResult.DynamicValues, dynamicValues, gotDynamicValues)
   403  				}
   404  				// Note: This is a race condition prone zone i.e when request has interactsh_matchers
   405  				// Interactsh.RequestEvent tries to access/update output.InternalWrappedEvent depending on logic
   406  				// to avoid conflicts with `callback` mutex is used here and in Interactsh.RequestEvent
   407  				// Note: this only happens if requests > 1 and interactsh matcher is used
   408  				// TODO: interactsh logic in nuclei needs to be refactored to avoid such situations
   409  				callback(event)
   410  			}, generator.currentIndex)
   411  
   412  			// If a variable is unresolved, skip all further requests
   413  			if errors.Is(err, errStopExecution) {
   414  				return true, nil
   415  			}
   416  			if err != nil {
   417  				if request.options.HostErrorsCache != nil {
   418  					request.options.HostErrorsCache.MarkFailed(input.MetaInput.ID(), err)
   419  				}
   420  				requestErr = err
   421  			}
   422  			request.options.Progress.IncrementRequests()
   423  
   424  			// If this was a match, and we want to stop at first match, skip all further requests.
   425  			shouldStopAtFirstMatch := generatedHttpRequest.original.options.Options.StopAtFirstMatch || generatedHttpRequest.original.options.StopAtFirstMatch || request.StopAtFirstMatch
   426  			if shouldStopAtFirstMatch && gotMatches {
   427  				return true, nil
   428  			}
   429  			return false, nil
   430  		}
   431  
   432  		inputData, payloads, ok := generator.nextValue()
   433  		if !ok {
   434  			break
   435  		}
   436  		var gotErr error
   437  		var skip bool
   438  		if len(gotDynamicValues) > 0 {
   439  			operators.MakeDynamicValuesCallback(gotDynamicValues, request.IterateAll, func(data map[string]interface{}) bool {
   440  				if skip, gotErr = executeFunc(inputData, payloads, data); skip || gotErr != nil {
   441  					return true
   442  				}
   443  				return false
   444  			})
   445  		} else {
   446  			skip, gotErr = executeFunc(inputData, payloads, dynamicValues)
   447  		}
   448  		if gotErr != nil && requestErr == nil {
   449  			requestErr = gotErr
   450  		}
   451  		if skip || gotErr != nil {
   452  			break
   453  		}
   454  	}
   455  	return requestErr
   456  }
   457  
   458  const drainReqSize = int64(8 * 1024)
   459  
   460  var errStopExecution = errors.New("stop execution due to unresolved variables")
   461  
   462  // executeRequest executes the actual generated request and returns error if occurred
   463  func (request *Request) executeRequest(input *contextargs.Context, generatedRequest *generatedRequest, previousEvent output.InternalEvent, hasInteractMatchers bool, callback protocols.OutputEventCallback, requestCount int) error {
   464  	request.setCustomHeaders(generatedRequest)
   465  
   466  	// Try to evaluate any payloads before replacement
   467  	finalMap := generators.MergeMaps(generatedRequest.dynamicValues, generatedRequest.meta)
   468  
   469  	// add known variables from metainput
   470  	if _, ok := finalMap["ip"]; !ok && input.MetaInput.CustomIP != "" {
   471  		finalMap["ip"] = input.MetaInput.CustomIP
   472  	}
   473  
   474  	for payloadName, payloadValue := range generatedRequest.dynamicValues {
   475  		if data, err := expressions.Evaluate(types.ToString(payloadValue), finalMap); err == nil {
   476  			generatedRequest.dynamicValues[payloadName] = data
   477  		}
   478  	}
   479  	for payloadName, payloadValue := range generatedRequest.meta {
   480  		if data, err := expressions.Evaluate(types.ToString(payloadValue), finalMap); err == nil {
   481  			generatedRequest.meta[payloadName] = data
   482  		}
   483  	}
   484  
   485  	var (
   486  		resp          *http.Response
   487  		fromCache     bool
   488  		dumpedRequest []byte
   489  		err           error
   490  	)
   491  
   492  	// Dump request for variables checks
   493  	// For race conditions we can't dump the request body at this point as it's already waiting the open-gate event, already handled with a similar code within the race function
   494  	if !generatedRequest.original.Race {
   495  
   496  		// change encoding type to content-length unless transfer-encoding header is manually set
   497  		if generatedRequest.request != nil && !stringsutil.EqualFoldAny(generatedRequest.request.Method, http.MethodGet, http.MethodHead) && generatedRequest.request.Body != nil && generatedRequest.request.Header.Get("Transfer-Encoding") != "chunked" {
   498  			var newReqBody *reader.ReusableReadCloser
   499  			newReqBody, ok := generatedRequest.request.Body.(*reader.ReusableReadCloser)
   500  			if !ok {
   501  				newReqBody, err = reader.NewReusableReadCloser(generatedRequest.request.Body)
   502  			}
   503  			if err == nil {
   504  				// update the request body with the reusable reader
   505  				generatedRequest.request.Body = newReqBody
   506  				// get content length
   507  				length, _ := io.Copy(io.Discard, newReqBody)
   508  				generatedRequest.request.ContentLength = length
   509  			} else {
   510  				// log error and continue
   511  				gologger.Verbose().Msgf("[%v] Could not read request body while forcing transfer encoding: %s\n", request.options.TemplateID, err)
   512  				err = nil
   513  			}
   514  		}
   515  
   516  		// do the same for unsafe requests
   517  		if generatedRequest.rawRequest != nil && !stringsutil.EqualFoldAny(generatedRequest.rawRequest.Method, http.MethodGet, http.MethodHead) && generatedRequest.rawRequest.Data != "" && generatedRequest.rawRequest.Headers["Transfer-Encoding"] != "chunked" {
   518  			generatedRequest.rawRequest.Headers["Content-Length"] = strconv.Itoa(len(generatedRequest.rawRequest.Data))
   519  		}
   520  
   521  		var dumpError error
   522  		// TODO: dump is currently not working with post-processors - somehow it alters the signature
   523  		dumpedRequest, dumpError = dump(generatedRequest, input.MetaInput.Input)
   524  		if dumpError != nil {
   525  			return dumpError
   526  		}
   527  		dumpedRequestString := string(dumpedRequest)
   528  
   529  		if ignoreList := GetVariablesNamesSkipList(generatedRequest.original.Signature.Value); ignoreList != nil {
   530  			if varErr := expressions.ContainsVariablesWithIgnoreList(ignoreList, dumpedRequestString); varErr != nil && !request.SkipVariablesCheck {
   531  				gologger.Warning().Msgf("[%s] Could not make http request for %s: %v\n", request.options.TemplateID, input.MetaInput.Input, varErr)
   532  				return errStopExecution
   533  			}
   534  		} else { // Check if are there any unresolved variables. If yes, skip unless overridden by user.
   535  			if varErr := expressions.ContainsUnresolvedVariables(dumpedRequestString); varErr != nil && !request.SkipVariablesCheck {
   536  				gologger.Warning().Msgf("[%s] Could not make http request for %s: %v\n", request.options.TemplateID, input.MetaInput.Input, varErr)
   537  				return errStopExecution
   538  			}
   539  		}
   540  	}
   541  	var formedURL string
   542  	var hostname string
   543  	timeStart := time.Now()
   544  	if generatedRequest.original.Pipeline {
   545  		// if request is a pipeline request, use the pipelined client
   546  		if generatedRequest.rawRequest != nil {
   547  			formedURL = generatedRequest.rawRequest.FullURL
   548  			if parsed, parseErr := urlutil.ParseURL(formedURL, true); parseErr == nil {
   549  				hostname = parsed.Host
   550  			}
   551  			resp, err = generatedRequest.pipelinedClient.DoRaw(generatedRequest.rawRequest.Method, input.MetaInput.Input, generatedRequest.rawRequest.Path, generators.ExpandMapValues(generatedRequest.rawRequest.Headers), io.NopCloser(strings.NewReader(generatedRequest.rawRequest.Data)))
   552  		} else if generatedRequest.request != nil {
   553  			resp, err = generatedRequest.pipelinedClient.Dor(generatedRequest.request)
   554  		}
   555  	} else if generatedRequest.original.Unsafe && generatedRequest.rawRequest != nil {
   556  		// if request is a unsafe request, use the rawhttp client
   557  		formedURL = generatedRequest.rawRequest.FullURL
   558  		// use request url as matched url if empty
   559  		if formedURL == "" {
   560  			urlx, err := urlutil.Parse(input.MetaInput.Input)
   561  			if err != nil {
   562  				formedURL = fmt.Sprintf("%s%s", input.MetaInput.Input, generatedRequest.rawRequest.Path)
   563  			} else {
   564  				_ = urlx.MergePath(generatedRequest.rawRequest.Path, true)
   565  				formedURL = urlx.String()
   566  			}
   567  		}
   568  		if parsed, parseErr := urlutil.ParseURL(formedURL, true); parseErr == nil {
   569  			hostname = parsed.Host
   570  		}
   571  		options := *generatedRequest.original.rawhttpClient.Options
   572  		options.FollowRedirects = request.Redirects
   573  		options.CustomRawBytes = generatedRequest.rawRequest.UnsafeRawBytes
   574  		options.ForceReadAllBody = request.ForceReadAllBody
   575  		options.SNI = request.options.Options.SNI
   576  		inputUrl := input.MetaInput.Input
   577  		if url, err := urlutil.ParseURL(inputUrl, false); err == nil {
   578  			url.Path = ""
   579  			url.Params = urlutil.NewOrderedParams() // donot include query params
   580  			// inputUrl should only contain scheme://host:port
   581  			inputUrl = url.String()
   582  		}
   583  		formedURL = fmt.Sprintf("%s%s", inputUrl, generatedRequest.rawRequest.Path)
   584  		resp, err = generatedRequest.original.rawhttpClient.DoRawWithOptions(generatedRequest.rawRequest.Method, inputUrl, generatedRequest.rawRequest.Path, generators.ExpandMapValues(generatedRequest.rawRequest.Headers), io.NopCloser(strings.NewReader(generatedRequest.rawRequest.Data)), &options)
   585  	} else {
   586  		//** For Normal requests **//
   587  		hostname = generatedRequest.request.URL.Host
   588  		formedURL = generatedRequest.request.URL.String()
   589  		// if nuclei-project is available check if the request was already sent previously
   590  		if request.options.ProjectFile != nil {
   591  			// if unavailable fail silently
   592  			fromCache = true
   593  			resp, err = request.options.ProjectFile.Get(dumpedRequest)
   594  			if err != nil {
   595  				fromCache = false
   596  			}
   597  		}
   598  		if resp == nil {
   599  			if errSignature := request.handleSignature(generatedRequest); errSignature != nil {
   600  				return errSignature
   601  			}
   602  
   603  			httpclient := request.httpClient
   604  			if input.CookieJar != nil {
   605  				connConfiguration := request.connConfiguration
   606  				connConfiguration.Connection.SetCookieJar(input.CookieJar)
   607  				client, err := httpclientpool.Get(request.options.Options, connConfiguration)
   608  				if err != nil {
   609  					return errors.Wrap(err, "could not get http client")
   610  				}
   611  				httpclient = client
   612  			}
   613  			resp, err = httpclient.Do(generatedRequest.request)
   614  		}
   615  	}
   616  	// use request url as matched url if empty
   617  	if formedURL == "" {
   618  		formedURL = input.MetaInput.Input
   619  	}
   620  
   621  	// converts whitespace and other chars that cannot be printed to url encoded values
   622  	formedURL = urlutil.URLEncodeWithEscapes(formedURL)
   623  
   624  	// Dump the requests containing all headers
   625  	if !generatedRequest.original.Race {
   626  		var dumpError error
   627  		dumpedRequest, dumpError = dump(generatedRequest, input.MetaInput.Input)
   628  		if dumpError != nil {
   629  			return dumpError
   630  		}
   631  		dumpedRequestString := string(dumpedRequest)
   632  		if request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse {
   633  			msg := fmt.Sprintf("[%s] Dumped HTTP request for %s\n\n", request.options.TemplateID, formedURL)
   634  
   635  			if request.options.Options.Debug || request.options.Options.DebugRequests {
   636  				gologger.Info().Msg(msg)
   637  				gologger.Print().Msgf("%s", dumpedRequestString)
   638  			}
   639  			if request.options.Options.StoreResponse {
   640  				request.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), fmt.Sprintf("%s\n%s", msg, dumpedRequestString))
   641  			}
   642  		}
   643  	}
   644  	if err != nil {
   645  		// rawhttp doesn't support draining response bodies.
   646  		if resp != nil && resp.Body != nil && generatedRequest.rawRequest == nil && !generatedRequest.original.Pipeline {
   647  			_, _ = io.CopyN(io.Discard, resp.Body, drainReqSize)
   648  			resp.Body.Close()
   649  		}
   650  		request.options.Output.Request(request.options.TemplatePath, formedURL, request.Type().String(), err)
   651  		request.options.Progress.IncrementErrorsBy(1)
   652  
   653  		// If we have interactsh markers and request times out, still send
   654  		// a callback event so in case we receive an interaction, correlation is possible.
   655  		if hasInteractMatchers {
   656  			outputEvent := request.responseToDSLMap(&http.Response{}, input.MetaInput.Input, formedURL, tostring.UnsafeToString(dumpedRequest), "", "", "", 0, generatedRequest.meta)
   657  			if i := strings.LastIndex(hostname, ":"); i != -1 {
   658  				hostname = hostname[:i]
   659  			}
   660  
   661  			if input.MetaInput.CustomIP != "" {
   662  				outputEvent["ip"] = input.MetaInput.CustomIP
   663  			} else {
   664  				outputEvent["ip"] = httpclientpool.Dialer.GetDialedIP(hostname)
   665  			}
   666  
   667  			event := &output.InternalWrappedEvent{InternalEvent: outputEvent}
   668  			if request.CompiledOperators != nil {
   669  				event.InternalEvent = outputEvent
   670  			}
   671  			callback(event)
   672  		}
   673  		return err
   674  	}
   675  	defer func() {
   676  		if resp.StatusCode != http.StatusSwitchingProtocols {
   677  			_, _ = io.CopyN(io.Discard, resp.Body, drainReqSize)
   678  		}
   679  		resp.Body.Close()
   680  	}()
   681  
   682  	var curlCommand string
   683  	if !request.Unsafe && resp != nil && generatedRequest.request != nil && resp.Request != nil && !request.Race {
   684  		bodyBytes, _ := generatedRequest.request.BodyBytes()
   685  		resp.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes))
   686  		command, err := http2curl.GetCurlCommand(generatedRequest.request.Request)
   687  		if err == nil && command != nil {
   688  			curlCommand = command.String()
   689  		}
   690  	}
   691  
   692  	gologger.Verbose().Msgf("[%s] Sent HTTP request to %s", request.options.TemplateID, formedURL)
   693  	request.options.Output.Request(request.options.TemplatePath, formedURL, request.Type().String(), err)
   694  
   695  	duration := time.Since(timeStart)
   696  
   697  	dumpedResponseHeaders, err := httputil.DumpResponse(resp, false)
   698  	if err != nil {
   699  		return errors.Wrap(err, "could not dump http response")
   700  	}
   701  
   702  	var dumpedResponse []redirectedResponse
   703  	var gotData []byte
   704  	// If the status code is HTTP 101, we should not proceed with reading body.
   705  	if resp.StatusCode != http.StatusSwitchingProtocols {
   706  		var bodyReader io.Reader
   707  		if request.MaxSize != 0 {
   708  			bodyReader = io.LimitReader(resp.Body, int64(request.MaxSize))
   709  		} else if request.options.Options.ResponseReadSize != 0 {
   710  			bodyReader = io.LimitReader(resp.Body, int64(request.options.Options.ResponseReadSize))
   711  		} else {
   712  			bodyReader = resp.Body
   713  		}
   714  		data, err := io.ReadAll(bodyReader)
   715  		if err != nil {
   716  			// Ignore body read due to server misconfiguration errors
   717  			if stringsutil.ContainsAny(err.Error(), "gzip: invalid header") {
   718  				gologger.Warning().Msgf("[%s] Server sent an invalid gzip header and it was not possible to read the uncompressed body for %s: %s", request.options.TemplateID, formedURL, err.Error())
   719  			} else if !stringsutil.ContainsAny(err.Error(), "unexpected EOF", "user canceled") { // ignore EOF and random error
   720  				return errors.Wrap(err, "could not read http body")
   721  			}
   722  		}
   723  		gotData = data
   724  		resp.Body.Close()
   725  
   726  		dumpedResponse, err = dumpResponseWithRedirectChain(resp, data)
   727  		if err != nil {
   728  			return errors.Wrap(err, "could not read http response with redirect chain")
   729  		}
   730  	} else {
   731  		dumpedResponse = []redirectedResponse{{resp: resp, fullResponse: dumpedResponseHeaders, headers: dumpedResponseHeaders}}
   732  	}
   733  
   734  	// if nuclei-project is enabled store the response if not previously done
   735  	if request.options.ProjectFile != nil && !fromCache {
   736  		if err := request.options.ProjectFile.Set(dumpedRequest, resp, gotData); err != nil {
   737  			return errors.Wrap(err, "could not store in project file")
   738  		}
   739  	}
   740  
   741  	for _, response := range dumpedResponse {
   742  		if response.resp == nil {
   743  			continue // Skip nil responses
   744  		}
   745  		matchedURL := input.MetaInput.Input
   746  		if generatedRequest.rawRequest != nil {
   747  			if generatedRequest.rawRequest.FullURL != "" {
   748  				matchedURL = generatedRequest.rawRequest.FullURL
   749  			} else {
   750  				matchedURL = formedURL
   751  			}
   752  		}
   753  		if generatedRequest.request != nil {
   754  			matchedURL = generatedRequest.request.URL.String()
   755  		}
   756  		// Give precedence to the final URL from response
   757  		if response.resp.Request != nil {
   758  			if responseURL := response.resp.Request.URL.String(); responseURL != "" {
   759  				matchedURL = responseURL
   760  			}
   761  		}
   762  		finalEvent := make(output.InternalEvent)
   763  
   764  		outputEvent := request.responseToDSLMap(response.resp, input.MetaInput.Input, matchedURL, tostring.UnsafeToString(dumpedRequest), tostring.UnsafeToString(response.fullResponse), tostring.UnsafeToString(response.body), tostring.UnsafeToString(response.headers), duration, generatedRequest.meta)
   765  		if i := strings.LastIndex(hostname, ":"); i != -1 {
   766  			hostname = hostname[:i]
   767  		}
   768  		outputEvent["curl-command"] = curlCommand
   769  		if input.MetaInput.CustomIP != "" {
   770  			outputEvent["ip"] = input.MetaInput.CustomIP
   771  		} else {
   772  			outputEvent["ip"] = httpclientpool.Dialer.GetDialedIP(hostname)
   773  		}
   774  		if request.options.Interactsh != nil {
   775  			request.options.Interactsh.MakePlaceholders(generatedRequest.interactshURLs, outputEvent)
   776  		}
   777  		for k, v := range previousEvent {
   778  			finalEvent[k] = v
   779  		}
   780  		for k, v := range outputEvent {
   781  			finalEvent[k] = v
   782  		}
   783  
   784  		// Add to history the current request number metadata if asked by the user.
   785  		if request.NeedsRequestCondition() {
   786  			for k, v := range outputEvent {
   787  				key := fmt.Sprintf("%s_%d", k, requestCount)
   788  				previousEvent[key] = v
   789  				finalEvent[key] = v
   790  			}
   791  		}
   792  		// prune signature internal values if any
   793  		request.pruneSignatureInternalValues(generatedRequest.meta)
   794  
   795  		event := eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(generatedRequest.dynamicValues, finalEvent), request.options.Options.Debug || request.options.Options.DebugResponse, func(internalWrappedEvent *output.InternalWrappedEvent) {
   796  			internalWrappedEvent.OperatorsResult.PayloadValues = generatedRequest.meta
   797  		})
   798  		if hasInteractMatchers {
   799  			event.UsesInteractsh = true
   800  		}
   801  
   802  		responseContentType := resp.Header.Get("Content-Type")
   803  		isResponseTruncated := request.MaxSize > 0 && len(gotData) >= request.MaxSize
   804  		dumpResponse(event, request, response.fullResponse, formedURL, responseContentType, isResponseTruncated, input.MetaInput.Input)
   805  
   806  		callback(event)
   807  
   808  		// Skip further responses if we have stop-at-first-match and a match
   809  		if (request.options.Options.StopAtFirstMatch || request.options.StopAtFirstMatch || request.StopAtFirstMatch) && event.HasResults() {
   810  			return nil
   811  		}
   812  	}
   813  	return nil
   814  }
   815  
   816  // handleSignature of the http request
   817  func (request *Request) handleSignature(generatedRequest *generatedRequest) error {
   818  	switch request.Signature.Value {
   819  	case AWSSignature:
   820  		var awsSigner signer.Signer
   821  		allvars := generators.MergeMaps(request.options.Options.Vars.AsMap(), generatedRequest.dynamicValues)
   822  		awsopts := signer.AWSOptions{
   823  			AwsID:          types.ToString(allvars["aws-id"]),
   824  			AwsSecretToken: types.ToString(allvars["aws-secret"]),
   825  		}
   826  		awsSigner, err := signerpool.Get(request.options.Options, &signerpool.Configuration{SignerArgs: &awsopts})
   827  		if err != nil {
   828  			return err
   829  		}
   830  		ctx := signer.GetCtxWithArgs(allvars, signer.AwsDefaultVars)
   831  		err = awsSigner.SignHTTP(ctx, generatedRequest.request.Request)
   832  		if err != nil {
   833  			return err
   834  		}
   835  	}
   836  
   837  	return nil
   838  }
   839  
   840  // setCustomHeaders sets the custom headers for generated request
   841  func (request *Request) setCustomHeaders(req *generatedRequest) {
   842  	for k, v := range request.customHeaders {
   843  		if req.rawRequest != nil {
   844  			req.rawRequest.Headers[k] = v
   845  		} else {
   846  			kk, vv := strings.TrimSpace(k), strings.TrimSpace(v)
   847  			req.request.Header.Set(kk, vv)
   848  			if kk == "Host" {
   849  				req.request.Host = vv
   850  			}
   851  		}
   852  	}
   853  }
   854  
   855  const CRLF = "\r\n"
   856  
   857  func dumpResponse(event *output.InternalWrappedEvent, request *Request, redirectedResponse []byte, formedURL string, responseContentType string, isResponseTruncated bool, reqURL string) {
   858  	cliOptions := request.options.Options
   859  	if cliOptions.Debug || cliOptions.DebugResponse || cliOptions.StoreResponse {
   860  		response := string(redirectedResponse)
   861  
   862  		var highlightedResult string
   863  		if responseContentType == "application/octet-stream" || ((responseContentType == "" || responseContentType == "application/x-www-form-urlencoded") && responsehighlighter.HasBinaryContent(response)) {
   864  			highlightedResult = createResponseHexDump(event, response, cliOptions.NoColor)
   865  		} else {
   866  			highlightedResult = responsehighlighter.Highlight(event.OperatorsResult, response, cliOptions.NoColor, false)
   867  		}
   868  
   869  		msg := "[%s] Dumped HTTP response %s\n\n%s"
   870  		if isResponseTruncated {
   871  			msg = "[%s] Dumped HTTP response (Truncated) %s\n\n%s"
   872  		}
   873  		fMsg := fmt.Sprintf(msg, request.options.TemplateID, formedURL, highlightedResult)
   874  		if cliOptions.Debug || cliOptions.DebugResponse {
   875  			gologger.Debug().Msg(fMsg)
   876  		}
   877  		if cliOptions.StoreResponse {
   878  			request.options.Output.WriteStoreDebugData(reqURL, request.options.TemplateID, request.Type().String(), fMsg)
   879  		}
   880  	}
   881  }
   882  
   883  func createResponseHexDump(event *output.InternalWrappedEvent, response string, noColor bool) string {
   884  	CRLFs := CRLF + CRLF
   885  	headerEndIndex := strings.Index(response, CRLFs) + len(CRLFs)
   886  	if headerEndIndex > 0 {
   887  		headers := response[0:headerEndIndex]
   888  		responseBodyHexDump := hex.Dump([]byte(response[headerEndIndex:]))
   889  
   890  		highlightedHeaders := responsehighlighter.Highlight(event.OperatorsResult, headers, noColor, false)
   891  		highlightedResponse := responsehighlighter.Highlight(event.OperatorsResult, responseBodyHexDump, noColor, true)
   892  		return fmt.Sprintf("%s\n%s", highlightedHeaders, highlightedResponse)
   893  	} else {
   894  		return responsehighlighter.Highlight(event.OperatorsResult, hex.Dump([]byte(response)), noColor, true)
   895  	}
   896  }
   897  
   898  func (request *Request) pruneSignatureInternalValues(maps ...map[string]interface{}) {
   899  	var signatureFieldsToSkip map[string]interface{}
   900  	switch request.Signature.Value {
   901  	case AWSSignature:
   902  		signatureFieldsToSkip = signer.AwsInternalOnlyVars
   903  	default:
   904  		return
   905  	}
   906  
   907  	for _, m := range maps {
   908  		for fieldName := range signatureFieldsToSkip {
   909  			delete(m, fieldName)
   910  		}
   911  	}
   912  }
   913  
   914  func (request *Request) newContext(input *contextargs.Context) context.Context {
   915  	if input.MetaInput.CustomIP != "" {
   916  		return context.WithValue(context.Background(), fastdialer.IP, input.MetaInput.CustomIP)
   917  	}
   918  	return context.Background()
   919  }