nuclei/pkg/executor/executer_http.go

226 lines
6.5 KiB
Go
Raw Normal View History

2020-04-26 05:50:33 +05:30
package executor
import (
"bufio"
2020-04-26 05:50:33 +05:30
"crypto/tls"
2020-04-28 22:15:26 +02:00
"fmt"
2020-04-26 05:50:33 +05:30
"io"
"io/ioutil"
"net/http"
2020-04-27 23:49:53 +05:30
"net/url"
"sync"
2020-04-26 05:50:33 +05:30
"time"
"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"
2020-04-28 04:01:25 +02:00
"golang.org/x/net/proxy"
2020-04-26 05:50:33 +05:30
)
// HTTPExecutor is client for performing HTTP requests
// for a template.
type HTTPExecutor struct {
httpClient *retryablehttp.Client
template *templates.Template
httpRequest *requests.HTTPRequest
writer *bufio.Writer
outputMutex *sync.Mutex
}
// HTTPOptions contains configuration options for the HTTP executor.
type HTTPOptions struct {
2020-04-28 04:01:25 +02:00
Template *templates.Template
HTTPRequest *requests.HTTPRequest
Writer *bufio.Writer
Timeout int
Retries int
ProxyURL string
ProxySocksURL string
2020-04-26 05:50:33 +05:30
}
// NewHTTPExecutor creates a new HTTP executor from a template
// and a HTTP request query.
2020-04-27 23:49:53 +05:30
func NewHTTPExecutor(options *HTTPOptions) (*HTTPExecutor, error) {
2020-04-28 00:29:57 +05:30
var proxyURL *url.URL
var err error
2020-04-26 05:50:33 +05:30
2020-04-28 00:29:57 +05:30
if options.ProxyURL != "" {
proxyURL, err = url.Parse(options.ProxyURL)
}
2020-04-27 23:49:53 +05:30
if err != nil {
return nil, err
}
2020-04-26 05:50:33 +05:30
// Create the HTTP Client
2020-04-28 00:29:57 +05:30
client := makeHTTPClient(proxyURL, options)
2020-04-26 05:50:33 +05:30
client.CheckRetry = retryablehttp.HostSprayRetryPolicy()
executer := &HTTPExecutor{
httpClient: client,
template: options.Template,
httpRequest: options.HTTPRequest,
outputMutex: &sync.Mutex{},
writer: options.Writer,
2020-04-26 05:50:33 +05:30
}
2020-04-27 23:49:53 +05:30
return executer, nil
2020-04-26 05:50:33 +05:30
}
// ExecuteHTTP executes the HTTP request on a URL
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 {
return errors.Wrap(err, "could not make http request")
2020-04-26 05:50:33 +05:30
}
// Send the request to the target servers
mainLoop:
for compiledRequest := range compiledRequest {
if compiledRequest.Error != nil {
return errors.Wrap(err, "could not make http request")
}
req := compiledRequest.Request
2020-04-26 05:50:33 +05:30
resp, err := e.httpClient.Do(req)
if err != nil {
if resp != nil {
resp.Body.Close()
}
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()
return errors.Wrap(err, "could not read http body")
2020-04-26 05:50:33 +05:30
}
resp.Body.Close()
// 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
data, err = requests.HandleDecompression(compiledRequest.Request, data)
if err != nil {
return errors.Wrap(err, "could not decompress http body")
}
// Convert response body from []byte to string with zero copy
2020-04-26 05:50:33 +05:30
body := unsafeToString(data)
var headers string
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
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
}
} 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 {
2020-05-14 18:09:36 +02:00
e.writeOutputHTTP(compiledRequest, matcher, nil)
2020-04-26 05:50:33 +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)
}
}
// 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 {
2020-05-14 18:09:36 +02:00
e.writeOutputHTTP(compiledRequest, nil, extractorResults)
}
2020-04-26 05:50:33 +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
}
2020-04-28 00:29:57 +05:30
// makeHTTPClient creates a http client
func makeHTTPClient(proxyURL *url.URL, options *HTTPOptions) *retryablehttp.Client {
retryablehttpOptions := retryablehttp.DefaultOptionsSpraying
retryablehttpOptions.RetryWaitMax = 10 * time.Second
retryablehttpOptions.RetryMax = options.Retries
followRedirects := options.HTTPRequest.Redirects
maxRedirects := options.HTTPRequest.MaxRedirects
transport := &http.Transport{
MaxIdleConnsPerHost: -1,
TLSClientConfig: &tls.Config{
Renegotiation: tls.RenegotiateOnceAsClient,
InsecureSkipVerify: true,
},
DisableKeepAlives: true,
}
2020-04-28 04:01:25 +02:00
// Attempts to overwrite the dial function with the socks proxied version
if options.ProxySocksURL != "" {
2020-04-28 22:15:26 +02:00
var proxyAuth *proxy.Auth
socksURL, err := url.Parse(options.ProxySocksURL)
if err == nil {
proxyAuth = &proxy.Auth{}
proxyAuth.User = socksURL.User.Username()
proxyAuth.Password, _ = socksURL.User.Password()
}
dialer, err := proxy.SOCKS5("tcp", fmt.Sprintf("%s:%s", socksURL.Hostname(), socksURL.Port()), proxyAuth, proxy.Direct)
2020-04-28 04:01:25 +02:00
if err == nil {
transport.Dial = dialer.Dial
}
}
2020-04-28 00:29:57 +05:30
if proxyURL != nil {
transport.Proxy = http.ProxyURL(proxyURL)
}
return retryablehttp.NewWithHTTPClient(&http.Client{
Transport: transport,
Timeout: time.Duration(options.Timeout) * time.Second,
CheckRedirect: makeCheckRedirectFunc(followRedirects, maxRedirects),
}, retryablehttpOptions)
}
type checkRedirectFunc func(_ *http.Request, requests []*http.Request) error
func makeCheckRedirectFunc(followRedirects bool, maxRedirects int) checkRedirectFunc {
return func(_ *http.Request, requests []*http.Request) error {
if !followRedirects {
return http.ErrUseLastResponse
}
if maxRedirects == 0 {
if len(requests) > 10 {
return http.ErrUseLastResponse
}
return nil
}
if len(requests) > maxRedirects {
return http.ErrUseLastResponse
}
return nil
}
}