nuclei/v2/pkg/protocols/http/build_request.go

285 lines
8.8 KiB
Go
Raw Normal View History

2020-12-25 20:39:16 +00:00
package http
import (
2020-12-27 20:03:50 +00:00
"context"
2021-08-13 14:38:18 +00:00
"fmt"
2020-12-27 20:03:50 +00:00
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
2020-12-25 20:39:16 +00:00
"regexp"
2020-12-27 20:03:50 +00:00
"strings"
"time"
2021-04-13 06:27:36 +00:00
"github.com/corpix/uarand"
"github.com/pkg/errors"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
2020-12-27 20:03:50 +00:00
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/race"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/raw"
"github.com/projectdiscovery/rawhttp"
2020-12-27 20:03:50 +00:00
"github.com/projectdiscovery/retryablehttp-go"
2020-12-25 20:39:16 +00:00
)
2020-12-27 20:03:50 +00:00
var (
urlWithPortRegex = regexp.MustCompile(`{{BaseURL}}:(\d+)`)
2020-12-27 20:03:50 +00:00
)
2020-12-27 20:03:50 +00:00
// generatedRequest is a single wrapped generated request for a template request
type generatedRequest struct {
original *Request
rawRequest *raw.Request
meta map[string]interface{}
pipelinedClient *rawhttp.PipelineClient
request *retryablehttp.Request
2020-12-27 20:03:50 +00:00
}
// Make creates a http request for the provided input.
// It returns io.EOF as error when all the requests have been exhausted.
2021-04-16 11:26:41 +00:00
func (r *requestGenerator) Make(baseURL string, dynamicValues map[string]interface{}, interactURL string) (*generatedRequest, error) {
// We get the next payload for the request.
2020-12-27 20:03:50 +00:00
data, payloads, ok := r.nextValue()
if !ok {
return nil, io.EOF
}
2020-12-25 20:39:16 +00:00
ctx := context.Background()
parsed, err := url.Parse(baseURL)
if err != nil {
return nil, err
}
2021-02-05 09:13:11 +00:00
data, parsed = baseURLWithTemplatePrefs(data, parsed)
2021-08-12 17:10:35 +00:00
trailingSlash := false
isRawRequest := len(r.request.Raw) > 0
2021-02-04 12:26:53 +00:00
if !isRawRequest && strings.HasSuffix(parsed.Path, "/") && strings.Contains(data, "{{BaseURL}}/") {
2021-08-12 17:10:35 +00:00
trailingSlash = true
2021-02-04 12:26:53 +00:00
}
2021-08-12 17:10:35 +00:00
values := generators.MergeMaps(dynamicValues, generateVariables(parsed, trailingSlash))
2020-12-25 20:39:16 +00:00
// merge with vars
if !r.options.Options.Vars.IsEmpty() {
values = generators.MergeMaps(values, r.options.Options.Vars.AsMap())
}
// merge with env vars
if r.options.Options.EnvironmentVariables {
values = generators.MergeMaps(generators.EnvVars(), values)
}
// If data contains \n it's a raw request, process it like raw. Else
// continue with the template based request flow.
2021-02-04 12:26:53 +00:00
if isRawRequest {
2021-08-12 17:10:35 +00:00
return r.makeHTTPRequestFromRaw(ctx, parsed.String(), data, values, payloads, interactURL)
2020-12-25 20:39:16 +00:00
}
return r.makeHTTPRequestFromModel(ctx, data, values, payloads, interactURL)
2020-12-25 20:39:16 +00:00
}
// Total returns the total number of requests for the generator
func (r *requestGenerator) Total() int {
if r.payloadIterator != nil {
return len(r.request.Raw) * r.payloadIterator.Remaining()
}
return len(r.request.Path)
}
// baseURLWithTemplatePrefs returns the url for BaseURL keeping
// the template port and path preference over the user provided one.
2021-02-05 09:13:11 +00:00
func baseURLWithTemplatePrefs(data string, parsed *url.URL) (string, *url.URL) {
// template port preference over input URL port if template has a port
matches := urlWithPortRegex.FindAllStringSubmatch(data, -1)
if len(matches) == 0 {
return data, parsed
}
port := matches[0][1]
parsed.Host = net.JoinHostPort(parsed.Hostname(), port)
data = strings.ReplaceAll(data, ":"+port, "")
if parsed.Path == "" {
parsed.Path = "/"
}
2021-02-05 09:13:11 +00:00
return data, parsed
}
2020-12-25 20:39:16 +00:00
// MakeHTTPRequestFromModel creates a *http.Request from a request template
func (r *requestGenerator) makeHTTPRequestFromModel(ctx context.Context, data string, values, generatorValues map[string]interface{}, interactURL string) (*generatedRequest, error) {
2021-05-03 09:01:44 +00:00
if interactURL != "" {
data = r.options.Interactsh.ReplaceMarkers(data, interactURL)
}
// Combine the template payloads along with base
// request values.
finalValues := generators.MergeMaps(generatorValues, values)
// Evaulate the expressions for the request if any.
var err error
data, err = expressions.Evaluate(data, finalValues)
if err != nil {
return nil, errors.Wrap(err, "could not evaluate helper expressions")
2021-05-03 09:01:44 +00:00
}
2020-12-25 20:39:16 +00:00
method, err := expressions.Evaluate(r.request.Method, finalValues)
if err != nil {
return nil, errors.Wrap(err, "could not evaluate helper expressions")
}
2020-12-25 20:39:16 +00:00
// Build a request on the specified URL
req, err := http.NewRequestWithContext(ctx, method, data, nil)
2020-12-25 20:39:16 +00:00
if err != nil {
return nil, err
}
request, err := r.fillRequest(req, finalValues, interactURL)
2020-12-25 20:39:16 +00:00
if err != nil {
return nil, err
}
return &generatedRequest{request: request, meta: generatorValues, original: r.request}, nil
2020-12-25 20:39:16 +00:00
}
// makeHTTPRequestFromRaw creates a *http.Request from a raw request
2021-04-16 11:26:41 +00:00
func (r *requestGenerator) makeHTTPRequestFromRaw(ctx context.Context, baseURL, data string, values, payloads map[string]interface{}, interactURL string) (*generatedRequest, error) {
2021-04-18 12:23:59 +00:00
if interactURL != "" {
2021-04-16 11:26:41 +00:00
data = r.options.Interactsh.ReplaceMarkers(data, interactURL)
}
2021-04-13 06:28:29 +00:00
return r.handleRawWithPayloads(ctx, data, baseURL, values, payloads)
2020-12-25 20:39:16 +00:00
}
2021-04-13 06:28:29 +00:00
// handleRawWithPayloads handles raw requests along with payloads
func (r *requestGenerator) handleRawWithPayloads(ctx context.Context, rawRequest, baseURL string, values, generatorValues map[string]interface{}) (*generatedRequest, error) {
// Combine the template payloads along with base
// request values.
finalValues := generators.MergeMaps(generatorValues, values)
2020-12-25 20:39:16 +00:00
// Evaulate the expressions for raw request if any.
var err error
rawRequest, err = expressions.Evaluate(rawRequest, finalValues)
if err != nil {
return nil, errors.Wrap(err, "could not evaluate helper expressions")
}
2020-12-27 20:03:50 +00:00
rawRequestData, err := raw.Parse(rawRequest, baseURL, r.request.Unsafe)
2020-12-25 20:39:16 +00:00
if err != nil {
return nil, err
}
// Unsafe option uses rawhttp library
2020-12-27 20:03:50 +00:00
if r.request.Unsafe {
unsafeReq := &generatedRequest{rawRequest: rawRequestData, meta: generatorValues, original: r.request}
2020-12-25 20:39:16 +00:00
return unsafeReq, nil
}
// retryablehttp
var body io.ReadCloser
2020-12-27 20:03:50 +00:00
body = ioutil.NopCloser(strings.NewReader(rawRequestData.Data))
if r.request.Race {
2020-12-25 20:39:16 +00:00
// More or less this ensures that all requests hit the endpoint at the same approximated time
// Todo: sync internally upon writing latest request byte
body = race.NewOpenGateWithTimeout(body, time.Duration(2)*time.Second)
2020-12-25 20:39:16 +00:00
}
2020-12-27 20:03:50 +00:00
req, err := http.NewRequestWithContext(ctx, rawRequestData.Method, rawRequestData.FullURL, body)
2020-12-25 20:39:16 +00:00
if err != nil {
return nil, err
}
2020-12-27 20:03:50 +00:00
for key, value := range rawRequestData.Headers {
2021-02-22 13:29:03 +00:00
if key == "" {
continue
}
2020-12-25 20:39:16 +00:00
req.Header[key] = []string{value}
if key == "Host" {
req.Host = value
}
2020-12-25 20:39:16 +00:00
}
request, err := r.fillRequest(req, finalValues, "")
2020-12-25 20:39:16 +00:00
if err != nil {
return nil, err
}
2021-02-19 23:35:39 +00:00
return &generatedRequest{request: request, meta: generatorValues, original: r.request}, nil
2020-12-25 20:39:16 +00:00
}
2020-12-27 20:03:50 +00:00
// fillRequest fills various headers in the request with values
2021-04-16 11:26:41 +00:00
func (r *requestGenerator) fillRequest(req *http.Request, values map[string]interface{}, interactURL string) (*retryablehttp.Request, error) {
2020-12-25 20:39:16 +00:00
// Set the header values requested
2020-12-27 20:03:50 +00:00
for header, value := range r.request.Headers {
2021-04-18 12:23:59 +00:00
if interactURL != "" {
2021-04-16 11:26:41 +00:00
value = r.options.Interactsh.ReplaceMarkers(value, interactURL)
}
value, err := expressions.Evaluate(value, values)
if err != nil {
return nil, errors.Wrap(err, "could not evaluate helper expressions")
}
req.Header[header] = []string{value}
if header == "Host" {
req.Host = value
}
2020-12-25 20:39:16 +00:00
}
// In case of multiple threads the underlying connection should remain open to allow reuse
2020-12-27 20:03:50 +00:00
if r.request.Threads <= 0 && req.Header.Get("Connection") == "" {
2020-12-25 20:39:16 +00:00
req.Close = true
}
// Check if the user requested a request body
2020-12-27 20:03:50 +00:00
if r.request.Body != "" {
2021-04-16 11:26:41 +00:00
body := r.request.Body
2021-04-18 12:23:59 +00:00
if interactURL != "" {
2021-04-16 11:26:41 +00:00
body = r.options.Interactsh.ReplaceMarkers(body, interactURL)
}
body, err := expressions.Evaluate(body, values)
if err != nil {
return nil, errors.Wrap(err, "could not evaluate helper expressions")
}
2021-04-16 11:26:41 +00:00
req.Body = ioutil.NopCloser(strings.NewReader(body))
2020-12-25 20:39:16 +00:00
}
2021-04-13 06:27:36 +00:00
setHeader(req, "User-Agent", uarand.GetRandom())
2020-12-25 20:39:16 +00:00
// Only set these headers on non raw requests
if len(r.request.Raw) == 0 {
setHeader(req, "Accept", "*/*")
setHeader(req, "Accept-Language", "en")
2020-12-25 20:39:16 +00:00
}
return retryablehttp.FromRequest(req)
}
// setHeader sets some headers only if the header wasn't supplied by the user
func setHeader(req *http.Request, name, value string) {
if _, ok := req.Header[name]; !ok {
req.Header.Set(name, value)
}
if name == "Host" {
req.Host = value
}
2020-12-25 20:39:16 +00:00
}
2021-08-12 17:10:35 +00:00
// generateVariables will create default variables after parsing a url
func generateVariables(parsed *url.URL, trailingSlash bool) map[string]interface{} {
domain := parsed.Host
if strings.Contains(parsed.Host, ":") {
domain = strings.Split(parsed.Host, ":")[0]
}
port := parsed.Port()
if port == "" {
if parsed.Scheme == "https" {
port = "443"
} else if parsed.Scheme == "http" {
port = "80"
}
}
if trailingSlash {
parsed.Path = strings.TrimSuffix(parsed.Path, "/")
}
return map[string]interface{}{
"BaseURL": parsed.String(),
2021-08-13 14:38:18 +00:00
"RootURL": fmt.Sprintf("%s://%s", parsed.Scheme, parsed.Host),
2021-08-12 17:10:35 +00:00
"Hostname": parsed.Host,
2021-08-13 14:38:18 +00:00
"Host": domain,
2021-08-12 17:10:35 +00:00
"Port": port,
2021-08-13 14:38:18 +00:00
"Path": parsed.EscapedPath(),
"Scheme": parsed.Scheme,
2021-08-12 17:10:35 +00:00
}
}