mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-17 22:05:27 +00:00
use CL instead of TE + unit test (#4154)
* force transfer encoding + unit test * fix nil panic in integration_test
This commit is contained in:
parent
5d8f0232a0
commit
cdd54acf70
@ -4,6 +4,8 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -276,6 +278,9 @@ func (r *requestGenerator) generateRawRequest(ctx context.Context, rawRequest st
|
|||||||
if len(r.options.Options.CustomHeaders) > 0 {
|
if len(r.options.Options.CustomHeaders) > 0 {
|
||||||
_ = rawRequestData.TryFillCustomHeaders(r.options.Options.CustomHeaders)
|
_ = rawRequestData.TryFillCustomHeaders(r.options.Options.CustomHeaders)
|
||||||
}
|
}
|
||||||
|
if rawRequestData.Data != "" && !stringsutil.EqualFoldAny(rawRequestData.Method, http.MethodHead, http.MethodGet) && rawRequestData.Headers["Transfer-Encoding"] != "chunked" {
|
||||||
|
rawRequestData.Headers["Content-Length"] = strconv.Itoa(len(rawRequestData.Data))
|
||||||
|
}
|
||||||
unsafeReq := &generatedRequest{rawRequest: rawRequestData, meta: generatorValues, original: r.request, interactshURLs: r.interactshURLs}
|
unsafeReq := &generatedRequest{rawRequest: rawRequestData, meta: generatorValues, original: r.request, interactshURLs: r.interactshURLs}
|
||||||
return unsafeReq, nil
|
return unsafeReq, nil
|
||||||
}
|
}
|
||||||
@ -288,6 +293,12 @@ func (r *requestGenerator) generateRawRequest(ctx context.Context, rawRequest st
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// force transfer encoding if conditions are met
|
||||||
|
if len(rawRequestData.Data) > 0 && req.Header.Get("Transfer-Encoding") != "chunked" && !stringsutil.EqualFoldAny(rawRequestData.Method, http.MethodGet, http.MethodHead) {
|
||||||
|
req.ContentLength = int64(len(rawRequestData.Data))
|
||||||
|
}
|
||||||
|
|
||||||
// override the body with a new one that will be used to read the request body in parallel threads
|
// override the body with a new one that will be used to read the request body in parallel threads
|
||||||
// for race condition testing
|
// for race condition testing
|
||||||
if r.request.Threads > 0 && r.request.Race {
|
if r.request.Threads > 0 && r.request.Race {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -36,6 +37,7 @@ import (
|
|||||||
templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||||
"github.com/projectdiscovery/rawhttp"
|
"github.com/projectdiscovery/rawhttp"
|
||||||
|
"github.com/projectdiscovery/utils/reader"
|
||||||
sliceutil "github.com/projectdiscovery/utils/slice"
|
sliceutil "github.com/projectdiscovery/utils/slice"
|
||||||
stringsutil "github.com/projectdiscovery/utils/strings"
|
stringsutil "github.com/projectdiscovery/utils/strings"
|
||||||
urlutil "github.com/projectdiscovery/utils/url"
|
urlutil "github.com/projectdiscovery/utils/url"
|
||||||
@ -490,6 +492,32 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
|
|||||||
// Dump request for variables checks
|
// Dump request for variables checks
|
||||||
// For race conditions we can't dump the request body at this point as it's already waiting the open-gate event, already handled with a similar code within the race function
|
// For race conditions we can't dump the request body at this point as it's already waiting the open-gate event, already handled with a similar code within the race function
|
||||||
if !generatedRequest.original.Race {
|
if !generatedRequest.original.Race {
|
||||||
|
|
||||||
|
// change encoding type to content-length unless transfer-encoding header is manually set
|
||||||
|
if generatedRequest.request != nil && !stringsutil.EqualFoldAny(generatedRequest.request.Method, http.MethodGet, http.MethodHead) && generatedRequest.request.Body != nil && generatedRequest.request.Header.Get("Transfer-Encoding") != "chunked" {
|
||||||
|
var newReqBody *reader.ReusableReadCloser
|
||||||
|
newReqBody, ok := generatedRequest.request.Body.(*reader.ReusableReadCloser)
|
||||||
|
if !ok {
|
||||||
|
newReqBody, err = reader.NewReusableReadCloser(generatedRequest.request.Body)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
// update the request body with the reusable reader
|
||||||
|
generatedRequest.request.Body = newReqBody
|
||||||
|
// get content length
|
||||||
|
length, _ := io.Copy(io.Discard, newReqBody)
|
||||||
|
generatedRequest.request.ContentLength = length
|
||||||
|
} else {
|
||||||
|
// log error and continue
|
||||||
|
gologger.Verbose().Msgf("[%v] Could not read request body while forcing transfer encoding: %s\n", request.options.TemplateID, err)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// do the same for unsafe requests
|
||||||
|
if generatedRequest.rawRequest != nil && !stringsutil.EqualFoldAny(generatedRequest.rawRequest.Method, http.MethodGet, http.MethodHead) && generatedRequest.rawRequest.Data != "" && generatedRequest.rawRequest.Headers["Transfer-Encoding"] != "chunked" {
|
||||||
|
generatedRequest.rawRequest.Headers["Content-Length"] = strconv.Itoa(len(generatedRequest.rawRequest.Data))
|
||||||
|
}
|
||||||
|
|
||||||
var dumpError error
|
var dumpError error
|
||||||
// TODO: dump is currently not working with post-processors - somehow it alters the signature
|
// TODO: dump is currently not working with post-processors - somehow it alters the signature
|
||||||
dumpedRequest, dumpError = dump(generatedRequest, input.MetaInput.Input)
|
dumpedRequest, dumpError = dump(generatedRequest, input.MetaInput.Input)
|
||||||
@ -514,6 +542,7 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
|
|||||||
var hostname string
|
var hostname string
|
||||||
timeStart := time.Now()
|
timeStart := time.Now()
|
||||||
if generatedRequest.original.Pipeline {
|
if generatedRequest.original.Pipeline {
|
||||||
|
// if request is a pipeline request, use the pipelined client
|
||||||
if generatedRequest.rawRequest != nil {
|
if generatedRequest.rawRequest != nil {
|
||||||
formedURL = generatedRequest.rawRequest.FullURL
|
formedURL = generatedRequest.rawRequest.FullURL
|
||||||
if parsed, parseErr := urlutil.ParseURL(formedURL, true); parseErr == nil {
|
if parsed, parseErr := urlutil.ParseURL(formedURL, true); parseErr == nil {
|
||||||
@ -524,6 +553,7 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
|
|||||||
resp, err = generatedRequest.pipelinedClient.Dor(generatedRequest.request)
|
resp, err = generatedRequest.pipelinedClient.Dor(generatedRequest.request)
|
||||||
}
|
}
|
||||||
} else if generatedRequest.original.Unsafe && generatedRequest.rawRequest != nil {
|
} else if generatedRequest.original.Unsafe && generatedRequest.rawRequest != nil {
|
||||||
|
// if request is a unsafe request, use the rawhttp client
|
||||||
formedURL = generatedRequest.rawRequest.FullURL
|
formedURL = generatedRequest.rawRequest.FullURL
|
||||||
// use request url as matched url if empty
|
// use request url as matched url if empty
|
||||||
if formedURL == "" {
|
if formedURL == "" {
|
||||||
@ -545,11 +575,15 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
|
|||||||
options.SNI = request.options.Options.SNI
|
options.SNI = request.options.Options.SNI
|
||||||
inputUrl := input.MetaInput.Input
|
inputUrl := input.MetaInput.Input
|
||||||
if url, err := urlutil.ParseURL(inputUrl, false); err == nil {
|
if url, err := urlutil.ParseURL(inputUrl, false); err == nil {
|
||||||
inputUrl = fmt.Sprintf("%s://%s", url.Scheme, url.Host)
|
url.Path = ""
|
||||||
|
url.Params = urlutil.NewOrderedParams() // donot include query params
|
||||||
|
// inputUrl should only contain scheme://host:port
|
||||||
|
inputUrl = url.String()
|
||||||
}
|
}
|
||||||
formedURL = fmt.Sprintf("%s%s", inputUrl, generatedRequest.rawRequest.Path)
|
formedURL = fmt.Sprintf("%s%s", inputUrl, generatedRequest.rawRequest.Path)
|
||||||
resp, err = generatedRequest.original.rawhttpClient.DoRawWithOptions(generatedRequest.rawRequest.Method, inputUrl, generatedRequest.rawRequest.Path, generators.ExpandMapValues(generatedRequest.rawRequest.Headers), io.NopCloser(strings.NewReader(generatedRequest.rawRequest.Data)), &options)
|
resp, err = generatedRequest.original.rawhttpClient.DoRawWithOptions(generatedRequest.rawRequest.Method, inputUrl, generatedRequest.rawRequest.Path, generators.ExpandMapValues(generatedRequest.rawRequest.Headers), io.NopCloser(strings.NewReader(generatedRequest.rawRequest.Data)), &options)
|
||||||
} else {
|
} else {
|
||||||
|
//** For Normal requests **//
|
||||||
hostname = generatedRequest.request.URL.Host
|
hostname = generatedRequest.request.URL.Host
|
||||||
formedURL = generatedRequest.request.URL.String()
|
formedURL = generatedRequest.request.URL.String()
|
||||||
// if nuclei-project is available check if the request was already sent previously
|
// if nuclei-project is available check if the request was already sent previously
|
||||||
|
|||||||
@ -94,3 +94,93 @@ Disallow: /c`))
|
|||||||
require.NotNil(t, finalEvent, "could not get event output from request")
|
require.NotNil(t, finalEvent, "could not get event output from request")
|
||||||
require.Equal(t, 3, matchCount, "could not get correct match count")
|
require.Equal(t, 3, matchCount, "could not get correct match count")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDisableTE(t *testing.T) {
|
||||||
|
options := testutils.DefaultOptions
|
||||||
|
|
||||||
|
testutils.Init(options)
|
||||||
|
templateID := "http-disable-transfer-encoding"
|
||||||
|
|
||||||
|
// in raw request format
|
||||||
|
request := &Request{
|
||||||
|
ID: templateID,
|
||||||
|
Raw: []string{
|
||||||
|
`POST / HTTP/1.1
|
||||||
|
Host: {{Hostname}}
|
||||||
|
User-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0
|
||||||
|
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
|
||||||
|
Accept-Language: en-US,en;q=0.5
|
||||||
|
|
||||||
|
login=1&username=admin&password=admin
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
Operators: operators.Operators{
|
||||||
|
Matchers: []*matchers.Matcher{{
|
||||||
|
Type: matchers.MatcherTypeHolder{MatcherType: matchers.StatusMatcher},
|
||||||
|
Status: []int{200},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// in base request format
|
||||||
|
request2 := &Request{
|
||||||
|
ID: templateID,
|
||||||
|
Method: HTTPMethodTypeHolder{MethodType: HTTPPost},
|
||||||
|
Path: []string{"{{BaseURL}}"},
|
||||||
|
Body: "login=1&username=admin&password=admin",
|
||||||
|
Operators: operators.Operators{
|
||||||
|
Matchers: []*matchers.Matcher{{
|
||||||
|
Type: matchers.MatcherTypeHolder{MatcherType: matchers.StatusMatcher},
|
||||||
|
Status: []int{200},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if len(r.TransferEncoding) > 0 || r.ContentLength <= 0 {
|
||||||
|
t.Error("Transfer-Encoding header should not be set")
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
|
||||||
|
ID: templateID,
|
||||||
|
Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := request.Compile(executerOpts)
|
||||||
|
require.Nil(t, err, "could not compile http raw request")
|
||||||
|
|
||||||
|
err = request2.Compile(executerOpts)
|
||||||
|
require.Nil(t, err, "could not compile http base request")
|
||||||
|
|
||||||
|
var finalEvent *output.InternalWrappedEvent
|
||||||
|
var matchCount int
|
||||||
|
t.Run("test", func(t *testing.T) {
|
||||||
|
metadata := make(output.InternalEvent)
|
||||||
|
previous := make(output.InternalEvent)
|
||||||
|
ctxArgs := contextargs.NewWithInput(ts.URL)
|
||||||
|
err := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {
|
||||||
|
if event.OperatorsResult != nil && event.OperatorsResult.Matched {
|
||||||
|
matchCount++
|
||||||
|
}
|
||||||
|
finalEvent = event
|
||||||
|
})
|
||||||
|
require.Nil(t, err, "could not execute network request")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test2", func(t *testing.T) {
|
||||||
|
metadata := make(output.InternalEvent)
|
||||||
|
previous := make(output.InternalEvent)
|
||||||
|
ctxArgs := contextargs.NewWithInput(ts.URL)
|
||||||
|
err := request2.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {
|
||||||
|
if event.OperatorsResult != nil && event.OperatorsResult.Matched {
|
||||||
|
matchCount++
|
||||||
|
}
|
||||||
|
finalEvent = event
|
||||||
|
})
|
||||||
|
require.Nil(t, err, "could not execute network request")
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NotNil(t, finalEvent, "could not get event output from request")
|
||||||
|
require.Equal(t, 2, matchCount, "could not get correct match count")
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user