use CL instead of TE + unit test (#4154)

* force transfer encoding + unit test

* fix nil panic in integration_test
This commit is contained in:
Tarun Koyalwar 2023-09-16 14:20:35 +05:30 committed by GitHub
parent 5d8f0232a0
commit cdd54acf70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 136 additions and 1 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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")
}