2020-12-26 14:55:15 +05:30
package http
2020-12-28 20:02:26 +05:30
import (
2021-02-08 01:55:53 +05:30
"bytes"
2022-07-21 21:29:34 +05:30
"context"
2021-10-30 13:17:47 +03:00
"encoding/hex"
2021-03-08 19:01:40 +05:30
"fmt"
2020-12-28 20:02:26 +05:30
"io"
"net/http"
2023-09-16 14:20:35 +05:30
"strconv"
2020-12-28 20:02:26 +05:30
"strings"
"sync"
2024-02-13 01:20:19 +05:30
"sync/atomic"
2020-12-28 20:02:26 +05:30
"time"
"github.com/pkg/errors"
2021-09-07 17:31:46 +03:00
"go.uber.org/multierr"
2021-10-15 13:55:50 +05:30
"moul.io/http2curl"
2021-09-07 17:31:46 +03:00
2023-09-04 10:24:34 +02:00
"github.com/projectdiscovery/fastdialer/fastdialer"
2020-12-29 01:30:07 +05:30
"github.com/projectdiscovery/gologger"
2024-11-19 11:51:32 +05:30
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/analyzers"
2025-02-13 18:46:28 +05:30
fuzzStats "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats"
2023-10-17 17:44:13 +05:30
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
2024-03-05 01:08:01 +01:00
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
2023-10-17 17:44:13 +05:30
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool"
2024-01-31 01:59:49 +05:30
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httputils"
2023-10-17 17:44:13 +05:30
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/signer"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/signerpool"
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
2024-05-25 00:29:04 +05:30
"github.com/projectdiscovery/nuclei/v3/pkg/types/nucleierr"
2020-12-28 20:02:26 +05:30
"github.com/projectdiscovery/rawhttp"
2024-01-31 01:59:49 +05:30
convUtil "github.com/projectdiscovery/utils/conversion"
2024-05-25 00:29:04 +05:30
"github.com/projectdiscovery/utils/errkit"
2024-02-29 00:51:17 +03:00
httpUtils "github.com/projectdiscovery/utils/http"
2023-09-16 14:20:35 +05:30
"github.com/projectdiscovery/utils/reader"
2023-08-18 02:37:35 +05:30
sliceutil "github.com/projectdiscovery/utils/slice"
2022-11-06 21:24:23 +01:00
stringsutil "github.com/projectdiscovery/utils/strings"
2024-05-15 15:34:59 +02:00
unitutils "github.com/projectdiscovery/utils/unit"
2023-01-24 22:04:52 +05:30
urlutil "github.com/projectdiscovery/utils/url"
2020-12-28 20:02:26 +05:30
)
2024-01-18 05:53:42 +05:30
const (
defaultMaxWorkers = 150
2024-04-24 19:34:13 +05:30
// max unique errors to store & combine
// when executing requests in parallel
maxErrorsWhenParallel = 3
2024-01-18 05:53:42 +05:30
)
2020-12-28 20:02:26 +05:30
2024-02-29 00:51:17 +03:00
var (
2024-05-15 15:34:59 +02:00
MaxBodyRead = 10 * unitutils . Mega
2024-04-01 12:25:17 +05:30
// ErrMissingVars is error occured when variables are missing
2024-05-25 00:29:04 +05:30
ErrMissingVars = errkit . New ( "stop execution due to unresolved variables" ) . SetKind ( nucleierr . ErrTemplateLogic ) . Build ( )
// ErrHttpEngineRequestDeadline is error occured when request deadline set by http request engine is exceeded
ErrHttpEngineRequestDeadline = errkit . New ( "http request engine deadline exceeded" ) . SetKind ( errkit . ErrKindDeadline ) . Build ( )
2024-02-29 00:51:17 +03:00
)
2021-11-03 19:53:45 +05:30
// Type returns the type of the protocol request
func ( request * Request ) Type ( ) templateTypes . ProtocolType {
return templateTypes . HTTPProtocol
}
2020-12-28 20:02:26 +05:30
// executeRaceRequest executes race condition request for a URL
2022-10-03 12:12:20 +02:00
func ( request * Request ) executeRaceRequest ( input * contextargs . Context , previous output . InternalEvent , callback protocols . OutputEventCallback ) error {
2022-11-09 14:18:56 +01:00
reqURL := input . MetaInput . Input
2021-10-01 14:30:04 +03:00
var generatedRequests [ ] * generatedRequest
2021-03-01 05:18:31 +01:00
// Requests within race condition should be dumped once and the output prefilled to allow DSL language to work
// This will introduce a delay and will populate in hacky way the field "request" of outputEvent
2022-11-01 20:28:50 +05:30
generator := request . newGenerator ( false )
2021-11-24 21:08:08 +05:30
inputData , payloads , ok := generator . nextValue ( )
if ! ok {
return nil
}
2022-11-09 14:18:56 +01:00
ctx := request . newContext ( input )
2022-12-09 19:47:03 +01:00
requestForDump , err := generator . Make ( ctx , input , inputData , payloads , nil )
2021-03-01 05:18:31 +01:00
if err != nil {
return err
}
2021-10-01 14:30:04 +03:00
request . setCustomHeaders ( requestForDump )
2021-03-01 05:18:31 +01:00
dumpedRequest , err := dump ( requestForDump , reqURL )
if err != nil {
return err
}
2022-04-01 14:29:02 -05:00
if request . options . Options . Debug || request . options . Options . DebugRequests || request . options . Options . StoreResponse {
msg := fmt . Sprintf ( "[%s] Dumped HTTP request for %s\n\n" , request . options . TemplateID , reqURL )
if request . options . Options . Debug || request . options . Options . DebugRequests {
gologger . Info ( ) . Msg ( msg )
gologger . Print ( ) . Msgf ( "%s" , string ( dumpedRequest ) )
}
if request . options . Options . StoreResponse {
request . options . Output . WriteStoreDebugData ( reqURL , request . options . TemplateID , request . Type ( ) . String ( ) , fmt . Sprintf ( "%s\n%s" , msg , dumpedRequest ) )
}
2021-03-01 05:18:31 +01:00
}
previous [ "request" ] = string ( dumpedRequest )
2020-12-26 14:55:15 +05:30
2021-03-01 05:18:31 +01:00
// Pre-Generate requests
2021-10-01 14:30:04 +03:00
for i := 0 ; i < request . RaceNumberRequests ; i ++ {
2022-11-01 20:28:50 +05:30
generator := request . newGenerator ( false )
2021-11-24 21:08:08 +05:30
inputData , payloads , ok := generator . nextValue ( )
if ! ok {
break
}
2022-11-09 14:18:56 +01:00
ctx := request . newContext ( input )
2022-12-09 19:47:03 +01:00
generatedRequest , err := generator . Make ( ctx , input , inputData , payloads , nil )
2021-03-01 05:18:31 +01:00
if err != nil {
return err
}
2021-10-01 14:30:04 +03:00
generatedRequests = append ( generatedRequests , generatedRequest )
2021-03-01 05:18:31 +01:00
}
2020-12-28 20:02:26 +05:30
2024-02-13 01:20:19 +05:30
shouldStop := ( request . options . Options . StopAtFirstMatch || request . StopAtFirstMatch || request . options . StopAtFirstMatch )
2024-05-20 00:48:40 +05:30
childCtx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
spmHandler := httputils . NewNonBlockingSPMHandler [ error ] ( childCtx , maxErrorsWhenParallel , shouldStop )
defer spmHandler . Cancel ( )
2024-02-13 01:20:19 +05:30
gotMatches := & atomic . Bool { }
// wrappedCallback is a callback that wraps the original callback
// to implement stop at first match logic
wrappedCallback := func ( event * output . InternalWrappedEvent ) {
if ! event . HasOperatorResult ( ) {
callback ( event ) // not required but we can allow it
return
}
// this will execute match condition such that if stop at first match is enabled
// this will be only executed once
spmHandler . MatchCallback ( func ( ) {
gotMatches . Store ( true )
callback ( event )
} )
if shouldStop {
// stop all running requests and exit
spmHandler . Trigger ( )
}
}
2024-04-24 19:34:13 +05:30
// look for unresponsive hosts and cancel inflight requests as well
spmHandler . SetOnResultCallback ( func ( err error ) {
// marks thsi host as unresponsive if applicable
2025-01-31 17:16:57 +07:00
request . markHostError ( input , err )
2024-05-25 00:29:04 +05:30
if request . isUnresponsiveAddress ( input ) {
2024-04-24 19:34:13 +05:30
// stop all inflight requests
spmHandler . Cancel ( )
}
} )
2021-10-01 14:30:04 +03:00
for i := 0 ; i < request . RaceNumberRequests ; i ++ {
2024-05-25 00:29:04 +05:30
if spmHandler . FoundFirstMatch ( ) || request . isUnresponsiveAddress ( input ) {
2024-04-24 19:34:13 +05:30
// stop sending more requests condition is met
break
}
2024-02-13 01:20:19 +05:30
spmHandler . Acquire ( )
// execute http request
2020-12-28 20:02:26 +05:30
go func ( httpRequest * generatedRequest ) {
2024-02-13 01:20:19 +05:30
defer spmHandler . Release ( )
2024-05-25 00:29:04 +05:30
if spmHandler . FoundFirstMatch ( ) || request . isUnresponsiveAddress ( input ) {
2024-02-13 01:20:19 +05:30
// stop sending more requests condition is met
return
}
select {
case <- spmHandler . Done ( ) :
return
case spmHandler . ResultChan <- request . executeRequest ( input , httpRequest , previous , false , wrappedCallback , 0 ) :
return
2020-12-26 14:55:15 +05:30
}
2021-10-01 14:30:04 +03:00
} ( generatedRequests [ i ] )
request . options . Progress . IncrementRequests ( )
2020-12-26 14:55:15 +05:30
}
2024-02-13 01:20:19 +05:30
spmHandler . Wait ( )
2021-03-01 05:18:31 +01:00
2024-02-13 01:20:19 +05:30
if spmHandler . FoundFirstMatch ( ) {
// ignore any context cancellation and in-transit execution errors
return nil
}
return multierr . Combine ( spmHandler . CombinedResults ( ) ... )
2020-12-26 14:55:15 +05:30
}
2021-02-04 22:00:09 +05:30
// executeRaceRequest executes parallel requests for a template
2022-10-03 12:12:20 +02:00
func ( request * Request ) executeParallelHTTP ( input * contextargs . Context , dynamicValues output . InternalEvent , callback protocols . OutputEventCallback ) error {
2020-12-26 14:55:15 +05:30
// Workers that keeps enqueuing new requests
2021-10-01 14:30:04 +03:00
maxWorkers := request . Threads
2020-12-28 20:02:26 +05:30
2024-04-03 23:06:08 +02:00
// if request threads matches global payload concurrency we follow it
shouldFollowGlobal := maxWorkers == request . options . Options . PayloadConcurrency
2024-03-07 16:16:07 +01:00
if protocolstate . IsLowOnMemory ( ) {
2024-03-11 19:48:56 +01:00
maxWorkers = protocolstate . GuardThreadsOrDefault ( request . Threads )
2024-03-05 01:08:01 +01:00
}
2024-02-13 01:20:19 +05:30
// Stop-at-first-match logic while executing requests
// parallely using threads
shouldStop := ( request . options . Options . StopAtFirstMatch || request . StopAtFirstMatch || request . options . StopAtFirstMatch )
2024-05-20 00:48:40 +05:30
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
spmHandler := httputils . NewBlockingSPMHandler [ error ] ( ctx , maxWorkers , maxErrorsWhenParallel , shouldStop )
defer spmHandler . Cancel ( )
2024-02-13 01:20:19 +05:30
// wrappedCallback is a callback that wraps the original callback
// to implement stop at first match logic
wrappedCallback := func ( event * output . InternalWrappedEvent ) {
if ! event . HasOperatorResult ( ) {
callback ( event ) // not required but we can allow it
return
}
// this will execute match condition such that if stop at first match is enabled
// this will be only executed once
spmHandler . MatchCallback ( func ( ) {
callback ( event )
} )
if shouldStop {
// stop all running requests and exit
spmHandler . Trigger ( )
}
}
2024-04-24 19:34:13 +05:30
// look for unresponsive hosts and cancel inflight requests as well
spmHandler . SetOnResultCallback ( func ( err error ) {
// marks thsi host as unresponsive if applicable
2025-01-31 17:16:57 +07:00
request . markHostError ( input , err )
2024-05-25 00:29:04 +05:30
if request . isUnresponsiveAddress ( input ) {
2024-04-24 19:34:13 +05:30
// stop all inflight requests
spmHandler . Cancel ( )
}
} )
2024-02-13 01:20:19 +05:30
// iterate payloads and make requests
generator := request . newGenerator ( false )
2020-12-28 20:02:26 +05:30
for {
2021-11-24 21:08:08 +05:30
inputData , payloads , ok := generator . nextValue ( )
if ! ok {
break
}
2024-04-03 23:06:08 +02:00
2024-04-25 15:37:56 +05:30
select {
case <- input . Context ( ) . Done ( ) :
return input . Context ( ) . Err ( )
default :
}
2024-04-03 23:06:08 +02:00
// resize check point - nop if there are no changes
if shouldFollowGlobal && spmHandler . Size ( ) != request . options . Options . PayloadConcurrency {
2024-05-01 00:28:11 +05:30
if err := spmHandler . Resize ( input . Context ( ) , request . options . Options . PayloadConcurrency ) ; err != nil {
return err
}
2024-04-03 23:06:08 +02:00
}
2024-04-24 19:34:13 +05:30
// break if stop at first match is found or host is unresponsive
2024-05-25 00:29:04 +05:30
if spmHandler . FoundFirstMatch ( ) || request . isUnresponsiveAddress ( input ) {
2024-04-24 19:34:13 +05:30
break
}
2022-11-09 14:18:56 +01:00
ctx := request . newContext ( input )
2022-12-09 19:47:03 +01:00
generatedHttpRequest , err := generator . Make ( ctx , input , inputData , payloads , dynamicValues )
2020-12-28 20:02:26 +05:30
if err != nil {
2023-07-13 00:51:06 +05:30
if err == types . ErrNoMoreRequests {
2021-11-10 18:11:42 +01:00
break
}
2021-10-01 14:30:04 +03:00
request . options . Progress . IncrementFailedRequestsBy ( int64 ( generator . Total ( ) ) )
2021-01-01 19:36:21 +05:30
return err
2020-12-26 14:55:15 +05:30
}
2022-11-09 14:18:56 +01:00
if input . MetaInput . Input == "" {
input . MetaInput . Input = generatedHttpRequest . URL ( )
2021-11-10 18:11:42 +01:00
}
2024-05-25 00:29:04 +05:30
updatedInput := contextargs . GetCopyIfHostOutdated ( input , generatedHttpRequest . URL ( ) )
if request . isUnresponsiveAddress ( updatedInput ) {
// skip on unresponsive host no need to continue
spmHandler . Cancel ( )
return nil
}
2024-02-13 01:20:19 +05:30
spmHandler . Acquire ( )
2020-12-28 20:02:26 +05:30
go func ( httpRequest * generatedRequest ) {
2024-02-13 01:20:19 +05:30
defer spmHandler . Release ( )
2024-05-25 00:29:04 +05:30
if spmHandler . FoundFirstMatch ( ) || request . isUnresponsiveAddress ( updatedInput ) || spmHandler . Cancelled ( ) {
2024-04-24 19:34:13 +05:30
return
}
// putting ratelimiter here prevents any unnecessary waiting if any
request . options . RateLimitTake ( )
// after ratelimit take, check if we need to stop
2024-05-25 00:29:04 +05:30
if spmHandler . FoundFirstMatch ( ) || request . isUnresponsiveAddress ( updatedInput ) || spmHandler . Cancelled ( ) {
2024-02-13 01:20:19 +05:30
return
}
2021-07-10 14:54:49 +05:30
2024-02-13 01:20:19 +05:30
select {
case <- spmHandler . Done ( ) :
return
2024-04-24 19:34:13 +05:30
case spmHandler . ResultChan <- request . executeRequest ( input , httpRequest , make ( map [ string ] interface { } ) , false , wrappedCallback , 0 ) :
2024-02-13 01:20:19 +05:30
return
2020-12-28 20:02:26 +05:30
}
2021-10-01 14:30:04 +03:00
} ( generatedHttpRequest )
request . options . Progress . IncrementRequests ( )
2020-12-26 14:55:15 +05:30
}
2024-02-13 01:20:19 +05:30
spmHandler . Wait ( )
if spmHandler . FoundFirstMatch ( ) {
// ignore any context cancellation and in-transit execution errors
return nil
}
return multierr . Combine ( spmHandler . CombinedResults ( ) ... )
2020-12-26 14:55:15 +05:30
}
2021-06-09 11:15:21 +05:30
// executeTurboHTTP executes turbo http request for a URL
2022-10-03 12:12:20 +02:00
func ( request * Request ) executeTurboHTTP ( input * contextargs . Context , dynamicValues , previous output . InternalEvent , callback protocols . OutputEventCallback ) error {
2022-11-01 20:28:50 +05:30
generator := request . newGenerator ( false )
2020-12-26 14:55:15 +05:30
// need to extract the target from the url
2023-01-24 22:04:52 +05:30
URL , err := urlutil . Parse ( input . MetaInput . Input )
2020-12-26 14:55:15 +05:30
if err != nil {
2021-01-01 19:36:21 +05:30
return err
2020-12-26 14:55:15 +05:30
}
pipeOptions := rawhttp . DefaultPipelineOptions
pipeOptions . Host = URL . Host
pipeOptions . MaxConnections = 1
2021-10-01 14:30:04 +03:00
if request . PipelineConcurrentConnections > 0 {
pipeOptions . MaxConnections = request . PipelineConcurrentConnections
2020-12-26 14:55:15 +05:30
}
2021-10-01 14:30:04 +03:00
if request . PipelineRequestsPerConnection > 0 {
pipeOptions . MaxPendingRequests = request . PipelineRequestsPerConnection
2020-12-26 14:55:15 +05:30
}
2021-10-01 14:30:04 +03:00
pipeClient := rawhttp . NewPipelineClient ( pipeOptions )
2020-12-26 14:55:15 +05:30
// defaultMaxWorkers should be a sufficient value to keep queues always full
// in case the queue is bigger increase the workers
2025-06-24 08:19:06 +08:00
maxWorkers := max ( pipeOptions . MaxPendingRequests , defaultMaxWorkers )
2020-12-28 20:02:26 +05:30
2024-02-13 01:20:19 +05:30
// Stop-at-first-match logic while executing requests
// parallely using threads
shouldStop := ( request . options . Options . StopAtFirstMatch || request . StopAtFirstMatch || request . options . StopAtFirstMatch )
2024-05-20 00:48:40 +05:30
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
spmHandler := httputils . NewBlockingSPMHandler [ error ] ( ctx , maxWorkers , maxErrorsWhenParallel , shouldStop )
defer spmHandler . Cancel ( )
2024-02-13 01:20:19 +05:30
// wrappedCallback is a callback that wraps the original callback
// to implement stop at first match logic
wrappedCallback := func ( event * output . InternalWrappedEvent ) {
if ! event . HasOperatorResult ( ) {
callback ( event ) // not required but we can allow it
return
}
// this will execute match condition such that if stop at first match is enabled
// this will be only executed once
spmHandler . MatchCallback ( func ( ) {
callback ( event )
} )
if shouldStop {
// stop all running requests and exit
spmHandler . Trigger ( )
}
}
2024-04-24 19:34:13 +05:30
// look for unresponsive hosts and cancel inflight requests as well
spmHandler . SetOnResultCallback ( func ( err error ) {
// marks thsi host as unresponsive if applicable
2025-01-31 17:16:57 +07:00
request . markHostError ( input , err )
2024-05-25 00:29:04 +05:30
if request . isUnresponsiveAddress ( input ) {
2024-04-24 19:34:13 +05:30
// stop all inflight requests
spmHandler . Cancel ( )
}
} )
2020-12-28 20:02:26 +05:30
for {
2021-11-24 21:08:08 +05:30
inputData , payloads , ok := generator . nextValue ( )
if ! ok {
break
}
2024-04-25 15:37:56 +05:30
select {
case <- input . Context ( ) . Done ( ) :
return input . Context ( ) . Err ( )
default :
}
2024-05-25 00:29:04 +05:30
if spmHandler . FoundFirstMatch ( ) || request . isUnresponsiveAddress ( input ) || spmHandler . Cancelled ( ) {
2024-04-24 19:34:13 +05:30
// skip if first match is found
break
}
2022-11-09 14:18:56 +01:00
ctx := request . newContext ( input )
2022-12-09 19:47:03 +01:00
generatedHttpRequest , err := generator . Make ( ctx , input , inputData , payloads , dynamicValues )
2020-12-28 20:02:26 +05:30
if err != nil {
2021-10-01 14:30:04 +03:00
request . options . Progress . IncrementFailedRequestsBy ( int64 ( generator . Total ( ) ) )
2021-01-01 19:36:21 +05:30
return err
2020-12-28 20:02:26 +05:30
}
2022-11-09 14:18:56 +01:00
if input . MetaInput . Input == "" {
input . MetaInput . Input = generatedHttpRequest . URL ( )
2021-11-10 18:11:42 +01:00
}
2024-05-25 00:29:04 +05:30
updatedInput := contextargs . GetCopyIfHostOutdated ( input , generatedHttpRequest . URL ( ) )
if request . isUnresponsiveAddress ( updatedInput ) {
// skip on unresponsive host no need to continue
spmHandler . Cancel ( )
return nil
}
2021-10-01 14:30:04 +03:00
generatedHttpRequest . pipelinedClient = pipeClient
2024-02-13 01:20:19 +05:30
spmHandler . Acquire ( )
2020-12-28 20:02:26 +05:30
go func ( httpRequest * generatedRequest ) {
2024-02-13 01:20:19 +05:30
defer spmHandler . Release ( )
2024-05-25 00:29:04 +05:30
if spmHandler . FoundFirstMatch ( ) || request . isUnresponsiveAddress ( updatedInput ) {
2024-02-13 01:20:19 +05:30
// skip if first match is found
return
}
select {
case <- spmHandler . Done ( ) :
return
case spmHandler . ResultChan <- request . executeRequest ( input , httpRequest , previous , false , wrappedCallback , 0 ) :
return
2020-12-28 20:02:26 +05:30
}
2021-10-01 14:30:04 +03:00
} ( generatedHttpRequest )
request . options . Progress . IncrementRequests ( )
2020-12-26 14:55:15 +05:30
}
2024-02-13 01:20:19 +05:30
spmHandler . Wait ( )
if spmHandler . FoundFirstMatch ( ) {
// ignore any context cancellation and in-transit execution errors
return nil
}
return multierr . Combine ( spmHandler . CombinedResults ( ) ... )
2020-12-26 14:55:15 +05:30
}
2020-12-29 11:42:46 +05:30
// ExecuteWithResults executes the final request on a URL
2022-10-03 12:12:20 +02:00
func ( request * Request ) ExecuteWithResults ( input * contextargs . Context , dynamicValues , previous output . InternalEvent , callback protocols . OutputEventCallback ) error {
2022-06-28 20:20:18 +05:30
if request . Pipeline || request . Race && request . RaceNumberRequests > 0 || request . Threads > 0 {
variablesMap := request . options . Variables . Evaluate ( generators . MergeMaps ( dynamicValues , previous ) )
2023-05-25 18:32:35 +02:00
dynamicValues = generators . MergeMaps ( variablesMap , dynamicValues , request . options . Constants )
2022-06-28 20:20:18 +05:30
}
2020-12-26 14:55:15 +05:30
// verify if pipeline was requested
2021-10-01 14:30:04 +03:00
if request . Pipeline {
2022-10-03 12:12:20 +02:00
return request . executeTurboHTTP ( input , dynamicValues , previous , callback )
2020-12-26 14:55:15 +05:30
}
// verify if a basic race condition was requested
2021-10-01 14:30:04 +03:00
if request . Race && request . RaceNumberRequests > 0 {
2022-10-03 12:12:20 +02:00
return request . executeRaceRequest ( input , dynamicValues , callback )
2020-12-26 14:55:15 +05:30
}
2022-11-01 20:28:50 +05:30
// verify if fuzz elaboration was requested
if len ( request . Fuzzing ) > 0 {
return request . executeFuzzingRule ( input , dynamicValues , callback )
}
2024-02-02 02:05:30 +05:30
// verify if parallel elaboration was requested
2024-03-13 20:35:19 +05:30
if request . Threads > 0 && len ( request . Payloads ) > 0 {
2024-02-02 02:05:30 +05:30
return request . executeParallelHTTP ( input , dynamicValues , callback )
}
2022-11-01 20:28:50 +05:30
generator := request . newGenerator ( false )
2020-12-26 14:55:15 +05:30
2021-11-24 21:08:08 +05:30
var gotDynamicValues map [ string ] [ ] string
2020-12-28 20:02:26 +05:30
var requestErr error
2023-08-18 02:37:35 +05:30
2020-12-28 20:02:26 +05:30
for {
2021-11-24 21:08:08 +05:30
// returns two values, error and skip, which skips the execution for the request instance.
executeFunc := func ( data string , payloads , dynamicValue map [ string ] interface { } ) ( bool , error ) {
2022-02-01 12:10:18 +01:00
hasInteractMatchers := interactsh . HasMatchers ( request . CompiledOperators )
2022-03-30 20:35:46 +05:30
2024-04-03 19:40:09 +02:00
request . options . RateLimitTake ( )
2022-09-19 13:39:28 +02:00
2022-11-09 14:18:56 +01:00
ctx := request . newContext ( input )
2024-07-15 13:27:15 +03:00
ctxWithTimeout , cancel := context . WithTimeoutCause ( ctx , request . options . Options . GetTimeouts ( ) . HttpTimeout , ErrHttpEngineRequestDeadline )
2022-07-21 21:29:34 +05:30
defer cancel ( )
2024-04-25 15:37:56 +05:30
2022-12-09 19:47:03 +01:00
generatedHttpRequest , err := generator . Make ( ctxWithTimeout , input , data , payloads , dynamicValue )
2021-11-24 21:08:08 +05:30
if err != nil {
2023-07-13 00:51:06 +05:30
if err == types . ErrNoMoreRequests {
2021-11-24 21:08:08 +05:30
return true , nil
}
return true , err
2021-11-10 18:11:42 +01:00
}
2024-05-25 00:29:04 +05:30
// ideally if http template used a custom port or hostname
// we would want to update it in input but currently templateCtx logic
// is closely tied to contextargs.Context so we are temporarily creating
// a copy and using it to check for host errors etc
// but this should be replaced once templateCtx is refactored properly
updatedInput := contextargs . GetCopyIfHostOutdated ( input , generatedHttpRequest . URL ( ) )
2022-10-10 08:10:07 +02:00
if generatedHttpRequest . customCancelFunction != nil {
defer generatedHttpRequest . customCancelFunction ( )
}
2022-02-01 12:34:12 +01:00
hasInteractMarkers := interactsh . HasMarkers ( data ) || len ( generatedHttpRequest . interactshURLs ) > 0
2022-11-09 14:18:56 +01:00
if input . MetaInput . Input == "" {
input . MetaInput . Input = generatedHttpRequest . URL ( )
2021-01-01 16:52:41 +05:30
}
2021-11-30 16:55:09 +05:30
// Check if hosts keep erroring
2024-05-25 00:29:04 +05:30
if request . isUnresponsiveAddress ( updatedInput ) {
2021-11-24 21:08:08 +05:30
return true , nil
2021-04-16 16:56:41 +05:30
}
2022-02-01 11:25:29 +01:00
var gotMatches bool
2024-03-13 20:35:19 +05:30
execReqErr := request . executeRequest ( input , generatedHttpRequest , previous , hasInteractMatchers , func ( event * output . InternalWrappedEvent ) {
2023-08-18 02:37:35 +05:30
// a special case where operators has interactsh matchers and multiple request are made
// ex: status_code_2 , interactsh_protocol (from 1st request) etc
needsRequestEvent := interactsh . HasMatchers ( request . CompiledOperators ) && request . NeedsRequestCondition ( )
if ( hasInteractMarkers || needsRequestEvent ) && request . options . Interactsh != nil {
2023-04-16 19:49:35 +02:00
requestData := & interactsh . RequestData {
2021-11-24 21:08:08 +05:30
MakeResultFunc : request . MakeResultEvent ,
Event : event ,
Operators : request . CompiledOperators ,
MatchFunc : request . Match ,
ExtractFunc : request . Extract ,
2023-04-16 19:49:35 +02:00
}
2024-01-31 01:59:49 +05:30
allOASTUrls := httputils . GetInteractshURLSFromEvent ( event . InternalEvent )
2023-08-18 02:37:35 +05:30
allOASTUrls = append ( allOASTUrls , generatedHttpRequest . interactshURLs ... )
request . options . Interactsh . RequestEvent ( sliceutil . Dedupe ( allOASTUrls ) , requestData )
2023-04-16 19:49:35 +02:00
gotMatches = request . options . Interactsh . AlreadyMatched ( requestData )
}
// Add the extracts to the dynamic values if any.
if event . OperatorsResult != nil {
gotMatches = event . OperatorsResult . Matched
gotDynamicValues = generators . MergeMapsMany ( event . OperatorsResult . DynamicValues , dynamicValues , gotDynamicValues )
2021-11-24 21:08:08 +05:30
}
2023-03-16 23:20:38 +05:30
// Note: This is a race condition prone zone i.e when request has interactsh_matchers
// Interactsh.RequestEvent tries to access/update output.InternalWrappedEvent depending on logic
// to avoid conflicts with `callback` mutex is used here and in Interactsh.RequestEvent
// Note: this only happens if requests > 1 and interactsh matcher is used
// TODO: interactsh logic in nuclei needs to be refactored to avoid such situations
2023-03-15 20:45:44 +05:30
callback ( event )
2022-06-11 14:29:05 +05:30
} , generator . currentIndex )
2021-11-30 16:55:09 +05:30
2021-11-24 21:08:08 +05:30
// If a variable is unresolved, skip all further requests
2024-04-01 12:25:17 +05:30
if errors . Is ( execReqErr , ErrMissingVars ) {
2021-11-24 21:08:08 +05:30
return true , nil
2021-01-01 16:52:41 +05:30
}
2025-05-07 17:22:15 +05:30
2024-03-13 20:35:19 +05:30
if execReqErr != nil {
2025-05-07 17:22:15 +05:30
request . markHostError ( updatedInput , execReqErr )
2024-04-24 19:34:13 +05:30
// if applicable mark the host as unresponsive
2025-05-07 17:22:15 +05:30
reqKitErr := errkit . FromError ( execReqErr )
reqKitErr . Msgf ( "got err while executing %v" , generatedHttpRequest . URL ( ) )
requestErr = reqKitErr
2024-04-24 19:34:13 +05:30
request . options . Progress . IncrementFailedRequestsBy ( 1 )
} else {
request . options . Progress . IncrementRequests ( )
2021-04-16 16:56:41 +05:30
}
2021-11-24 21:08:08 +05:30
2021-12-02 17:03:02 +05:30
// If this was a match, and we want to stop at first match, skip all further requests.
2023-04-16 19:49:35 +02:00
shouldStopAtFirstMatch := generatedHttpRequest . original . options . Options . StopAtFirstMatch || generatedHttpRequest . original . options . StopAtFirstMatch || request . StopAtFirstMatch
if shouldStopAtFirstMatch && gotMatches {
2021-11-24 21:08:08 +05:30
return true , nil
2021-08-17 14:23:42 +05:30
}
2021-11-24 21:08:08 +05:30
return false , nil
2020-12-26 14:55:15 +05:30
}
2021-11-24 21:08:08 +05:30
inputData , payloads , ok := generator . nextValue ( )
if ! ok {
break
}
2024-04-25 15:37:56 +05:30
select {
case <- input . Context ( ) . Done ( ) :
return input . Context ( ) . Err ( )
default :
}
2021-11-24 21:08:08 +05:30
var gotErr error
var skip bool
if len ( gotDynamicValues ) > 0 {
2021-11-24 22:44:43 +05:30
operators . MakeDynamicValuesCallback ( gotDynamicValues , request . IterateAll , func ( data map [ string ] interface { } ) bool {
2021-11-24 21:08:08 +05:30
if skip , gotErr = executeFunc ( inputData , payloads , data ) ; skip || gotErr != nil {
return true
}
return false
} )
} else {
skip , gotErr = executeFunc ( inputData , payloads , dynamicValues )
}
if gotErr != nil && requestErr == nil {
requestErr = gotErr
}
if skip || gotErr != nil {
2025-05-07 17:22:15 +05:30
request . options . Progress . SetRequests ( uint64 ( generator . Remaining ( ) + 1 ) )
2020-12-26 14:55:15 +05:30
break
}
}
2021-01-01 19:36:21 +05:30
return requestErr
2020-12-26 14:55:15 +05:30
}
2024-05-15 15:34:59 +02:00
const drainReqSize = int64 ( 8 * unitutils . Kilo )
2021-02-04 22:09:32 +05:30
2021-02-26 13:13:11 +05:30
// executeRequest executes the actual generated request and returns error if occurred
2024-04-03 17:19:06 +05:30
func ( request * Request ) executeRequest ( input * contextargs . Context , generatedRequest * generatedRequest , previousEvent output . InternalEvent , hasInteractMatchers bool , processEvent protocols . OutputEventCallback , requestCount int ) ( err error ) {
2024-04-24 19:34:13 +05:30
// Check if hosts keep erroring
2024-05-25 00:29:04 +05:30
if request . isUnresponsiveAddress ( input ) {
2024-04-24 19:34:13 +05:30
return fmt . Errorf ( "hostErrorsCache : host %s is unresponsive" , input . MetaInput . Input )
}
2024-04-03 17:19:06 +05:30
// wrap one more callback for validation and fixing event
callback := func ( event * output . InternalWrappedEvent ) {
// validateNFixEvent performs necessary validation on generated event
// and attempts to fix it , this includes things like making sure
// `template-id` is set , `request-url-pattern` is set etc
request . validateNFixEvent ( input , generatedRequest , err , event )
processEvent ( event )
}
2024-03-17 16:25:26 +05:30
2021-10-01 14:30:04 +03:00
request . setCustomHeaders ( generatedRequest )
2020-12-26 14:55:15 +05:30
2022-03-29 20:36:26 +05:30
// Try to evaluate any payloads before replacement
finalMap := generators . MergeMaps ( generatedRequest . dynamicValues , generatedRequest . meta )
2022-12-09 19:47:03 +01:00
// add known variables from metainput
if _ , ok := finalMap [ "ip" ] ; ! ok && input . MetaInput . CustomIP != "" {
finalMap [ "ip" ] = input . MetaInput . CustomIP
}
2022-03-29 20:36:26 +05:30
for payloadName , payloadValue := range generatedRequest . meta {
if data , err := expressions . Evaluate ( types . ToString ( payloadValue ) , finalMap ) ; err == nil {
generatedRequest . meta [ payloadName ] = data
}
}
2020-12-26 14:55:15 +05:30
var (
2021-02-25 02:08:10 +01:00
resp * http . Response
2021-10-01 14:30:04 +03:00
fromCache bool
2021-02-25 02:08:10 +01:00
dumpedRequest [ ] byte
2020-12-26 14:55:15 +05:30
)
2021-02-08 01:43:51 +05:30
2021-11-26 13:49:12 +01:00
// Dump request for variables checks
2021-10-07 01:40:49 +05:30
// 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
2021-10-11 13:58:20 +03:00
if ! generatedRequest . original . Race {
2023-09-16 14:20:35 +05:30
// change encoding type to content-length unless transfer-encoding header is manually set
if generatedRequest . request != nil && ! stringsutil . EqualFoldAny ( generatedRequest . request . Method , http . MethodGet , http . MethodHead ) && generatedRequest . request . Body != nil && generatedRequest . request . Header . Get ( "Transfer-Encoding" ) != "chunked" {
var newReqBody * reader . ReusableReadCloser
newReqBody , ok := generatedRequest . request . Body . ( * reader . ReusableReadCloser )
if ! ok {
newReqBody , err = reader . NewReusableReadCloser ( generatedRequest . request . Body )
}
if err == nil {
// update the request body with the reusable reader
generatedRequest . request . Body = newReqBody
// get content length
length , _ := io . Copy ( io . Discard , newReqBody )
generatedRequest . request . ContentLength = length
} else {
// log error and continue
gologger . Verbose ( ) . Msgf ( "[%v] Could not read request body while forcing transfer encoding: %s\n" , request . options . TemplateID , err )
err = nil
}
}
// do the same for unsafe requests
if generatedRequest . rawRequest != nil && ! stringsutil . EqualFoldAny ( generatedRequest . rawRequest . Method , http . MethodGet , http . MethodHead ) && generatedRequest . rawRequest . Data != "" && generatedRequest . rawRequest . Headers [ "Transfer-Encoding" ] != "chunked" {
generatedRequest . rawRequest . Headers [ "Content-Length" ] = strconv . Itoa ( len ( generatedRequest . rawRequest . Data ) )
}
2021-10-07 01:40:49 +05:30
var dumpError error
2021-11-13 03:17:05 +01:00
// TODO: dump is currently not working with post-processors - somehow it alters the signature
2022-11-09 14:18:56 +01:00
dumpedRequest , dumpError = dump ( generatedRequest , input . MetaInput . Input )
2021-10-07 01:40:49 +05:30
if dumpError != nil {
return dumpError
}
dumpedRequestString := string ( dumpedRequest )
2021-12-18 20:06:51 +01:00
if ignoreList := GetVariablesNamesSkipList ( generatedRequest . original . Signature . Value ) ; ignoreList != nil {
if varErr := expressions . ContainsVariablesWithIgnoreList ( ignoreList , dumpedRequestString ) ; varErr != nil && ! request . SkipVariablesCheck {
2022-11-09 14:18:56 +01:00
gologger . Warning ( ) . Msgf ( "[%s] Could not make http request for %s: %v\n" , request . options . TemplateID , input . MetaInput . Input , varErr )
2024-04-01 12:25:17 +05:30
return ErrMissingVars
2021-12-18 20:06:51 +01:00
}
} else { // Check if are there any unresolved variables. If yes, skip unless overridden by user.
if varErr := expressions . ContainsUnresolvedVariables ( dumpedRequestString ) ; varErr != nil && ! request . SkipVariablesCheck {
2022-11-09 14:18:56 +01:00
gologger . Warning ( ) . Msgf ( "[%s] Could not make http request for %s: %v\n" , request . options . TemplateID , input . MetaInput . Input , varErr )
2024-04-01 12:25:17 +05:30
return ErrMissingVars
2021-12-18 20:06:51 +01:00
}
2021-10-07 01:40:49 +05:30
}
}
2024-03-14 03:08:53 +05:30
// === apply auth strategies ===
2024-08-16 19:40:48 +07:00
if generatedRequest . request != nil && ! request . SkipSecretFile {
2024-03-14 03:08:53 +05:30
generatedRequest . ApplyAuth ( request . options . AuthProvider )
}
2021-01-11 19:59:12 +05:30
var formedURL string
2021-01-16 12:06:27 +05:30
var hostname string
2020-12-26 14:55:15 +05:30
timeStart := time . Now ( )
2021-10-01 14:30:04 +03:00
if generatedRequest . original . Pipeline {
2023-09-16 14:20:35 +05:30
// if request is a pipeline request, use the pipelined client
2021-10-01 14:30:04 +03:00
if generatedRequest . rawRequest != nil {
formedURL = generatedRequest . rawRequest . FullURL
2023-01-24 22:04:52 +05:30
if parsed , parseErr := urlutil . ParseURL ( formedURL , true ) ; parseErr == nil {
2021-06-09 11:15:21 +05:30
hostname = parsed . Host
}
2022-11-09 14:18:56 +01:00
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 ) ) )
2021-10-01 14:30:04 +03:00
} else if generatedRequest . request != nil {
resp , err = generatedRequest . pipelinedClient . Dor ( generatedRequest . request )
2021-01-16 12:06:27 +05:30
}
2021-10-01 14:30:04 +03:00
} else if generatedRequest . original . Unsafe && generatedRequest . rawRequest != nil {
2023-09-16 14:20:35 +05:30
// if request is a unsafe request, use the rawhttp client
2021-10-01 14:30:04 +03:00
formedURL = generatedRequest . rawRequest . FullURL
2022-07-26 06:38:53 -05:00
// use request url as matched url if empty
if formedURL == "" {
2023-01-24 22:04:52 +05:30
urlx , err := urlutil . Parse ( input . MetaInput . Input )
2023-01-05 21:02:36 +05:30
if err != nil {
2023-06-20 01:25:22 +05:30
formedURL = fmt . Sprintf ( "%s%s" , input . MetaInput . Input , generatedRequest . rawRequest . Path )
2023-01-05 21:02:36 +05:30
} else {
2023-06-20 01:25:22 +05:30
_ = urlx . MergePath ( generatedRequest . rawRequest . Path , true )
formedURL = urlx . String ( )
2022-10-27 20:09:38 +02:00
}
2022-07-26 06:38:53 -05:00
}
2023-01-24 22:04:52 +05:30
if parsed , parseErr := urlutil . ParseURL ( formedURL , true ) ; parseErr == nil {
2021-02-26 13:13:11 +05:30
hostname = parsed . Host
2021-01-16 12:06:27 +05:30
}
2022-07-19 01:08:30 +05:30
options := * generatedRequest . original . rawhttpClient . Options
2021-10-01 14:30:04 +03:00
options . FollowRedirects = request . Redirects
options . CustomRawBytes = generatedRequest . rawRequest . UnsafeRawBytes
2022-02-25 19:26:10 +05:30
options . ForceReadAllBody = request . ForceReadAllBody
2022-05-27 18:23:07 +02:00
options . SNI = request . options . Options . SNI
2023-07-04 19:04:13 +05:30
inputUrl := input . MetaInput . Input
if url , err := urlutil . ParseURL ( inputUrl , false ) ; err == nil {
2023-09-16 14:20:35 +05:30
url . Path = ""
url . Params = urlutil . NewOrderedParams ( ) // donot include query params
// inputUrl should only contain scheme://host:port
inputUrl = url . String ( )
2023-07-04 19:04:13 +05:30
}
formedURL = fmt . Sprintf ( "%s%s" , inputUrl , generatedRequest . rawRequest . Path )
2024-05-25 00:29:04 +05:30
// send rawhttp request and get response
resp , err = httpclientpool . SendRawRequest ( generatedRequest . original . rawhttpClient , & httpclientpool . RawHttpRequestOpts {
Method : generatedRequest . rawRequest . Method ,
URL : inputUrl ,
Path : generatedRequest . rawRequest . Path ,
Headers : generators . ExpandMapValues ( generatedRequest . rawRequest . Headers ) ,
Body : io . NopCloser ( strings . NewReader ( generatedRequest . rawRequest . Data ) ) ,
Options : & options ,
} )
2020-12-26 14:55:15 +05:30
} else {
2023-09-16 14:20:35 +05:30
//** For Normal requests **//
2021-10-01 14:30:04 +03:00
hostname = generatedRequest . request . URL . Host
formedURL = generatedRequest . request . URL . String ( )
2020-12-26 14:55:15 +05:30
// if nuclei-project is available check if the request was already sent previously
2021-10-01 14:30:04 +03:00
if request . options . ProjectFile != nil {
2020-12-26 14:55:15 +05:30
// if unavailable fail silently
2021-10-01 14:30:04 +03:00
fromCache = true
resp , err = request . options . ProjectFile . Get ( dumpedRequest )
2020-12-26 14:55:15 +05:30
if err != nil {
2021-10-01 14:30:04 +03:00
fromCache = false
2020-12-26 14:55:15 +05:30
}
}
if resp == nil {
2021-12-02 15:57:52 +01:00
if errSignature := request . handleSignature ( generatedRequest ) ; errSignature != nil {
return errSignature
2021-11-12 19:29:45 +01:00
}
2022-10-03 12:12:20 +02:00
httpclient := request . httpClient
2024-05-25 00:29:04 +05:30
// this will be assigned/updated if this specific request has a custom configuration
var modifiedConfig * httpclientpool . Configuration
// check for cookie related configuration
2022-10-03 12:12:20 +02:00
if input . CookieJar != nil {
2024-08-19 23:02:27 +03:00
connConfiguration := request . connConfiguration . Clone ( )
2023-02-13 12:16:41 +01:00
connConfiguration . Connection . SetCookieJar ( input . CookieJar )
2024-05-25 00:29:04 +05:30
modifiedConfig = connConfiguration
}
// check for request updatedTimeout annotation
updatedTimeout , ok := generatedRequest . request . Context ( ) . Value ( httpclientpool . WithCustomTimeout { } ) . ( httpclientpool . WithCustomTimeout )
if ok {
if modifiedConfig == nil {
2024-08-19 23:02:27 +03:00
connConfiguration := request . connConfiguration . Clone ( )
modifiedConfig = connConfiguration
2024-05-25 00:29:04 +05:30
}
modifiedConfig . ResponseHeaderTimeout = updatedTimeout . Timeout
}
if modifiedConfig != nil {
client , err := httpclientpool . Get ( request . options . Options , modifiedConfig )
2022-10-03 12:12:20 +02:00
if err != nil {
return errors . Wrap ( err , "could not get http client" )
}
httpclient = client
}
2024-05-25 00:29:04 +05:30
2022-10-03 12:12:20 +02:00
resp , err = httpclient . Do ( generatedRequest . request )
2020-12-26 14:55:15 +05:30
}
}
2022-06-16 17:24:21 +05:30
// use request url as matched url if empty
if formedURL == "" {
2022-11-09 14:18:56 +01:00
formedURL = input . MetaInput . Input
2022-06-16 17:24:21 +05:30
}
2021-11-26 13:49:12 +01:00
2023-02-10 18:28:28 +05:30
// converts whitespace and other chars that cannot be printed to url encoded values
formedURL = urlutil . URLEncodeWithEscapes ( formedURL )
2021-11-26 13:49:12 +01:00
// Dump the requests containing all headers
if ! generatedRequest . original . Race {
var dumpError error
2022-11-09 14:18:56 +01:00
dumpedRequest , dumpError = dump ( generatedRequest , input . MetaInput . Input )
2021-11-26 13:49:12 +01:00
if dumpError != nil {
return dumpError
}
dumpedRequestString := string ( dumpedRequest )
2022-04-01 14:29:02 -05:00
if request . options . Options . Debug || request . options . Options . DebugRequests || request . options . Options . StoreResponse {
2022-06-16 17:24:21 +05:30
msg := fmt . Sprintf ( "[%s] Dumped HTTP request for %s\n\n" , request . options . TemplateID , formedURL )
2022-04-01 14:29:02 -05:00
if request . options . Options . Debug || request . options . Options . DebugRequests {
gologger . Info ( ) . Msg ( msg )
gologger . Print ( ) . Msgf ( "%s" , dumpedRequestString )
}
if request . options . Options . StoreResponse {
2022-11-09 14:18:56 +01:00
request . options . Output . WriteStoreDebugData ( input . MetaInput . Input , request . options . TemplateID , request . Type ( ) . String ( ) , fmt . Sprintf ( "%s\n%s" , msg , dumpedRequestString ) )
2022-04-01 14:29:02 -05:00
}
2021-11-26 13:49:12 +01:00
}
}
2024-02-29 00:51:17 +03:00
2020-12-29 01:30:07 +05:30
if err != nil {
2021-09-07 17:31:46 +03:00
// rawhttp doesn't support draining response bodies.
2022-05-04 19:49:33 +05:30
if resp != nil && resp . Body != nil && generatedRequest . rawRequest == nil && ! generatedRequest . original . Pipeline {
2022-08-25 13:22:08 +02:00
_ , _ = io . CopyN ( io . Discard , resp . Body , drainReqSize )
2020-12-29 01:30:07 +05:30
resp . Body . Close ( )
}
2021-11-05 03:01:41 +05:30
request . options . Output . Request ( request . options . TemplatePath , formedURL , request . Type ( ) . String ( ) , err )
2021-10-01 14:30:04 +03:00
request . options . Progress . IncrementErrorsBy ( 1 )
2021-08-26 02:43:58 +05:30
2023-11-27 19:54:45 +01:00
// In case of interactsh markers and request times out, still send
2021-09-07 17:31:46 +03:00
// a callback event so in case we receive an interaction, correlation is possible.
2023-11-27 19:54:45 +01:00
// Also, to log failed use-cases.
2024-04-01 12:25:17 +05:30
outputEvent := request . responseToDSLMap ( & http . Response { } , input . MetaInput . Input , formedURL , convUtil . String ( dumpedRequest ) , "" , "" , "" , 0 , generatedRequest . meta )
2023-11-27 19:54:45 +01:00
if i := strings . LastIndex ( hostname , ":" ) ; i != - 1 {
hostname = hostname [ : i ]
}
2022-11-09 14:18:56 +01:00
2023-11-27 19:54:45 +01:00
if input . MetaInput . CustomIP != "" {
outputEvent [ "ip" ] = input . MetaInput . CustomIP
} else {
2025-06-16 22:24:52 +05:30
outputEvent [ "ip" ] = request . dialer . GetDialedIP ( hostname )
2024-07-10 20:43:22 +05:30
// try getting cname
request . addCNameIfAvailable ( hostname , outputEvent )
2023-11-27 19:54:45 +01:00
}
2024-04-01 12:25:17 +05:30
if len ( generatedRequest . interactshURLs ) > 0 {
// according to logic we only need to trigger a callback if interactsh was used
// and request failed in hope that later on oast interaction will be received
2024-04-03 17:19:06 +05:30
event := & output . InternalWrappedEvent { }
2024-04-01 12:25:17 +05:30
if request . CompiledOperators != nil && request . CompiledOperators . HasDSL ( ) {
event . InternalEvent = outputEvent
}
callback ( event )
}
2021-01-01 19:36:21 +05:30
return err
2020-12-29 01:30:07 +05:30
}
2021-02-05 12:36:01 +05:30
2021-10-15 13:55:50 +05:30
var curlCommand string
2022-01-17 11:31:14 +01:00
if ! request . Unsafe && resp != nil && generatedRequest . request != nil && resp . Request != nil && ! request . Race {
2021-10-15 13:55:50 +05:30
bodyBytes , _ := generatedRequest . request . BodyBytes ( )
2022-08-25 13:22:08 +02:00
resp . Request . Body = io . NopCloser ( bytes . NewReader ( bodyBytes ) )
2023-08-01 22:17:42 +03:00
command , err := http2curl . GetCurlCommand ( generatedRequest . request . Request )
2021-10-15 13:55:50 +05:30
if err == nil && command != nil {
curlCommand = command . String ( )
}
}
2021-10-01 14:30:04 +03:00
gologger . Verbose ( ) . Msgf ( "[%s] Sent HTTP request to %s" , request . options . TemplateID , formedURL )
2021-11-05 03:01:41 +05:30
request . options . Output . Request ( request . options . TemplatePath , formedURL , request . Type ( ) . String ( ) , err )
2020-12-26 14:55:15 +05:30
duration := time . Since ( timeStart )
2024-02-29 00:51:17 +03:00
2024-01-31 01:59:49 +05:30
// define max body read limit
2024-02-29 00:51:17 +03:00
maxBodylimit := MaxBodyRead // 10MB
2024-01-31 01:59:49 +05:30
if request . MaxSize > 0 {
2024-05-15 15:34:59 +02:00
maxBodylimit = request . MaxSize
2024-02-29 00:51:17 +03:00
}
if request . options . Options . ResponseReadSize != 0 {
2024-05-15 15:34:59 +02:00
maxBodylimit = request . options . Options . ResponseReadSize
2024-01-31 01:59:49 +05:30
}
// respChain is http response chain that reads response body
// efficiently by reusing buffers and does all decoding and optimizations
2024-05-15 15:34:59 +02:00
respChain := httpUtils . NewResponseChain ( resp , int64 ( maxBodylimit ) )
2024-01-31 01:59:49 +05:30
defer respChain . Close ( ) // reuse buffers
// we only intend to log/save the final redirected response
// i.e why we have to use sync.Once to ensure it's only done once
var errx error
onceFunc := sync . OnceFunc ( func ( ) {
// if nuclei-project is enabled store the response if not previously done
if request . options . ProjectFile != nil && ! fromCache {
if err := request . options . ProjectFile . Set ( dumpedRequest , resp , respChain . Body ( ) . Bytes ( ) ) ; err != nil {
errx = errors . Wrap ( err , "could not store in project file" )
2021-08-30 12:37:36 +05:30
}
}
2024-01-31 01:59:49 +05:30
} )
2021-02-07 03:34:07 +05:30
2024-01-31 01:59:49 +05:30
// evaluate responses continiously until first redirect request in reverse order
for respChain . Has ( ) {
// fill buffers, read response body and reuse connection
if err := respChain . Fill ( ) ; err != nil {
return errors . Wrap ( err , "could not generate response chain" )
2021-11-26 18:51:02 +05:30
}
2025-02-13 17:13:39 +05:30
// log request stats
request . options . Output . RequestStatsLog ( strconv . Itoa ( respChain . Response ( ) . StatusCode ) , respChain . FullResponse ( ) . String ( ) )
2024-01-31 01:59:49 +05:30
// save response to projectfile
onceFunc ( )
2022-11-09 14:18:56 +01:00
matchedURL := input . MetaInput . Input
2023-01-05 21:02:36 +05:30
if generatedRequest . rawRequest != nil {
if generatedRequest . rawRequest . FullURL != "" {
matchedURL = generatedRequest . rawRequest . FullURL
} else {
matchedURL = formedURL
}
2021-09-10 21:19:05 +05:30
}
2021-11-09 06:00:30 +05:30
if generatedRequest . request != nil {
matchedURL = generatedRequest . request . URL . String ( )
2021-09-10 21:19:05 +05:30
}
2022-05-30 15:19:09 +05:30
// Give precedence to the final URL from response
2024-01-31 01:59:49 +05:30
if respChain . Request ( ) != nil {
if responseURL := respChain . Request ( ) . URL . String ( ) ; responseURL != "" {
2022-05-30 15:19:09 +05:30
matchedURL = responseURL
}
}
2024-11-19 11:51:32 +05:30
2021-11-09 06:00:30 +05:30
finalEvent := make ( output . InternalEvent )
2021-10-16 00:17:33 +02:00
2024-11-19 11:51:32 +05:30
if request . Analyzer != nil {
analyzer := analyzers . GetAnalyzer ( request . Analyzer . Name )
analysisMatched , analysisDetails , err := analyzer . Analyze ( & analyzers . Options {
FuzzGenerated : generatedRequest . fuzzGeneratedRequest ,
HttpClient : request . httpClient ,
ResponseTimeDelay : duration ,
AnalyzerParameters : request . Analyzer . Parameters ,
} )
if err != nil {
gologger . Warning ( ) . Msgf ( "Could not analyze response: %v\n" , err )
}
if analysisMatched {
finalEvent [ "analyzer_details" ] = analysisDetails
finalEvent [ "analyzer" ] = true
}
}
2024-04-01 12:25:17 +05:30
outputEvent := request . responseToDSLMap ( respChain . Response ( ) , input . MetaInput . Input , matchedURL , convUtil . String ( dumpedRequest ) , respChain . FullResponse ( ) . String ( ) , respChain . Body ( ) . String ( ) , respChain . Headers ( ) . String ( ) , duration , generatedRequest . meta )
2023-06-09 19:52:56 +05:30
// add response fields to template context and merge templatectx variables to output event
2023-08-31 18:03:01 +05:30
request . options . AddTemplateVars ( input . MetaInput , request . Type ( ) , request . ID , outputEvent )
2024-01-18 05:53:42 +05:30
if request . options . HasTemplateCtx ( input . MetaInput ) {
outputEvent = generators . MergeMaps ( outputEvent , request . options . GetTemplateCtx ( input . MetaInput ) . GetAll ( ) )
}
2021-11-09 06:00:30 +05:30
if i := strings . LastIndex ( hostname , ":" ) ; i != - 1 {
hostname = hostname [ : i ]
2021-10-16 00:17:33 +02:00
}
2021-11-09 06:00:30 +05:30
outputEvent [ "curl-command" ] = curlCommand
2022-11-09 14:18:56 +01:00
if input . MetaInput . CustomIP != "" {
outputEvent [ "ip" ] = input . MetaInput . CustomIP
} else {
2024-08-21 16:09:47 +02:00
dialer := protocolstate . GetDialer ( )
if dialer != nil {
outputEvent [ "ip" ] = dialer . GetDialedIP ( hostname )
}
2024-07-10 20:43:22 +05:30
// try getting cname
request . addCNameIfAvailable ( hostname , outputEvent )
2022-11-09 14:18:56 +01:00
}
2022-01-13 13:22:43 +05:30
if request . options . Interactsh != nil {
request . options . Interactsh . MakePlaceholders ( generatedRequest . interactshURLs , outputEvent )
}
2021-11-09 06:00:30 +05:30
for k , v := range previousEvent {
finalEvent [ k ] = v
2020-12-26 14:55:15 +05:30
}
2021-03-09 14:45:04 +05:30
for k , v := range outputEvent {
2021-11-09 06:00:30 +05:30
finalEvent [ k ] = v
}
2022-01-13 13:22:43 +05:30
2021-11-09 06:00:30 +05:30
// Add to history the current request number metadata if asked by the user.
2022-10-15 11:49:04 +02:00
if request . NeedsRequestCondition ( ) {
2021-11-09 06:00:30 +05:30
for k , v := range outputEvent {
key := fmt . Sprintf ( "%s_%d" , k , requestCount )
2023-10-20 17:54:10 +05:30
if previousEvent != nil {
2023-10-17 17:44:13 +05:30
previousEvent [ key ] = v
}
2021-11-09 06:00:30 +05:30
finalEvent [ key ] = v
}
2021-03-09 14:45:04 +05:30
}
2022-01-09 13:39:50 +01:00
// prune signature internal values if any
request . pruneSignatureInternalValues ( generatedRequest . meta )
2024-10-14 20:55:46 +07:00
interimEvent := generators . MergeMaps ( generatedRequest . dynamicValues , finalEvent )
isDebug := request . options . Options . Debug || request . options . Options . DebugResponse
event := eventcreator . CreateEventWithAdditionalOptions ( request , interimEvent , isDebug , func ( internalWrappedEvent * output . InternalWrappedEvent ) {
2021-11-09 06:00:30 +05:30
internalWrappedEvent . OperatorsResult . PayloadValues = generatedRequest . meta
} )
2024-10-14 20:55:46 +07:00
2022-02-01 12:10:18 +01:00
if hasInteractMatchers {
2021-11-22 17:53:25 +05:30
event . UsesInteractsh = true
}
2021-10-01 14:24:45 +03:00
2024-10-14 20:55:46 +07:00
if request . options . GlobalMatchers . HasMatchers ( ) {
request . options . GlobalMatchers . Match ( interimEvent , request . Match , request . Extract , isDebug , func ( event output . InternalEvent , result * operators . Result ) {
callback ( eventcreator . CreateEventWithOperatorResults ( request , event , result ) )
} )
}
2024-03-30 23:50:31 +05:30
// if requrlpattern is enabled, only then it is reflected in result event else it is empty string
// consult @Ice3man543 before changing this logic (context: vuln_hash)
if request . options . ExportReqURLPattern {
for _ , v := range event . Results {
v . ReqURLPattern = generatedRequest . requestURLPattern
}
}
2024-01-31 01:59:49 +05:30
responseContentType := respChain . Response ( ) . Header . Get ( "Content-Type" )
isResponseTruncated := request . MaxSize > 0 && respChain . Body ( ) . Len ( ) >= request . MaxSize
dumpResponse ( event , request , respChain . FullResponse ( ) . Bytes ( ) , formedURL , responseContentType , isResponseTruncated , input . MetaInput . Input )
2021-09-29 19:43:46 +03:00
2021-11-09 06:00:30 +05:30
callback ( event )
2022-05-30 15:19:09 +05:30
2025-02-13 18:46:28 +05:30
if request . options . FuzzStatsDB != nil && generatedRequest . fuzzGeneratedRequest . Request != nil {
request . options . FuzzStatsDB . RecordResultEvent ( fuzzStats . FuzzingEvent {
URL : input . MetaInput . Target ( ) ,
TemplateID : request . options . TemplateID ,
ComponentType : generatedRequest . fuzzGeneratedRequest . Component . Name ( ) ,
ComponentName : generatedRequest . fuzzGeneratedRequest . Parameter ,
PayloadSent : generatedRequest . fuzzGeneratedRequest . Value ,
StatusCode : respChain . Response ( ) . StatusCode ,
Matched : event . HasResults ( ) ,
RawRequest : string ( dumpedRequest ) ,
RawResponse : respChain . FullResponse ( ) . String ( ) ,
Severity : request . options . TemplateInfo . SeverityHolder . Severity . String ( ) ,
} )
}
2022-05-30 15:19:09 +05:30
// Skip further responses if we have stop-at-first-match and a match
2023-04-16 19:49:35 +02:00
if ( request . options . Options . StopAtFirstMatch || request . options . StopAtFirstMatch || request . StopAtFirstMatch ) && event . HasResults ( ) {
2022-05-30 15:19:09 +05:30
return nil
}
2024-01-31 01:59:49 +05:30
// proceed with previous response
// we evaluate operators recursively for each response
// until we reach the first redirect response
if ! respChain . Previous ( ) {
break
}
2021-11-09 06:00:30 +05:30
}
2024-01-31 01:59:49 +05:30
// return project file save error if any
return errx
2021-09-29 19:43:46 +03:00
}
2024-04-03 17:19:06 +05:30
// validateNFixEvent validates and fixes the event
// it adds any missing template-id and request-url-pattern
func ( request * Request ) validateNFixEvent ( input * contextargs . Context , gr * generatedRequest , err error , event * output . InternalWrappedEvent ) {
if event != nil {
if event . InternalEvent == nil {
event . InternalEvent = make ( map [ string ] interface { } )
event . InternalEvent [ "template-id" ] = request . options . TemplateID
}
// add the request URL pattern to the event
event . InternalEvent [ ReqURLPatternKey ] = gr . requestURLPattern
if event . InternalEvent [ "host" ] == nil {
event . InternalEvent [ "host" ] = input . MetaInput . Input
}
if event . InternalEvent [ "template-id" ] == nil {
event . InternalEvent [ "template-id" ] = request . options . TemplateID
}
if event . InternalEvent [ "type" ] == nil {
event . InternalEvent [ "type" ] = request . Type ( ) . String ( )
}
if event . InternalEvent [ "template-path" ] == nil {
event . InternalEvent [ "template-path" ] = request . options . TemplatePath
}
if event . InternalEvent [ "template-info" ] == nil {
event . InternalEvent [ "template-info" ] = request . options . TemplateInfo
}
if err != nil {
event . InternalEvent [ "error" ] = err . Error ( )
}
}
}
2024-07-10 20:43:22 +05:30
// addCNameIfAvailable adds the cname to the event if available
func ( request * Request ) addCNameIfAvailable ( hostname string , outputEvent map [ string ] interface { } ) {
2025-06-16 22:24:52 +05:30
if request . dialer == nil {
2024-09-25 23:30:39 +07:00
return
}
2025-06-16 22:24:52 +05:30
data , err := request . dialer . GetDNSData ( hostname )
2024-07-10 20:43:22 +05:30
if err == nil {
switch len ( data . CNAME ) {
case 0 :
return
case 1 :
outputEvent [ "cname" ] = data . CNAME [ 0 ]
default :
// add 1st and put others in cname_all
outputEvent [ "cname" ] = data . CNAME [ 0 ]
outputEvent [ "cname_all" ] = data . CNAME
}
}
}
2021-12-02 15:57:52 +01:00
// handleSignature of the http request
func ( request * Request ) handleSignature ( generatedRequest * generatedRequest ) error {
switch request . Signature . Value {
case AWSSignature :
var awsSigner signer . Signer
2023-01-24 20:50:20 +05:30
allvars := generators . MergeMaps ( request . options . Options . Vars . AsMap ( ) , generatedRequest . dynamicValues )
2022-12-03 07:10:57 +05:30
awsopts := signer . AWSOptions {
2023-01-24 20:50:20 +05:30
AwsID : types . ToString ( allvars [ "aws-id" ] ) ,
AwsSecretToken : types . ToString ( allvars [ "aws-secret" ] ) ,
2022-12-03 07:10:57 +05:30
}
awsSigner , err := signerpool . Get ( request . options . Options , & signerpool . Configuration { SignerArgs : & awsopts } )
2021-12-02 15:57:52 +01:00
if err != nil {
return err
}
2023-01-24 20:50:20 +05:30
ctx := signer . GetCtxWithArgs ( allvars , signer . AwsDefaultVars )
2022-12-03 07:10:57 +05:30
err = awsSigner . SignHTTP ( ctx , generatedRequest . request . Request )
2021-12-02 15:57:52 +01:00
if err != nil {
return err
}
}
return nil
}
2020-12-29 01:30:07 +05:30
// setCustomHeaders sets the custom headers for generated request
2021-10-01 14:30:04 +03:00
func ( request * Request ) setCustomHeaders ( req * generatedRequest ) {
for k , v := range request . customHeaders {
2021-02-04 22:00:09 +05:30
if req . rawRequest != nil {
req . rawRequest . Headers [ k ] = v
2020-12-29 01:30:07 +05:30
} else {
2021-05-04 14:36:04 +02:00
kk , vv := strings . TrimSpace ( k ) , strings . TrimSpace ( v )
2024-07-26 22:24:35 +07:00
// NOTE(dwisiswant0): Do we really not need to convert it first into
// lowercase?
2021-05-04 14:36:04 +02:00
if kk == "Host" {
req . request . Host = vv
2024-07-26 22:24:35 +07:00
continue
2021-05-04 14:36:04 +02:00
}
2024-07-26 22:24:35 +07:00
req . request . Header [ kk ] = [ ] string { vv }
2020-12-29 01:30:07 +05:30
}
2020-12-26 14:55:15 +05:30
}
}
2021-10-30 13:17:47 +03:00
const CRLF = "\r\n"
2022-04-01 14:29:02 -05:00
func dumpResponse ( event * output . InternalWrappedEvent , request * Request , redirectedResponse [ ] byte , formedURL string , responseContentType string , isResponseTruncated bool , reqURL string ) {
cliOptions := request . options . Options
if cliOptions . Debug || cliOptions . DebugResponse || cliOptions . StoreResponse {
2021-10-30 13:17:47 +03:00
response := string ( redirectedResponse )
2021-11-01 20:45:54 +02:00
var highlightedResult string
2025-06-19 20:07:59 +05:30
if ( responseContentType == "application/octet-stream" || responseContentType == "application/x-www-form-urlencoded" ) && responsehighlighter . HasBinaryContent ( response ) {
2021-11-01 20:45:54 +02:00
highlightedResult = createResponseHexDump ( event , response , cliOptions . NoColor )
} else {
highlightedResult = responsehighlighter . Highlight ( event . OperatorsResult , response , cliOptions . NoColor , false )
2021-10-30 13:17:47 +03:00
}
2022-03-07 13:32:17 +01:00
msg := "[%s] Dumped HTTP response %s\n\n%s"
if isResponseTruncated {
msg = "[%s] Dumped HTTP response (Truncated) %s\n\n%s"
}
2022-04-01 14:29:02 -05:00
fMsg := fmt . Sprintf ( msg , request . options . TemplateID , formedURL , highlightedResult )
if cliOptions . Debug || cliOptions . DebugResponse {
gologger . Debug ( ) . Msg ( fMsg )
}
if cliOptions . StoreResponse {
request . options . Output . WriteStoreDebugData ( reqURL , request . options . TemplateID , request . Type ( ) . String ( ) , fMsg )
}
2021-11-01 20:45:54 +02:00
}
}
func createResponseHexDump ( event * output . InternalWrappedEvent , response string , noColor bool ) string {
CRLFs := CRLF + CRLF
headerEndIndex := strings . Index ( response , CRLFs ) + len ( CRLFs )
if headerEndIndex > 0 {
headers := response [ 0 : headerEndIndex ]
responseBodyHexDump := hex . Dump ( [ ] byte ( response [ headerEndIndex : ] ) )
highlightedHeaders := responsehighlighter . Highlight ( event . OperatorsResult , headers , noColor , false )
highlightedResponse := responsehighlighter . Highlight ( event . OperatorsResult , responseBodyHexDump , noColor , true )
return fmt . Sprintf ( "%s\n%s" , highlightedHeaders , highlightedResponse )
} else {
return responsehighlighter . Highlight ( event . OperatorsResult , hex . Dump ( [ ] byte ( response ) ) , noColor , true )
2021-10-30 13:17:47 +03:00
}
}
2022-01-09 13:39:50 +01:00
func ( request * Request ) pruneSignatureInternalValues ( maps ... map [ string ] interface { } ) {
var signatureFieldsToSkip map [ string ] interface { }
switch request . Signature . Value {
case AWSSignature :
2022-02-07 16:41:55 +02:00
signatureFieldsToSkip = signer . AwsInternalOnlyVars
2022-01-09 13:39:50 +01:00
default :
return
}
for _ , m := range maps {
for fieldName := range signatureFieldsToSkip {
delete ( m , fieldName )
}
}
}
2022-11-09 14:18:56 +01:00
func ( request * Request ) newContext ( input * contextargs . Context ) context . Context {
if input . MetaInput . CustomIP != "" {
2024-04-25 15:37:56 +05:30
return context . WithValue ( input . Context ( ) , fastdialer . IP , input . MetaInput . CustomIP )
2022-11-09 14:18:56 +01:00
}
2024-04-25 15:37:56 +05:30
return input . Context ( )
2022-11-09 14:18:56 +01:00
}
2024-04-24 19:34:13 +05:30
2025-01-31 17:16:57 +07:00
// markHostError checks if the error is a unreponsive host error and marks it
func ( request * Request ) markHostError ( input * contextargs . Context , err error ) {
2025-05-07 17:22:15 +05:30
if request . options . HostErrorsCache != nil && err != nil {
2025-01-31 17:16:57 +07:00
request . options . HostErrorsCache . MarkFailedOrRemove ( request . options . ProtocolType . String ( ) , input , err )
2024-04-24 19:34:13 +05:30
}
}
2024-05-25 00:29:04 +05:30
// isUnresponsiveAddress checks if the error is a unreponsive based on its execution history
func ( request * Request ) isUnresponsiveAddress ( input * contextargs . Context ) bool {
2024-04-24 19:34:13 +05:30
if request . options . HostErrorsCache != nil {
2024-09-28 17:20:35 +04:00
return request . options . HostErrorsCache . Check ( request . options . ProtocolType . String ( ) , input )
2024-04-24 19:34:13 +05:30
}
return false
}