2020-12-26 09:25:15 +00:00
package http
2020-12-28 14:32:26 +00:00
import (
2021-02-07 20:25:53 +00:00
"bytes"
2021-03-08 13:31:40 +00:00
"fmt"
2020-12-28 14:32:26 +00:00
"io"
"io/ioutil"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"sync"
"time"
"github.com/pkg/errors"
2020-12-28 20:00:07 +00:00
"github.com/projectdiscovery/gologger"
2020-12-28 14:32:26 +00:00
"github.com/projectdiscovery/nuclei/v2/pkg/output"
2021-01-01 14:06:21 +00:00
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
2020-12-28 14:32:26 +00:00
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
2021-01-01 09:58:28 +00:00
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/tostring"
2021-01-16 06:36:27 +00:00
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/httpclientpool"
2020-12-28 14:32:26 +00:00
"github.com/projectdiscovery/rawhttp"
"github.com/remeh/sizedwaitgroup"
"go.uber.org/multierr"
)
const defaultMaxWorkers = 150
// executeRaceRequest executes race condition request for a URL
2021-02-26 07:43:11 +00:00
func ( r * Request ) executeRaceRequest ( reqURL string , previous output . InternalEvent , callback protocols . OutputEventCallback ) error {
2021-03-01 04:18:31 +00:00
var requests [ ] * generatedRequest
// 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
2021-02-04 16:30:09 +00:00
generator := r . newGenerator ( )
2021-03-01 04:18:31 +00:00
requestForDump , err := generator . Make ( reqURL , nil )
if err != nil {
return err
}
r . setCustomHeaders ( requestForDump )
dumpedRequest , err := dump ( requestForDump , reqURL )
if err != nil {
return err
}
if r . options . Options . Debug || r . options . Options . DebugRequests {
gologger . Info ( ) . Msgf ( "[%s] Dumped HTTP request for %s\n\n" , r . options . TemplateID , reqURL )
gologger . Print ( ) . Msgf ( "%s" , string ( dumpedRequest ) )
}
previous [ "request" ] = string ( dumpedRequest )
2020-12-26 09:25:15 +00:00
2021-03-01 04:18:31 +00:00
// Pre-Generate requests
for i := 0 ; i < r . RaceNumberRequests ; i ++ {
generator := r . newGenerator ( )
request , err := generator . Make ( reqURL , nil )
if err != nil {
return err
}
requests = append ( requests , request )
}
2020-12-28 14:32:26 +00:00
2021-03-01 04:18:31 +00:00
wg := sync . WaitGroup { }
2020-12-28 14:32:26 +00:00
var requestErr error
2020-12-30 11:19:45 +00:00
mutex := & sync . Mutex { }
2021-02-04 16:30:09 +00:00
for i := 0 ; i < r . RaceNumberRequests ; i ++ {
2021-03-01 04:18:31 +00:00
wg . Add ( 1 )
2020-12-28 14:32:26 +00:00
go func ( httpRequest * generatedRequest ) {
2021-03-01 04:18:31 +00:00
defer wg . Done ( )
2021-02-26 07:43:11 +00:00
err := r . executeRequest ( reqURL , httpRequest , previous , callback )
2020-12-28 14:32:26 +00:00
mutex . Lock ( )
2020-12-26 09:25:15 +00:00
if err != nil {
2020-12-28 14:32:26 +00:00
requestErr = multierr . Append ( requestErr , err )
2020-12-26 09:25:15 +00:00
}
2020-12-28 14:32:26 +00:00
mutex . Unlock ( )
2021-03-01 04:18:31 +00:00
} ( requests [ i ] )
2021-03-01 03:31:53 +00:00
r . options . Progress . IncrementRequests ( )
2020-12-26 09:25:15 +00:00
}
2021-03-01 04:18:31 +00:00
wg . Wait ( )
2021-01-01 14:06:21 +00:00
return requestErr
2020-12-26 09:25:15 +00:00
}
2021-02-04 16:30:09 +00:00
// executeRaceRequest executes parallel requests for a template
func ( r * Request ) executeParallelHTTP ( reqURL string , dynamicValues , previous output . InternalEvent , callback protocols . OutputEventCallback ) error {
generator := r . newGenerator ( )
2020-12-26 09:25:15 +00:00
// Workers that keeps enqueuing new requests
2021-02-04 16:30:09 +00:00
maxWorkers := r . Threads
2020-12-26 09:25:15 +00:00
swg := sizedwaitgroup . New ( maxWorkers )
2020-12-28 14:32:26 +00:00
var requestErr error
2020-12-30 11:19:45 +00:00
mutex := & sync . Mutex { }
2020-12-28 14:32:26 +00:00
for {
request , err := generator . Make ( reqURL , dynamicValues )
if err == io . EOF {
break
}
if err != nil {
2021-03-02 01:22:15 +00:00
r . options . Progress . IncrementFailedRequestsBy ( int64 ( generator . Total ( ) ) )
2021-01-01 14:06:21 +00:00
return err
2020-12-26 09:25:15 +00:00
}
2020-12-28 14:32:26 +00:00
swg . Add ( )
go func ( httpRequest * generatedRequest ) {
defer swg . Done ( )
2021-02-04 16:30:09 +00:00
r . options . RateLimiter . Take ( )
2021-02-26 07:43:11 +00:00
err := r . executeRequest ( reqURL , httpRequest , previous , callback )
2020-12-28 14:32:26 +00:00
mutex . Lock ( )
if err != nil {
requestErr = multierr . Append ( requestErr , err )
}
mutex . Unlock ( )
} ( request )
2021-02-04 16:30:09 +00:00
r . options . Progress . IncrementRequests ( )
2020-12-26 09:25:15 +00:00
}
swg . Wait ( )
2021-01-01 14:06:21 +00:00
return requestErr
2020-12-26 09:25:15 +00:00
}
2021-02-04 16:30:09 +00:00
// executeRaceRequest executes turbo http request for a URL
func ( r * Request ) executeTurboHTTP ( reqURL string , dynamicValues , previous output . InternalEvent , callback protocols . OutputEventCallback ) error {
generator := r . newGenerator ( )
2020-12-26 09:25:15 +00:00
// need to extract the target from the url
URL , err := url . Parse ( reqURL )
if err != nil {
2021-01-01 14:06:21 +00:00
return err
2020-12-26 09:25:15 +00:00
}
pipeOptions := rawhttp . DefaultPipelineOptions
pipeOptions . Host = URL . Host
pipeOptions . MaxConnections = 1
2021-02-04 16:30:09 +00:00
if r . PipelineConcurrentConnections > 0 {
pipeOptions . MaxConnections = r . PipelineConcurrentConnections
2020-12-26 09:25:15 +00:00
}
2021-02-04 16:30:09 +00:00
if r . PipelineRequestsPerConnection > 0 {
pipeOptions . MaxPendingRequests = r . PipelineRequestsPerConnection
2020-12-26 09:25:15 +00:00
}
pipeclient := rawhttp . NewPipelineClient ( pipeOptions )
// defaultMaxWorkers should be a sufficient value to keep queues always full
maxWorkers := defaultMaxWorkers
// in case the queue is bigger increase the workers
if pipeOptions . MaxPendingRequests > maxWorkers {
maxWorkers = pipeOptions . MaxPendingRequests
}
swg := sizedwaitgroup . New ( maxWorkers )
2020-12-28 14:32:26 +00:00
var requestErr error
2020-12-30 11:19:45 +00:00
mutex := & sync . Mutex { }
2020-12-28 14:32:26 +00:00
for {
request , err := generator . Make ( reqURL , dynamicValues )
if err == io . EOF {
break
2020-12-26 09:25:15 +00:00
}
2020-12-28 14:32:26 +00:00
if err != nil {
2021-03-02 01:22:15 +00:00
r . options . Progress . IncrementFailedRequestsBy ( int64 ( generator . Total ( ) ) )
2021-01-01 14:06:21 +00:00
return err
2020-12-28 14:32:26 +00:00
}
request . pipelinedClient = pipeclient
swg . Add ( )
go func ( httpRequest * generatedRequest ) {
defer swg . Done ( )
2020-12-26 09:25:15 +00:00
2021-02-26 07:43:11 +00:00
err := r . executeRequest ( reqURL , httpRequest , previous , callback )
2020-12-28 14:32:26 +00:00
mutex . Lock ( )
if err != nil {
requestErr = multierr . Append ( requestErr , err )
}
mutex . Unlock ( )
} ( request )
2021-02-04 16:30:09 +00:00
r . options . Progress . IncrementRequests ( )
2020-12-26 09:25:15 +00:00
}
swg . Wait ( )
2021-01-01 14:06:21 +00:00
return requestErr
2020-12-26 09:25:15 +00:00
}
2020-12-29 06:12:46 +00:00
// ExecuteWithResults executes the final request on a URL
2021-01-16 08:40:24 +00:00
func ( r * Request ) ExecuteWithResults ( reqURL string , dynamicValues , previous output . InternalEvent , callback protocols . OutputEventCallback ) error {
2020-12-26 09:25:15 +00:00
// verify if pipeline was requested
2020-12-30 15:44:04 +00:00
if r . Pipeline {
2021-01-16 08:40:24 +00:00
return r . executeTurboHTTP ( reqURL , dynamicValues , previous , callback )
2020-12-26 09:25:15 +00:00
}
// verify if a basic race condition was requested
2020-12-30 15:44:04 +00:00
if r . Race && r . RaceNumberRequests > 0 {
2021-02-26 07:43:11 +00:00
return r . executeRaceRequest ( reqURL , previous , callback )
2020-12-26 09:25:15 +00:00
}
// verify if parallel elaboration was requested
2020-12-30 15:44:04 +00:00
if r . Threads > 0 {
2021-01-16 08:40:24 +00:00
return r . executeParallelHTTP ( reqURL , dynamicValues , previous , callback )
2020-12-26 09:25:15 +00:00
}
2020-12-30 15:44:04 +00:00
generator := r . newGenerator ( )
2020-12-26 09:25:15 +00:00
2021-03-08 13:31:40 +00:00
requestCount := 1
2020-12-28 14:32:26 +00:00
var requestErr error
for {
request , err := generator . Make ( reqURL , dynamicValues )
if err == io . EOF {
break
}
if err != nil {
2021-03-02 01:22:15 +00:00
r . options . Progress . IncrementFailedRequestsBy ( int64 ( generator . Total ( ) ) )
2021-01-01 14:06:21 +00:00
return err
2020-12-28 14:32:26 +00:00
}
2020-12-26 09:25:15 +00:00
2021-01-01 14:06:21 +00:00
var gotOutput bool
2021-03-08 13:31:40 +00:00
var outputEvent output . InternalEvent
2020-12-30 15:44:04 +00:00
r . options . RateLimiter . Take ( )
2021-02-26 07:43:11 +00:00
err = r . executeRequest ( reqURL , request , previous , func ( event * output . InternalWrappedEvent ) {
2021-01-01 11:22:41 +00:00
// Add the extracts to the dynamic values if any.
2021-01-01 14:06:21 +00:00
if event . OperatorsResult != nil {
gotOutput = true
dynamicValues = generators . MergeMaps ( dynamicValues , event . OperatorsResult . DynamicValues )
2021-01-01 11:22:41 +00:00
}
2021-03-08 13:31:40 +00:00
if r . ReqCondition {
outputEvent = event . InternalEvent
}
2021-01-01 14:06:21 +00:00
callback ( event )
} )
if err != nil {
requestErr = multierr . Append ( requestErr , err )
2020-12-26 09:25:15 +00:00
}
2021-03-08 13:31:40 +00:00
// Add to history the current request number metadata if asked by the user.
if r . ReqCondition {
for k , v := range outputEvent {
previous [ fmt . Sprintf ( "%s_%d" , k , requestCount ) ] = v
}
}
requestCount ++
2020-12-30 15:44:04 +00:00
r . options . Progress . IncrementRequests ( )
2020-12-26 09:25:15 +00:00
2021-01-01 14:06:21 +00:00
if request . original . options . Options . StopAtFirstMatch && gotOutput {
2021-03-02 01:22:15 +00:00
r . options . Progress . IncrementErrorsBy ( int64 ( generator . Total ( ) ) )
2020-12-26 09:25:15 +00:00
break
}
}
2021-01-01 14:06:21 +00:00
return requestErr
2020-12-26 09:25:15 +00:00
}
2021-02-04 16:39:32 +00:00
const drainReqSize = int64 ( 8 * 1024 )
2021-02-26 07:43:11 +00:00
// executeRequest executes the actual generated request and returns error if occurred
func ( r * Request ) executeRequest ( reqURL string , request * generatedRequest , previous output . InternalEvent , callback protocols . OutputEventCallback ) error {
2020-12-30 15:44:04 +00:00
r . setCustomHeaders ( request )
2020-12-26 09:25:15 +00:00
var (
2021-02-25 01:08:10 +00:00
resp * http . Response
fromcache bool
dumpedRequest [ ] byte
err error
2020-12-26 09:25:15 +00:00
)
2021-02-07 20:13:51 +00:00
2021-03-01 04:18:31 +00:00
// 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-02-25 01:08:10 +00:00
if ! request . original . Race {
dumpedRequest , err = dump ( request , reqURL )
if err != nil {
return err
}
if r . options . Options . Debug || r . options . Options . DebugRequests {
gologger . Info ( ) . Msgf ( "[%s] Dumped HTTP request for %s\n\n" , r . options . TemplateID , reqURL )
gologger . Print ( ) . Msgf ( "%s" , string ( dumpedRequest ) )
}
2020-12-26 09:25:15 +00:00
}
2021-01-11 14:29:12 +00:00
var formedURL string
2021-01-16 06:36:27 +00:00
var hostname string
2020-12-26 09:25:15 +00:00
timeStart := time . Now ( )
2020-12-28 14:32:26 +00:00
if request . original . Pipeline {
2021-01-11 14:29:12 +00:00
formedURL = request . rawRequest . FullURL
2021-02-26 07:43:11 +00:00
if parsed , parseErr := url . Parse ( formedURL ) ; parseErr == nil {
hostname = parsed . Host
2021-01-16 06:36:27 +00:00
}
2020-12-28 20:00:07 +00:00
resp , err = request . pipelinedClient . DoRaw ( request . rawRequest . Method , reqURL , request . rawRequest . Path , generators . ExpandMapValues ( request . rawRequest . Headers ) , ioutil . NopCloser ( strings . NewReader ( request . rawRequest . Data ) ) )
2021-02-08 10:37:16 +00:00
} else if request . original . Unsafe && request . rawRequest != nil {
2021-01-11 14:29:12 +00:00
formedURL = request . rawRequest . FullURL
2021-02-26 07:43:11 +00:00
if parsed , parseErr := url . Parse ( formedURL ) ; parseErr == nil {
hostname = parsed . Host
2021-01-16 06:36:27 +00:00
}
2020-12-28 20:00:07 +00:00
options := request . original . rawhttpClient . Options
2020-12-30 15:44:04 +00:00
options . FollowRedirects = r . Redirects
2021-02-26 07:43:11 +00:00
options . CustomRawBytes = request . rawRequest . UnsafeRawBytes
2020-12-28 20:00:07 +00:00
resp , err = request . original . rawhttpClient . DoRawWithOptions ( request . rawRequest . Method , reqURL , request . rawRequest . Path , generators . ExpandMapValues ( request . rawRequest . Headers ) , ioutil . NopCloser ( strings . NewReader ( request . rawRequest . Data ) ) , options )
2020-12-26 09:25:15 +00:00
} else {
2021-02-26 07:43:11 +00:00
hostname = request . request . URL . Host
2021-01-11 14:29:12 +00:00
formedURL = request . request . URL . String ( )
2020-12-26 09:25:15 +00:00
// if nuclei-project is available check if the request was already sent previously
2020-12-30 15:44:04 +00:00
if r . options . ProjectFile != nil {
2020-12-26 09:25:15 +00:00
// if unavailable fail silently
fromcache = true
2020-12-30 15:44:04 +00:00
resp , err = r . options . ProjectFile . Get ( dumpedRequest )
2020-12-26 09:25:15 +00:00
if err != nil {
fromcache = false
}
}
if resp == nil {
2020-12-30 15:44:04 +00:00
resp , err = r . httpClient . Do ( request . request )
2020-12-26 09:25:15 +00:00
}
}
2021-02-08 10:37:16 +00:00
if resp == nil {
err = errors . New ( "no response got for request" )
}
2020-12-28 20:00:07 +00:00
if err != nil {
2021-01-11 20:35:41 +00:00
// rawhttp doesn't supports draining response bodies.
if resp != nil && resp . Body != nil && request . rawRequest == nil {
2021-02-06 22:04:07 +00:00
_ , _ = io . CopyN ( ioutil . Discard , resp . Body , drainReqSize )
2020-12-28 20:00:07 +00:00
resp . Body . Close ( )
}
2021-03-08 13:31:40 +00:00
r . options . Output . Request ( r . options . TemplateID , formedURL , "http" , err )
2021-03-02 01:22:15 +00:00
r . options . Progress . IncrementErrorsBy ( 1 )
2021-01-01 14:06:21 +00:00
return err
2020-12-28 20:00:07 +00:00
}
2021-02-26 07:43:11 +00:00
defer func ( ) {
_ , _ = io . CopyN ( ioutil . Discard , resp . Body , drainReqSize )
resp . Body . Close ( )
} ( )
2021-02-05 07:06:01 +00:00
2021-01-11 14:29:12 +00:00
gologger . Verbose ( ) . Msgf ( "[%s] Sent HTTP request to %s" , r . options . TemplateID , formedURL )
2021-03-08 13:31:40 +00:00
r . options . Output . Request ( r . options . TemplateID , formedURL , "http" , err )
2020-12-26 09:25:15 +00:00
duration := time . Since ( timeStart )
2021-02-05 07:06:01 +00:00
2021-02-07 20:25:53 +00:00
dumpedResponseHeaders , err := httputil . DumpResponse ( resp , false )
2021-02-05 07:06:01 +00:00
if err != nil {
return errors . Wrap ( err , "could not dump http response" )
2020-12-26 09:25:15 +00:00
}
2021-02-04 16:39:32 +00:00
var bodyReader io . Reader
if r . MaxSize != 0 {
bodyReader = io . LimitReader ( resp . Body , int64 ( r . MaxSize ) )
} else {
bodyReader = resp . Body
}
data , err := ioutil . ReadAll ( bodyReader )
2020-12-26 09:25:15 +00:00
if err != nil {
2021-01-01 14:06:21 +00:00
return errors . Wrap ( err , "could not read http body" )
2020-12-26 09:25:15 +00:00
}
resp . Body . Close ( )
2021-02-06 22:04:07 +00:00
redirectedResponse , err := dumpResponseWithRedirectChain ( resp , data )
if err != nil {
return errors . Wrap ( err , "could not read http response with redirect chain" )
}
2020-12-28 20:00:07 +00:00
// net/http doesn't automatically decompress the response body if an
// encoding has been specified by the user in the request so in case we have to
// manually do it.
2021-02-07 20:25:53 +00:00
dataOrig := data
2021-02-08 10:43:55 +00:00
data , _ = handleDecompression ( resp , data )
2020-12-26 09:25:15 +00:00
// Dump response - step 2 - replace gzip body with deflated one or with itself (NOP operation)
2021-02-07 20:25:53 +00:00
dumpedResponseBuilder := & bytes . Buffer { }
dumpedResponseBuilder . Write ( dumpedResponseHeaders )
dumpedResponseBuilder . Write ( data )
dumpedResponse := dumpedResponseBuilder . Bytes ( )
redirectedResponse = bytes . ReplaceAll ( redirectedResponse , dataOrig , data )
2021-02-06 22:04:07 +00:00
2021-02-07 20:25:53 +00:00
// Dump response - step 2 - replace gzip body with deflated one or with itself (NOP operation)
if r . options . Options . Debug || r . options . Options . DebugResponse {
2021-01-11 14:29:12 +00:00
gologger . Info ( ) . Msgf ( "[%s] Dumped HTTP response for %s\n\n" , r . options . TemplateID , formedURL )
2021-02-06 22:04:07 +00:00
gologger . Print ( ) . Msgf ( "%s" , string ( redirectedResponse ) )
2020-12-26 09:25:15 +00:00
}
// if nuclei-project is enabled store the response if not previously done
2020-12-30 15:44:04 +00:00
if r . options . ProjectFile != nil && ! fromcache {
err := r . options . ProjectFile . Set ( dumpedRequest , resp , data )
2020-12-26 09:25:15 +00:00
if err != nil {
2021-01-01 14:06:21 +00:00
return errors . Wrap ( err , "could not store in project file" )
2020-12-26 09:25:15 +00:00
}
}
2021-02-25 07:07:47 +00:00
matchedURL := reqURL
if request . rawRequest != nil && request . rawRequest . FullURL != "" {
2020-12-29 06:12:46 +00:00
matchedURL = request . rawRequest . FullURL
}
if request . request != nil {
matchedURL = request . request . URL . String ( )
}
2021-01-16 06:36:27 +00:00
outputEvent := r . responseToDSLMap ( resp , reqURL , matchedURL , tostring . UnsafeToString ( dumpedRequest ) , tostring . UnsafeToString ( dumpedResponse ) , tostring . UnsafeToString ( data ) , headersToString ( resp . Header ) , duration , request . meta )
outputEvent [ "ip" ] = httpclientpool . Dialer . GetDialedIP ( hostname )
2021-02-06 22:04:07 +00:00
outputEvent [ "redirect-chain" ] = tostring . UnsafeToString ( redirectedResponse )
2021-01-16 08:40:24 +00:00
for k , v := range previous {
outputEvent [ k ] = v
}
2020-12-28 20:00:07 +00:00
2021-01-16 06:36:27 +00:00
event := & output . InternalWrappedEvent { InternalEvent : outputEvent }
2020-12-30 15:44:04 +00:00
if r . CompiledOperators != nil {
2021-01-17 06:56:45 +00:00
var ok bool
event . OperatorsResult , ok = r . CompiledOperators . Execute ( outputEvent , r . Match , r . Extract )
if ok && event . OperatorsResult != nil {
event . OperatorsResult . PayloadValues = request . meta
2021-01-13 06:48:56 +00:00
event . Results = r . MakeResultEvent ( event )
2020-12-26 09:25:15 +00:00
}
}
2021-01-13 06:48:56 +00:00
callback ( event )
2021-01-01 14:06:21 +00:00
return nil
2020-12-28 20:00:07 +00:00
}
2020-12-26 09:25:15 +00:00
2020-12-28 20:00:07 +00:00
// setCustomHeaders sets the custom headers for generated request
2021-02-04 16:30:09 +00:00
func ( r * Request ) setCustomHeaders ( req * generatedRequest ) {
for k , v := range r . customHeaders {
if req . rawRequest != nil {
req . rawRequest . Headers [ k ] = v
2020-12-28 20:00:07 +00:00
} else {
2021-02-04 16:30:09 +00:00
req . request . Header . Set ( strings . TrimSpace ( k ) , strings . TrimSpace ( v ) )
2020-12-28 20:00:07 +00:00
}
2020-12-26 09:25:15 +00:00
}
}