2020-04-26 05:50:33 +05:30
|
|
|
package executor
|
|
|
|
|
|
|
|
|
|
import (
|
2020-04-26 06:33:59 +05:30
|
|
|
"bufio"
|
2020-04-26 05:50:33 +05:30
|
|
|
"crypto/tls"
|
|
|
|
|
"io"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"net/http"
|
2020-04-26 06:33:59 +05:30
|
|
|
"sync"
|
2020-04-26 05:50:33 +05:30
|
|
|
"time"
|
|
|
|
|
|
2020-04-26 06:33:59 +05:30
|
|
|
"github.com/pkg/errors"
|
2020-04-26 05:50:33 +05:30
|
|
|
"github.com/projectdiscovery/nuclei/pkg/extractors"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/pkg/matchers"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/pkg/requests"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/pkg/templates"
|
|
|
|
|
"github.com/projectdiscovery/retryablehttp-go"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// HTTPExecutor is client for performing HTTP requests
|
|
|
|
|
// for a template.
|
|
|
|
|
type HTTPExecutor struct {
|
|
|
|
|
httpClient *retryablehttp.Client
|
|
|
|
|
template *templates.Template
|
|
|
|
|
httpRequest *requests.HTTPRequest
|
2020-04-26 06:33:59 +05:30
|
|
|
writer *bufio.Writer
|
|
|
|
|
outputMutex *sync.Mutex
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HTTPOptions contains configuration options for the HTTP executor.
|
|
|
|
|
type HTTPOptions struct {
|
|
|
|
|
Template *templates.Template
|
|
|
|
|
HTTPRequest *requests.HTTPRequest
|
|
|
|
|
Writer *bufio.Writer
|
|
|
|
|
Timeout int
|
|
|
|
|
Retries int
|
2020-04-26 05:50:33 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewHTTPExecutor creates a new HTTP executor from a template
|
|
|
|
|
// and a HTTP request query.
|
2020-04-26 06:33:59 +05:30
|
|
|
func NewHTTPExecutor(options *HTTPOptions) *HTTPExecutor {
|
2020-04-26 05:50:33 +05:30
|
|
|
retryablehttpOptions := retryablehttp.DefaultOptionsSpraying
|
|
|
|
|
retryablehttpOptions.RetryWaitMax = 10 * time.Second
|
2020-04-26 06:33:59 +05:30
|
|
|
retryablehttpOptions.RetryMax = options.Retries
|
|
|
|
|
followRedirects := options.HTTPRequest.Redirects
|
|
|
|
|
maxRedirects := options.HTTPRequest.MaxRedirects
|
2020-04-26 05:50:33 +05:30
|
|
|
|
|
|
|
|
// Create the HTTP Client
|
|
|
|
|
client := retryablehttp.NewWithHTTPClient(&http.Client{
|
|
|
|
|
Transport: &http.Transport{
|
|
|
|
|
MaxIdleConnsPerHost: -1,
|
|
|
|
|
TLSClientConfig: &tls.Config{
|
|
|
|
|
Renegotiation: tls.RenegotiateOnceAsClient,
|
|
|
|
|
InsecureSkipVerify: true,
|
|
|
|
|
},
|
|
|
|
|
DisableKeepAlives: true,
|
|
|
|
|
},
|
2020-04-26 06:33:59 +05:30
|
|
|
Timeout: time.Duration(options.Timeout) * time.Second,
|
2020-04-26 05:50:33 +05:30
|
|
|
CheckRedirect: func(_ *http.Request, requests []*http.Request) error {
|
2020-04-26 06:33:59 +05:30
|
|
|
if !followRedirects {
|
2020-04-26 05:50:33 +05:30
|
|
|
return http.ErrUseLastResponse
|
|
|
|
|
}
|
2020-04-26 06:33:59 +05:30
|
|
|
if maxRedirects == 0 {
|
2020-04-26 05:50:33 +05:30
|
|
|
if len(requests) > 10 {
|
|
|
|
|
return http.ErrUseLastResponse
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2020-04-26 06:33:59 +05:30
|
|
|
if len(requests) > maxRedirects {
|
2020-04-26 05:50:33 +05:30
|
|
|
return http.ErrUseLastResponse
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
}, retryablehttpOptions)
|
|
|
|
|
client.CheckRetry = retryablehttp.HostSprayRetryPolicy()
|
|
|
|
|
|
|
|
|
|
executer := &HTTPExecutor{
|
|
|
|
|
httpClient: client,
|
2020-04-26 06:33:59 +05:30
|
|
|
template: options.Template,
|
|
|
|
|
httpRequest: options.HTTPRequest,
|
|
|
|
|
outputMutex: &sync.Mutex{},
|
|
|
|
|
writer: options.Writer,
|
2020-04-26 05:50:33 +05:30
|
|
|
}
|
|
|
|
|
return executer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ExecuteHTTP executes the HTTP request on a URL
|
2020-04-26 06:33:59 +05:30
|
|
|
func (e *HTTPExecutor) ExecuteHTTP(URL string) error {
|
2020-04-26 05:50:33 +05:30
|
|
|
// Compile each request for the template based on the URL
|
|
|
|
|
compiledRequest, err := e.httpRequest.MakeHTTPRequest(URL)
|
|
|
|
|
if err != nil {
|
2020-04-26 06:33:59 +05:30
|
|
|
return errors.Wrap(err, "could not make http request")
|
2020-04-26 05:50:33 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send the request to the target servers
|
2020-04-26 06:33:59 +05:30
|
|
|
mainLoop:
|
2020-04-26 05:50:33 +05:30
|
|
|
for _, req := range compiledRequest {
|
|
|
|
|
resp, err := e.httpClient.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if resp != nil {
|
|
|
|
|
resp.Body.Close()
|
|
|
|
|
}
|
2020-04-26 06:33:59 +05:30
|
|
|
return errors.Wrap(err, "could not make http request")
|
2020-04-26 05:50:33 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data, err := ioutil.ReadAll(resp.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
io.Copy(ioutil.Discard, resp.Body)
|
|
|
|
|
resp.Body.Close()
|
2020-04-26 06:33:59 +05:30
|
|
|
return errors.Wrap(err, "could not read http body")
|
2020-04-26 05:50:33 +05:30
|
|
|
}
|
|
|
|
|
resp.Body.Close()
|
|
|
|
|
|
2020-04-26 06:33:59 +05:30
|
|
|
// Convert response body from []byte to string with zero copy
|
2020-04-26 05:50:33 +05:30
|
|
|
body := unsafeToString(data)
|
|
|
|
|
|
|
|
|
|
var headers string
|
2020-04-26 06:33:59 +05:30
|
|
|
matcherCondition := e.httpRequest.GetMatchersCondition()
|
2020-04-26 05:50:33 +05:30
|
|
|
for _, matcher := range e.httpRequest.Matchers {
|
|
|
|
|
// Only build the headers string if the matcher asks for it
|
|
|
|
|
part := matcher.GetPart()
|
|
|
|
|
if part == matchers.AllPart || part == matchers.HeaderPart && headers == "" {
|
|
|
|
|
headers = headersToString(resp.Header)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if the matcher matched
|
2020-04-26 06:33:59 +05:30
|
|
|
if !matcher.Match(resp, body, headers) {
|
|
|
|
|
// If the condition is AND we haven't matched, try next request.
|
|
|
|
|
if matcherCondition == matchers.ANDCondition {
|
2020-04-26 07:02:38 +05:30
|
|
|
continue mainLoop
|
2020-04-26 06:33:59 +05:30
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// If the matcher has matched, and its an OR
|
|
|
|
|
// write the first output then move to next matcher.
|
|
|
|
|
if matcherCondition == matchers.ORCondition && len(e.httpRequest.Extractors) == 0 {
|
|
|
|
|
e.writeOutputHTTP(req, matcher, nil)
|
2020-04-26 05:50:33 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-26 06:33:59 +05:30
|
|
|
|
|
|
|
|
// All matchers have successfully completed so now start with the
|
|
|
|
|
// next task which is extraction of input from matchers.
|
|
|
|
|
var extractorResults []string
|
|
|
|
|
for _, extractor := range e.httpRequest.Extractors {
|
|
|
|
|
part := extractor.GetPart()
|
|
|
|
|
if part == extractors.AllPart || part == extractors.HeaderPart && headers == "" {
|
|
|
|
|
headers = headersToString(resp.Header)
|
|
|
|
|
}
|
2020-04-27 23:34:08 +05:30
|
|
|
for match := range extractor.Extract(body, headers) {
|
|
|
|
|
extractorResults = append(extractorResults, match)
|
|
|
|
|
}
|
2020-04-26 06:33:59 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write a final string of output if matcher type is
|
|
|
|
|
// AND or if we have extractors for the mechanism too.
|
|
|
|
|
if len(e.httpRequest.Extractors) > 0 || matcherCondition == matchers.ANDCondition {
|
|
|
|
|
e.writeOutputHTTP(req, nil, extractorResults)
|
|
|
|
|
}
|
2020-04-26 05:50:33 +05:30
|
|
|
}
|
2020-04-26 06:33:59 +05:30
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close closes the http executor for a template.
|
|
|
|
|
func (e *HTTPExecutor) Close() {
|
|
|
|
|
e.outputMutex.Lock()
|
|
|
|
|
e.writer.Flush()
|
|
|
|
|
e.outputMutex.Unlock()
|
2020-04-26 05:50:33 +05:30
|
|
|
}
|