2020-04-04 00:17:57 +05:30
package requests
import (
2020-04-28 23:02:07 +02:00
"bufio"
2020-08-25 23:24:31 +02:00
"context"
2020-04-29 02:57:18 +02:00
"fmt"
2020-04-04 02:50:32 +05:30
"io/ioutil"
2020-09-05 14:44:47 +02:00
"net"
2020-04-04 02:50:32 +05:30
"net/http"
2020-04-04 03:26:11 +05:30
"net/url"
2020-05-04 23:24:59 +02:00
"regexp"
2020-04-04 02:50:32 +05:30
"strings"
2020-05-04 23:24:59 +02:00
"github.com/Knetic/govaluate"
2020-07-01 16:17:24 +05:30
"github.com/projectdiscovery/nuclei/v2/pkg/extractors"
"github.com/projectdiscovery/nuclei/v2/pkg/generators"
"github.com/projectdiscovery/nuclei/v2/pkg/matchers"
2020-04-04 17:12:29 +05:30
retryablehttp "github.com/projectdiscovery/retryablehttp-go"
2020-04-04 00:17:57 +05:30
)
2020-08-25 23:24:31 +02:00
const (
two = 2
three = 3
)
2020-09-05 14:44:47 +02:00
var urlWithPortRgx = regexp . MustCompile ( ` {{ BaseURL }} :(\d+) ` )
2020-07-18 21:42:23 +02:00
// BulkHTTPRequest contains a request to be made from a template
type BulkHTTPRequest struct {
2020-08-25 23:24:31 +02:00
// CookieReuse is an optional setting that makes cookies shared within requests
CookieReuse bool ` yaml:"cookie-reuse,omitempty" `
// Redirects specifies whether redirects should be followed.
Redirects bool ` yaml:"redirects,omitempty" `
Name string ` yaml:"Name,omitempty" `
2020-05-04 23:24:59 +02:00
// AttackType is the attack type
// Sniper, PitchFork and ClusterBomb. Default is Sniper
AttackType string ` yaml:"attack,omitempty" `
// attackType is internal attack type
attackType generators . Type
// Path contains the path/s for the request variables
2020-07-13 03:30:07 +02:00
Payloads map [ string ] interface { } ` yaml:"payloads,omitempty" `
2020-04-04 00:17:57 +05:30
// Method is the request method, whether GET, POST, PUT, etc
Method string ` yaml:"method" `
// Path contains the path/s for the request
Path [ ] string ` yaml:"path" `
// Headers contains headers to send with the request
Headers map [ string ] string ` yaml:"headers,omitempty" `
// Body is an optional parameter which contains the request body for POST methods, etc
Body string ` yaml:"body,omitempty" `
// Matchers contains the detection mechanism for the request to identify
// whether the request was successful
2020-04-05 23:58:22 +05:30
Matchers [ ] * matchers . Matcher ` yaml:"matchers,omitempty" `
2020-04-26 05:50:33 +05:30
// MatchersCondition is the condition of the matchers
// whether to use AND or OR. Default is OR.
MatchersCondition string ` yaml:"matchers-condition,omitempty" `
2020-04-26 06:33:59 +05:30
// matchersCondition is internal condition for the matchers.
matchersCondition matchers . ConditionType
2020-04-05 23:58:22 +05:30
// Extractors contains the extraction mechanism for the request to identify
// and extract parts of the response.
2020-04-06 00:44:45 +05:30
Extractors [ ] * extractors . Extractor ` yaml:"extractors,omitempty" `
2020-04-23 03:56:41 +05:30
// MaxRedirects is the maximum number of redirects that should be followed.
MaxRedirects int ` yaml:"max-redirects,omitempty" `
2020-04-29 21:19:35 +02:00
// Raw contains raw requests
2020-09-28 02:17:35 +02:00
Raw [ ] string ` yaml:"raw,omitempty" `
// Specify in order to skip request RFC normalization
Unsafe bool ` yaml:"unsafe,omitempty" `
2020-10-05 22:19:48 +02:00
// DisableAutoHostname Enable/Disable Host header for unsafe raw requests
DisableAutoHostname bool ` yaml:"disable-automatic-host-header,omitempty" `
// DisableAutoContentLength Enable/Disable Content-Length header for unsafe raw requests
DisableAutoContentLength bool ` yaml:"disable-automatic-content-length-header,omitempty" `
2020-09-28 02:17:35 +02:00
// Internal Finite State Machine keeping track of scan process
2020-07-24 13:37:01 +02:00
gsfm * GeneratorFSM
2020-04-04 00:17:57 +05:30
}
2020-04-04 02:50:32 +05:30
2020-04-26 06:33:59 +05:30
// GetMatchersCondition returns the condition for the matcher
2020-07-18 21:42:23 +02:00
func ( r * BulkHTTPRequest ) GetMatchersCondition ( ) matchers . ConditionType {
2020-04-26 06:33:59 +05:30
return r . matchersCondition
}
// SetMatchersCondition sets the condition for the matcher
2020-07-18 21:42:23 +02:00
func ( r * BulkHTTPRequest ) SetMatchersCondition ( condition matchers . ConditionType ) {
2020-04-26 06:33:59 +05:30
r . matchersCondition = condition
}
2020-05-04 23:24:59 +02:00
// GetAttackType returns the attack
2020-07-18 21:42:23 +02:00
func ( r * BulkHTTPRequest ) GetAttackType ( ) generators . Type {
2020-05-04 23:24:59 +02:00
return r . attackType
}
// SetAttackType sets the attack
2020-07-18 21:42:23 +02:00
func ( r * BulkHTTPRequest ) SetAttackType ( attack generators . Type ) {
2020-05-04 23:24:59 +02:00
r . attackType = attack
}
2020-09-05 14:44:47 +02:00
// GetRequestCount returns the total number of requests the YAML rule will perform
2020-07-23 20:19:19 +02:00
func ( r * BulkHTTPRequest ) GetRequestCount ( ) int64 {
return int64 ( len ( r . Raw ) | len ( r . Path ) )
2020-07-05 17:17:04 +02:00
}
2020-09-05 14:44:47 +02:00
// MakeHTTPRequest makes the HTTP request
2020-08-25 23:24:31 +02:00
func ( r * BulkHTTPRequest ) MakeHTTPRequest ( ctx context . Context , baseURL string , dynamicValues map [ string ] interface { } , data string ) ( * HTTPRequest , error ) {
2020-04-04 03:26:11 +05:30
parsed , err := url . Parse ( baseURL )
if err != nil {
return nil , err
}
2020-08-25 23:24:31 +02:00
2020-07-20 00:41:31 +02:00
hostname := parsed . Host
2020-04-04 02:50:32 +05:30
2020-07-19 03:14:19 +02:00
values := generators . MergeMaps ( dynamicValues , map [ string ] interface { } {
2020-09-05 14:44:47 +02:00
"BaseURL" : baseURLWithTemplatePrefs ( data , parsed ) ,
2020-04-29 02:57:18 +02:00
"Hostname" : hostname ,
2020-07-19 03:14:19 +02:00
} )
2020-04-29 02:57:18 +02:00
2020-07-18 21:42:23 +02:00
// if data contains \n it's a raw request
if strings . Contains ( data , "\n" ) {
2020-08-25 23:24:31 +02:00
return r . makeHTTPRequestFromRaw ( ctx , baseURL , data , values )
2020-04-29 02:57:18 +02:00
}
2020-08-25 23:24:31 +02:00
return r . makeHTTPRequestFromModel ( ctx , data , values )
2020-04-29 02:57:18 +02:00
}
// MakeHTTPRequestFromModel creates a *http.Request from a request template
2020-08-25 23:24:31 +02:00
func ( r * BulkHTTPRequest ) makeHTTPRequestFromModel ( ctx context . Context , data string , values map [ string ] interface { } ) ( * HTTPRequest , error ) {
2020-07-18 21:42:23 +02:00
replacer := newReplacer ( values )
URL := replacer . Replace ( data )
2020-05-04 23:24:59 +02:00
2020-07-18 21:42:23 +02:00
// Build a request on the specified URL
2020-08-25 23:24:31 +02:00
req , err := http . NewRequestWithContext ( ctx , r . Method , URL , nil )
2020-07-18 21:42:23 +02:00
if err != nil {
return nil , err
}
2020-05-04 23:24:59 +02:00
2020-07-18 21:42:23 +02:00
request , err := r . fillRequest ( req , values )
if err != nil {
return nil , err
}
2020-04-04 02:50:32 +05:30
2020-08-25 23:24:31 +02:00
return & HTTPRequest { Request : request } , nil
2020-04-04 02:50:32 +05:30
}
2020-04-28 23:02:07 +02:00
2020-09-05 14:44:47 +02:00
// InitGenerator initializes the generator
2020-07-24 18:12:16 +02:00
func ( r * BulkHTTPRequest ) InitGenerator ( ) {
r . gsfm = NewGeneratorFSM ( r . attackType , r . Payloads , r . Path , r . Raw )
2020-05-05 21:42:28 +02:00
}
2020-05-04 23:24:59 +02:00
2020-09-05 14:44:47 +02:00
// CreateGenerator creates the generator
2020-08-25 23:24:31 +02:00
func ( r * BulkHTTPRequest ) CreateGenerator ( reqURL string ) {
r . gsfm . Add ( reqURL )
2020-07-18 21:42:23 +02:00
}
2020-04-04 02:50:32 +05:30
2020-09-05 14:44:47 +02:00
// HasGenerator check if an URL has a generator
2020-08-25 23:24:31 +02:00
func ( r * BulkHTTPRequest ) HasGenerator ( reqURL string ) bool {
return r . gsfm . Has ( reqURL )
2020-07-25 22:25:21 +02:00
}
2020-09-05 14:44:47 +02:00
// ReadOne reads and return a generator by URL
2020-08-25 23:24:31 +02:00
func ( r * BulkHTTPRequest ) ReadOne ( reqURL string ) {
r . gsfm . ReadOne ( reqURL )
2020-04-04 02:50:32 +05:30
}
2020-05-04 23:24:59 +02:00
2020-04-28 23:02:07 +02:00
// makeHTTPRequestFromRaw creates a *http.Request from a raw request
2020-08-25 23:24:31 +02:00
func ( r * BulkHTTPRequest ) makeHTTPRequestFromRaw ( ctx context . Context , baseURL , data string , values map [ string ] interface { } ) ( * HTTPRequest , error ) {
2020-07-18 21:42:23 +02:00
// Add trailing line
data += "\n"
2020-08-25 23:24:31 +02:00
2020-07-18 21:42:23 +02:00
if len ( r . Payloads ) > 0 {
2020-07-24 13:37:01 +02:00
r . gsfm . InitOrSkip ( baseURL )
r . ReadOne ( baseURL )
2020-08-25 23:24:31 +02:00
return r . handleRawWithPaylods ( ctx , data , baseURL , values , r . gsfm . Value ( baseURL ) )
2020-05-05 21:42:28 +02:00
}
2020-05-04 23:24:59 +02:00
2020-07-18 21:42:23 +02:00
// otherwise continue with normal flow
2020-08-25 23:24:31 +02:00
return r . handleRawWithPaylods ( ctx , data , baseURL , values , nil )
2020-05-05 21:42:28 +02:00
}
2020-05-04 23:24:59 +02:00
2020-08-25 23:24:31 +02:00
func ( r * BulkHTTPRequest ) handleRawWithPaylods ( ctx context . Context , raw , baseURL string , values , genValues map [ string ] interface { } ) ( * HTTPRequest , error ) {
2020-05-05 21:42:28 +02:00
baseValues := generators . CopyMap ( values )
finValues := generators . MergeMaps ( baseValues , genValues )
2020-05-04 23:24:59 +02:00
2020-05-05 21:42:28 +02:00
replacer := newReplacer ( finValues )
// Replace the dynamic variables in the URL if any
raw = replacer . Replace ( raw )
2020-05-04 23:24:59 +02:00
2020-05-05 21:42:28 +02:00
dynamicValues := make ( map [ string ] interface { } )
// find all potentials tokens between {{}}
var re = regexp . MustCompile ( ` (?m)\ { \ { .+}} ` )
for _ , match := range re . FindAllString ( raw , - 1 ) {
// check if the match contains a dynamic variable
2020-07-20 01:37:07 +02:00
expr := generators . TrimDelimiters ( match )
compiled , err := govaluate . NewEvaluableExpressionWithFunctions ( expr , generators . HelperFunctions ( ) )
2020-08-25 23:24:31 +02:00
2020-07-20 01:37:07 +02:00
if err != nil {
return nil , err
}
2020-08-25 23:24:31 +02:00
2020-07-20 01:37:07 +02:00
result , err := compiled . Evaluate ( finValues )
if err != nil {
return nil , err
2020-05-04 23:24:59 +02:00
}
2020-08-25 23:24:31 +02:00
2020-07-20 01:37:07 +02:00
dynamicValues [ expr ] = result
2020-05-05 21:42:28 +02:00
}
2020-04-28 23:02:07 +02:00
2020-05-05 21:42:28 +02:00
// replace dynamic values
dynamicReplacer := newReplacer ( dynamicValues )
raw = dynamicReplacer . Replace ( raw )
2020-09-28 02:17:35 +02:00
rawRequest , err := r . parseRawRequest ( raw , baseURL )
2020-05-05 21:42:28 +02:00
if err != nil {
2020-07-18 21:42:23 +02:00
return nil , err
2020-05-05 21:42:28 +02:00
}
2020-09-28 02:17:35 +02:00
// rawhttp
if r . Unsafe {
2020-10-05 22:19:48 +02:00
rawRequest . AutomaticContentLength = ! r . DisableAutoContentLength
rawRequest . AutomaticHostHeader = ! r . DisableAutoHostname
2020-09-28 02:17:35 +02:00
return & HTTPRequest { RawRequest : rawRequest , Meta : genValues } , nil
}
// retryablehttp
req , err := http . NewRequestWithContext ( ctx , rawRequest . Method , rawRequest . FullURL , strings . NewReader ( rawRequest . Data ) )
2020-05-05 21:42:28 +02:00
if err != nil {
2020-07-18 21:42:23 +02:00
return nil , err
2020-05-05 21:42:28 +02:00
}
// copy headers
2020-09-28 02:17:35 +02:00
for key , value := range rawRequest . Headers {
2020-07-17 16:04:13 +02:00
req . Header [ key ] = [ ] string { value }
2020-06-29 19:50:11 +05:30
}
2020-05-05 21:42:28 +02:00
request , err := r . fillRequest ( req , values )
if err != nil {
2020-07-18 21:42:23 +02:00
return nil , err
2020-05-05 21:42:28 +02:00
}
2020-08-25 23:24:31 +02:00
return & HTTPRequest { Request : request , Meta : genValues } , nil
2020-04-29 02:57:18 +02:00
}
2020-04-28 23:02:07 +02:00
2020-07-18 21:42:23 +02:00
func ( r * BulkHTTPRequest ) fillRequest ( req * http . Request , values map [ string ] interface { } ) ( * retryablehttp . Request , error ) {
2020-04-30 17:39:33 +02:00
replacer := newReplacer ( values )
2020-07-04 14:34:41 +07:00
2020-04-29 02:57:18 +02:00
// Check if the user requested a request body
if r . Body != "" {
req . Body = ioutil . NopCloser ( strings . NewReader ( r . Body ) )
}
2020-04-28 23:02:07 +02:00
2020-04-29 02:57:18 +02:00
// Set the header values requested
for header , value := range r . Headers {
2020-07-17 16:04:13 +02:00
req . Header [ header ] = [ ] string { replacer . Replace ( value ) }
2020-04-29 02:57:18 +02:00
}
2020-04-28 23:02:07 +02:00
2020-10-06 20:39:42 +02:00
// if the user specified a Connection header we don't alter it
if req . Header . Get ( "Connection" ) == "" {
// Otherwise we set it to "Connection: close" - The instruction is redundant, but it ensures that internally net/http don't miss the header/internal flag
setHeader ( req , "Connection" , "close" )
req . Close = true
}
2020-08-29 16:07:05 +02:00
setHeader ( req , "User-Agent" , "Nuclei - Open-source project (github.com/projectdiscovery/nuclei)" )
2020-04-29 23:07:19 +02:00
2020-07-04 14:34:41 +07:00
// raw requests are left untouched
if len ( r . Raw ) > 0 {
return retryablehttp . FromRequest ( req )
}
2020-08-29 16:07:05 +02:00
setHeader ( req , "Accept" , "*/*" )
setHeader ( req , "Accept-Language" , "en" )
2020-04-28 23:02:07 +02:00
2020-04-29 02:57:18 +02:00
return retryablehttp . FromRequest ( req )
2020-04-28 23:02:07 +02:00
}
2020-05-05 21:42:28 +02:00
2020-09-05 14:44:47 +02:00
// HTTPRequest is the basic HTTP request
2020-08-25 23:24:31 +02:00
type HTTPRequest struct {
2020-09-28 02:17:35 +02:00
Request * retryablehttp . Request
RawRequest * RawRequest
Meta map [ string ] interface { }
2020-05-05 21:42:28 +02:00
}
2020-05-22 00:23:38 +02:00
2020-09-05 14:44:47 +02:00
func setHeader ( req * http . Request , name , value string ) {
// Set some headers only if the header wasn't supplied by the user
if _ , ok := req . Header [ name ] ; ! ok {
req . Header . Set ( name , value )
}
}
// baseURLWithTemplatePrefs returns the url for BaseURL keeping
// the template port and path preference
2020-09-05 18:27:02 +02:00
func baseURLWithTemplatePrefs ( data string , parsedURL * url . URL ) string {
2020-09-05 14:44:47 +02:00
// template port preference over input URL port
hasPort := len ( urlWithPortRgx . FindStringSubmatch ( data ) ) > 0
if hasPort {
hostname , _ , _ := net . SplitHostPort ( parsedURL . Host )
parsedURL . Host = hostname
}
return parsedURL . String ( )
}
2020-05-22 00:23:38 +02:00
// CustomHeaders valid for all requests
type CustomHeaders [ ] string
// String returns just a label
func ( c * CustomHeaders ) String ( ) string {
return "Custom Global Headers"
}
// Set a new global header
func ( c * CustomHeaders ) Set ( value string ) error {
* c = append ( * c , value )
return nil
}
2020-06-29 19:50:11 +05:30
2020-09-05 14:44:47 +02:00
// RawRequest defines a basic HTTP raw request
2020-07-18 21:42:23 +02:00
type RawRequest struct {
2020-10-05 22:19:48 +02:00
FullURL string
Method string
Path string
Data string
Headers map [ string ] string
AutomaticHostHeader bool
AutomaticContentLength bool
2020-06-29 19:50:11 +05:30
}
// parseRawRequest parses the raw request as supplied by the user
2020-08-25 23:24:31 +02:00
func ( r * BulkHTTPRequest ) parseRawRequest ( request , baseURL string ) ( * RawRequest , error ) {
2020-06-29 19:50:11 +05:30
reader := bufio . NewReader ( strings . NewReader ( request ) )
2020-07-18 21:42:23 +02:00
rawRequest := RawRequest {
2020-06-29 19:50:11 +05:30
Headers : make ( map [ string ] string ) ,
}
s , err := reader . ReadString ( '\n' )
if err != nil {
return nil , fmt . Errorf ( "could not read request: %s" , err )
}
2020-08-25 23:24:31 +02:00
2020-06-29 19:50:11 +05:30
parts := strings . Split ( s , " " )
2020-08-25 23:24:31 +02:00
if len ( parts ) < three {
2020-06-29 19:50:11 +05:30
return nil , fmt . Errorf ( "malformed request supplied" )
}
// Set the request Method
rawRequest . Method = parts [ 0 ]
2020-09-28 02:17:35 +02:00
// Accepts all malformed headers
var key , value string
2020-06-29 19:50:11 +05:30
for {
2020-08-25 23:24:31 +02:00
line , readErr := reader . ReadString ( '\n' )
2020-06-29 19:50:11 +05:30
line = strings . TrimSpace ( line )
2020-08-25 23:24:31 +02:00
if readErr != nil || line == "" {
2020-06-29 19:50:11 +05:30
break
}
2020-08-25 23:24:31 +02:00
p := strings . SplitN ( line , ":" , two )
2020-09-28 02:17:35 +02:00
key = p [ 0 ]
if len ( p ) > 1 {
value = p [ 1 ]
2020-06-29 19:50:11 +05:30
}
2020-09-28 02:17:35 +02:00
rawRequest . Headers [ key ] = value
2020-06-29 19:50:11 +05:30
}
// Handle case with the full http url in path. In that case,
// ignore any host header that we encounter and use the path as request URL
if strings . HasPrefix ( parts [ 1 ] , "http" ) {
2020-08-25 23:24:31 +02:00
parsed , parseErr := url . Parse ( parts [ 1 ] )
if parseErr != nil {
return nil , fmt . Errorf ( "could not parse request URL: %s" , parseErr )
2020-06-29 19:50:11 +05:30
}
2020-08-25 23:24:31 +02:00
2020-06-29 19:50:11 +05:30
rawRequest . Path = parts [ 1 ]
rawRequest . Headers [ "Host" ] = parsed . Host
} else {
rawRequest . Path = parts [ 1 ]
}
2020-07-04 14:34:41 +07:00
// If raw request doesn't have a Host header and/ path,
// this will be generated from the parsed baseURL
parsedURL , err := url . Parse ( baseURL )
if err != nil {
return nil , fmt . Errorf ( "could not parse request URL: %s" , err )
}
var hostURL string
2020-08-25 23:24:31 +02:00
if rawRequest . Headers [ "Host" ] == "" {
2020-07-04 14:34:41 +07:00
hostURL = parsedURL . Host
} else {
hostURL = rawRequest . Headers [ "Host" ]
}
2020-08-25 23:24:31 +02:00
if rawRequest . Path == "" {
2020-07-04 14:34:41 +07:00
rawRequest . Path = parsedURL . Path
2020-08-25 23:24:31 +02:00
} else if strings . HasPrefix ( rawRequest . Path , "?" ) {
2020-07-04 14:34:41 +07:00
// requests generated from http.ReadRequest have incorrect RequestURI, so they
// cannot be used to perform another request directly, we need to generate a new one
// with the new target url
2020-08-25 23:24:31 +02:00
rawRequest . Path = fmt . Sprintf ( "%s%s" , parsedURL . Path , rawRequest . Path )
2020-07-04 14:34:41 +07:00
}
2020-09-28 02:17:35 +02:00
rawRequest . FullURL = fmt . Sprintf ( "%s://%s%s" , parsedURL . Scheme , strings . TrimSpace ( hostURL ) , rawRequest . Path )
2020-07-04 14:34:41 +07:00
2020-06-29 19:50:11 +05:30
// Set the request body
b , err := ioutil . ReadAll ( reader )
if err != nil {
return nil , fmt . Errorf ( "could not read request body: %s" , err )
}
2020-08-25 23:24:31 +02:00
2020-06-29 19:50:11 +05:30
rawRequest . Data = string ( b )
2020-08-25 23:24:31 +02:00
2020-06-29 19:50:11 +05:30
return & rawRequest , nil
}
2020-07-18 21:42:23 +02:00
2020-09-05 14:44:47 +02:00
// Next returns the next generator by URL
2020-08-25 23:24:31 +02:00
func ( r * BulkHTTPRequest ) Next ( reqURL string ) bool {
return r . gsfm . Next ( reqURL )
2020-07-18 21:42:23 +02:00
}
2020-09-05 14:44:47 +02:00
2020-09-19 22:26:59 +02:00
// Position returns the current generator's position by URL
2020-08-25 23:24:31 +02:00
func ( r * BulkHTTPRequest ) Position ( reqURL string ) int {
return r . gsfm . Position ( reqURL )
2020-07-18 21:42:23 +02:00
}
2020-07-23 20:19:19 +02:00
2020-09-05 14:44:47 +02:00
// Reset resets the generator by URL
2020-08-25 23:24:31 +02:00
func ( r * BulkHTTPRequest ) Reset ( reqURL string ) {
r . gsfm . Reset ( reqURL )
2020-07-18 21:42:23 +02:00
}
2020-09-05 14:44:47 +02:00
// Current returns the current generator by URL
2020-08-25 23:24:31 +02:00
func ( r * BulkHTTPRequest ) Current ( reqURL string ) string {
return r . gsfm . Current ( reqURL )
2020-07-18 21:42:23 +02:00
}
2020-07-23 23:28:34 +02:00
2020-09-05 14:44:47 +02:00
// Total is the total number of requests
2020-07-18 21:42:23 +02:00
func ( r * BulkHTTPRequest ) Total ( ) int {
return len ( r . Path ) + len ( r . Raw )
}
2020-09-05 14:44:47 +02:00
// Increment increments the processed request
2020-08-25 23:24:31 +02:00
func ( r * BulkHTTPRequest ) Increment ( reqURL string ) {
r . gsfm . Increment ( reqURL )
2020-07-18 21:42:23 +02:00
}