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 }