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

212 lines
5.8 KiB
Go
Raw Normal View History

2020-12-26 02:09:16 +05:30
package http
import (
"context"
"io"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"github.com/Knetic/govaluate"
"github.com/projectdiscovery/nuclei/pkg/generators"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/replacer"
"github.com/projectdiscovery/nuclei/v2/pkg/syncedreadcloser"
)
// MakeHTTPRequest makes the HTTP request
func (r *Request) MakeHTTPRequest(baseURL string, dynamicValues map[string]interface{}, data string) (*HTTPRequest, error) {
ctx := context.Background()
parsed, err := url.Parse(baseURL)
if err != nil {
return nil, err
}
hostname := parsed.Host
values := generators.MergeMaps(dynamicValues, map[string]interface{}{
"BaseURL": baseURLWithTemplatePrefs(data, parsed),
"Hostname": hostname,
})
// if data contains \n it's a raw request
if strings.Contains(data, "\n") {
return r.makeHTTPRequestFromRaw(ctx, baseURL, data, values)
}
return r.makeHTTPRequestFromModel(ctx, data, values)
}
// MakeHTTPRequestFromModel creates a *http.Request from a request template
func (r *Request) makeHTTPRequestFromModel(ctx context.Context, data string, values map[string]interface{}) (*HTTPRequest, error) {
replacer := newReplacer(values)
URL := replacer.Replace(data)
// Build a request on the specified URL
req, err := http.NewRequestWithContext(ctx, r.Method, URL, nil)
if err != nil {
return nil, err
}
request, err := r.fillRequest(req, values)
if err != nil {
return nil, err
}
return &HTTPRequest{Request: request}, nil
}
// InitGenerator initializes the generator
func (r *Request) InitGenerator() {
r.gsfm = NewGeneratorFSM(r.attackType, r.Payloads, r.Path, r.Raw)
}
// CreateGenerator creates the generator
func (r *Request) CreateGenerator(reqURL string) {
r.gsfm.Add(reqURL)
}
// HasGenerator check if an URL has a generator
func (r *Request) HasGenerator(reqURL string) bool {
return r.gsfm.Has(reqURL)
}
// ReadOne reads and return a generator by URL
func (r *Request) ReadOne(reqURL string) {
r.gsfm.ReadOne(reqURL)
}
// makeHTTPRequestFromRaw creates a *http.Request from a raw request
func (r *Request) makeHTTPRequestFromRaw(ctx context.Context, baseURL, data string, values map[string]interface{}) (*HTTPRequest, error) {
// Add trailing line
data += "\n"
if len(r.Payloads) > 0 {
r.gsfm.InitOrSkip(baseURL)
r.ReadOne(baseURL)
payloads, err := r.GetPayloadsValues(baseURL)
if err != nil {
return nil, err
}
return r.handleRawWithPaylods(ctx, data, baseURL, values, payloads)
}
// otherwise continue with normal flow
return r.handleRawWithPaylods(ctx, data, baseURL, values, nil)
}
func (r *Request) handleRawWithPaylods(ctx context.Context, raw, baseURL string, values, genValues map[string]interface{}) (*HTTPRequest, error) {
baseValues := generators.CopyMap(values)
finValues := generators.MergeMaps(baseValues, genValues)
replacer := newReplacer(finValues)
// Replace the dynamic variables in the URL if any
raw = replacer.Replace(raw)
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
expr := generators.TrimDelimiters(match)
compiled, err := govaluate.NewEvaluableExpressionWithFunctions(expr, generators.HelperFunctions())
if err != nil {
return nil, err
}
result, err := compiled.Evaluate(finValues)
if err != nil {
return nil, err
}
dynamicValues[expr] = result
}
// replace dynamic values
dynamicReplacer := newReplacer(dynamicValues)
raw = dynamicReplacer.Replace(raw)
rawRequest, err := r.parseRawRequest(raw, baseURL)
if err != nil {
return nil, err
}
// rawhttp
if r.Unsafe {
unsafeReq := &HTTPRequest{
RawRequest: rawRequest,
Meta: genValues,
AutomaticHostHeader: !r.DisableAutoHostname,
AutomaticContentLengthHeader: !r.DisableAutoContentLength,
Unsafe: true,
FollowRedirects: r.Redirects,
}
return unsafeReq, nil
}
// retryablehttp
var body io.ReadCloser
body = ioutil.NopCloser(strings.NewReader(rawRequest.Data))
if r.Race {
// 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 = syncedreadcloser.NewOpenGateWithTimeout(body, time.Duration(two)*time.Second)
}
req, err := http.NewRequestWithContext(ctx, rawRequest.Method, rawRequest.FullURL, body)
if err != nil {
return nil, err
}
// copy headers
for key, value := range rawRequest.Headers {
req.Header[key] = []string{value}
}
request, err := r.fillRequest(req, values)
if err != nil {
return nil, err
}
return &HTTPRequest{Request: request, Meta: genValues}, nil
}
func (r *Request) fillRequest(req *http.Request, values map[string]interface{}) (*retryablehttp.Request, error) {
replacer := replacer.New(values)
// Set the header values requested
for header, value := range r.Headers {
req.Header[header] = []string{replacer.Replace(value)}
}
// In case of multiple threads the underlying connection should remain open to allow reuse
if r.Threads <= 0 && req.Header.Get("Connection") == "" {
req.Close = true
}
// Check if the user requested a request body
if r.Body != "" {
req.Body = ioutil.NopCloser(strings.NewReader(r.Body))
}
setHeader(req, "User-Agent", "Nuclei - Open-source project (github.com/projectdiscovery/nuclei)")
// raw requests are left untouched
if len(r.Raw) > 0 {
return retryablehttp.FromRequest(req)
}
setHeader(req, "Accept", "*/*")
setHeader(req, "Accept-Language", "en")
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)
}
}