From 41393fc09dc6e24171e8854deb6fcefb827475ad Mon Sep 17 00:00:00 2001 From: Sajad Parra Date: Thu, 4 Nov 2021 17:13:47 +0530 Subject: [PATCH 1/3] unique payload per interactsh placeholder #1068 --- .../protocols/common/interactsh/interactsh.go | 55 ++++++++++--------- v2/pkg/protocols/http/build_request.go | 55 ++++++++----------- v2/pkg/protocols/http/build_request_test.go | 14 ++--- v2/pkg/protocols/http/request.go | 16 ++---- v2/pkg/protocols/http/request_generator.go | 1 + v2/pkg/protocols/network/request.go | 14 ++--- 6 files changed, 71 insertions(+), 84 deletions(-) diff --git a/v2/pkg/protocols/common/interactsh/interactsh.go b/v2/pkg/protocols/common/interactsh/interactsh.go index 5c2914ff..5fff7ac5 100644 --- a/v2/pkg/protocols/common/interactsh/interactsh.go +++ b/v2/pkg/protocols/common/interactsh/interactsh.go @@ -206,12 +206,14 @@ func (c *Client) Close() bool { // // It accepts data to replace as well as the URL to replace placeholders // with generated uniquely for each request. -func (c *Client) ReplaceMarkers(data, interactshURL string) string { - if !strings.Contains(data, interactshURLMarker) { - return data +func (c *Client) ReplaceMarkers(data string, interactshURLs []string) (string, []string) { + + for strings.Contains(data, interactshURLMarker) { + url := c.URL() + interactshURLs = append(interactshURLs, url) + data = strings.Replace(data, interactshURLMarker, url, 1) } - replaced := strings.NewReplacer("{{interactsh-url}}", interactshURL).Replace(data) - return replaced + return data, interactshURLs } // MakeResultEventFunc is a result making function for nuclei @@ -227,30 +229,33 @@ type RequestData struct { } // RequestEvent is the event for a network request sent by nuclei. -func (c *Client) RequestEvent(interactshURL string, data *RequestData) { - id := strings.TrimSuffix(interactshURL, c.dotHostname) +func (c *Client) RequestEvent(interactshURLs []string, data *RequestData) { + for _, interactshURL := range interactshURLs { + id := strings.TrimSuffix(interactshURL, c.dotHostname) - interaction := c.interactions.Get(id) - if interaction != nil { - // If we have previous interactions, get them and process them. - interactions, ok := interaction.Value().([]*server.Interaction) - if !ok { - c.requests.Set(id, data, c.eviction) - return - } - matched := false - for _, interaction := range interactions { - if c.processInteractionForRequest(interaction, data) { - matched = true - break + interaction := c.interactions.Get(id) + if interaction != nil { + // If we have previous interactions, get them and process them. + interactions, ok := interaction.Value().([]*server.Interaction) + if !ok { + c.requests.Set(id, data, c.eviction) + return } + matched := false + for _, interaction := range interactions { + if c.processInteractionForRequest(interaction, data) { + matched = true + break + } + } + if matched { + c.interactions.Delete(id) + } + } else { + c.requests.Set(id, data, c.eviction) } - if matched { - c.interactions.Delete(id) - } - } else { - c.requests.Set(id, data, c.eviction) } + } // HasMatchers returns true if an operator has interactsh part diff --git a/v2/pkg/protocols/http/build_request.go b/v2/pkg/protocols/http/build_request.go index ea83aea7..20f3d480 100644 --- a/v2/pkg/protocols/http/build_request.go +++ b/v2/pkg/protocols/http/build_request.go @@ -38,6 +38,7 @@ type generatedRequest struct { pipelinedClient *rawhttp.PipelineClient request *retryablehttp.Request dynamicValues map[string]interface{} + interactshURLs []string } func (g *generatedRequest) URL() string { @@ -52,9 +53,9 @@ func (g *generatedRequest) URL() string { // Make creates a http request for the provided input. // It returns io.EOF as error when all the requests have been exhausted. -func (r *requestGenerator) Make(baseURL string, dynamicValues map[string]interface{}, interactURL string) (*generatedRequest, error) { +func (r *requestGenerator) Make(baseURL string, dynamicValues map[string]interface{}) (*generatedRequest, error) { if r.request.SelfContained { - return r.makeSelfContainedRequest(dynamicValues, interactURL) + return r.makeSelfContainedRequest(dynamicValues) } // We get the next payload for the request. data, payloads, ok := r.nextValue() @@ -63,12 +64,10 @@ func (r *requestGenerator) Make(baseURL string, dynamicValues map[string]interfa } ctx := context.Background() - if interactURL != "" { - data = r.options.Interactsh.ReplaceMarkers(data, interactURL) + data, r.interactshURLs = r.options.Interactsh.ReplaceMarkers(data, r.interactshURLs) - for payloadName, payloadValue := range payloads { - payloads[payloadName] = r.options.Interactsh.ReplaceMarkers(types.ToString(payloadValue), interactURL) - } + for payloadName, payloadValue := range payloads { + payloads[payloadName], r.interactshURLs = r.options.Interactsh.ReplaceMarkers(types.ToString(payloadValue), r.interactshURLs) } parsed, err := url.Parse(baseURL) @@ -98,12 +97,12 @@ func (r *requestGenerator) Make(baseURL string, dynamicValues map[string]interfa // If data contains \n it's a raw request, process it like raw. Else // continue with the template based request flow. if isRawRequest { - return r.makeHTTPRequestFromRaw(ctx, parsed.String(), data, values, payloads, interactURL) + return r.makeHTTPRequestFromRaw(ctx, parsed.String(), data, values, payloads) } - return r.makeHTTPRequestFromModel(ctx, data, values, payloads, interactURL) + return r.makeHTTPRequestFromModel(ctx, data, values, payloads) } -func (r *requestGenerator) makeSelfContainedRequest(dynamicValues map[string]interface{}, interactURL string) (*generatedRequest, error) { +func (r *requestGenerator) makeSelfContainedRequest(dynamicValues map[string]interface{}) (*generatedRequest, error) { // We get the next payload for the request. data, payloads, ok := r.nextValue() if !ok { @@ -136,13 +135,13 @@ func (r *requestGenerator) makeSelfContainedRequest(dynamicValues map[string]int generators.BuildPayloadFromOptions(r.request.options.Options), ) - return r.makeHTTPRequestFromRaw(ctx, parsed.String(), data, values, payloads, interactURL) + return r.makeHTTPRequestFromRaw(ctx, parsed.String(), data, values, payloads) } values := generators.MergeMaps( dynamicValues, generators.BuildPayloadFromOptions(r.request.options.Options), ) - return r.makeHTTPRequestFromModel(ctx, data, values, payloads, interactURL) + return r.makeHTTPRequestFromModel(ctx, data, values, payloads) } // Total returns the total number of requests for the generator @@ -171,10 +170,8 @@ func baseURLWithTemplatePrefs(data string, parsed *url.URL) (string, *url.URL) { } // 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) { - if interactURL != "" { - data = r.options.Interactsh.ReplaceMarkers(data, interactURL) - } +func (r *requestGenerator) makeHTTPRequestFromModel(ctx context.Context, data string, values, generatorValues map[string]interface{}) (*generatedRequest, error) { + data, r.interactshURLs = r.options.Interactsh.ReplaceMarkers(data, r.interactshURLs) // Combine the template payloads along with base // request values. @@ -198,18 +195,16 @@ func (r *requestGenerator) makeHTTPRequestFromModel(ctx context.Context, data st return nil, err } - request, err := r.fillRequest(req, finalValues, interactURL) + request, err := r.fillRequest(req, finalValues) if err != nil { return nil, err } - return &generatedRequest{request: request, meta: generatorValues, original: r.request, dynamicValues: finalValues}, nil + return &generatedRequest{request: request, meta: generatorValues, original: r.request, dynamicValues: finalValues, interactshURLs: r.interactshURLs}, nil } // makeHTTPRequestFromRaw creates a *http.Request from a raw request -func (r *requestGenerator) makeHTTPRequestFromRaw(ctx context.Context, baseURL, data string, values, payloads map[string]interface{}, interactURL string) (*generatedRequest, error) { - if interactURL != "" { - data = r.options.Interactsh.ReplaceMarkers(data, interactURL) - } +func (r *requestGenerator) makeHTTPRequestFromRaw(ctx context.Context, baseURL, data string, values, payloads map[string]interface{}) (*generatedRequest, error) { + data, r.interactshURLs = r.options.Interactsh.ReplaceMarkers(data, r.interactshURLs) return r.handleRawWithPayloads(ctx, data, baseURL, values, payloads) } @@ -258,21 +253,19 @@ func (r *requestGenerator) handleRawWithPayloads(ctx context.Context, rawRequest req.Host = value } } - request, err := r.fillRequest(req, finalValues, "") + request, err := r.fillRequest(req, finalValues) if err != nil { return nil, err } - return &generatedRequest{request: request, meta: generatorValues, original: r.request, dynamicValues: finalValues}, nil + return &generatedRequest{request: request, meta: generatorValues, original: r.request, dynamicValues: finalValues, interactshURLs: r.interactshURLs}, nil } // fillRequest fills various headers in the request with values -func (r *requestGenerator) fillRequest(req *http.Request, values map[string]interface{}, interactURL string) (*retryablehttp.Request, error) { +func (r *requestGenerator) fillRequest(req *http.Request, values map[string]interface{}) (*retryablehttp.Request, error) { // Set the header values requested for header, value := range r.request.Headers { - if interactURL != "" { - value = r.options.Interactsh.ReplaceMarkers(value, interactURL) - } + value, r.interactshURLs = r.options.Interactsh.ReplaceMarkers(value, r.interactshURLs) value, err := expressions.Evaluate(value, values) if err != nil { return nil, errors.Wrap(err, "could not evaluate helper expressions") @@ -290,10 +283,8 @@ func (r *requestGenerator) fillRequest(req *http.Request, values map[string]inte // Check if the user requested a request body if r.request.Body != "" { - body := r.request.Body - if interactURL != "" { - body = r.options.Interactsh.ReplaceMarkers(body, interactURL) - } + var body string + body, r.interactshURLs = r.options.Interactsh.ReplaceMarkers(r.request.Body, r.interactshURLs) body, err := expressions.Evaluate(body, values) if err != nil { return nil, errors.Wrap(err, "could not evaluate helper expressions") diff --git a/v2/pkg/protocols/http/build_request_test.go b/v2/pkg/protocols/http/build_request_test.go index d45f7f8f..122d81bc 100644 --- a/v2/pkg/protocols/http/build_request_test.go +++ b/v2/pkg/protocols/http/build_request_test.go @@ -84,7 +84,7 @@ func TestMakeRequestFromModal(t *testing.T) { require.Nil(t, err, "could not compile http request") generator := request.newGenerator() - req, err := generator.Make("https://example.com", map[string]interface{}{}, "") + req, err := generator.Make("https://example.com", map[string]interface{}{}) require.Nil(t, err, "could not make http request") bodyBytes, _ := req.request.BodyBytes() @@ -111,12 +111,12 @@ func TestMakeRequestFromModalTrimSuffixSlash(t *testing.T) { require.Nil(t, err, "could not compile http request") generator := request.newGenerator() - req, err := generator.Make("https://example.com/test.php", map[string]interface{}{}, "") + req, err := generator.Make("https://example.com/test.php", map[string]interface{}{}) require.Nil(t, err, "could not make http request") require.Equal(t, "https://example.com/test.php?query=example", req.request.URL.String(), "could not get correct request path") generator = request.newGenerator() - req, err = generator.Make("https://example.com/test/", map[string]interface{}{}, "") + req, err = generator.Make("https://example.com/test/", map[string]interface{}{}) require.Nil(t, err, "could not make http request") require.Equal(t, "https://example.com/test/?query=example", req.request.URL.String(), "could not get correct request path") } @@ -149,12 +149,12 @@ Accept-Encoding: gzip`}, require.Nil(t, err, "could not compile http request") generator := request.newGenerator() - req, err := generator.Make("https://example.com", map[string]interface{}{}, "") + req, err := generator.Make("https://example.com", map[string]interface{}{}) require.Nil(t, err, "could not make http request") authorization := req.request.Header.Get("Authorization") require.Equal(t, "Basic admin:admin", authorization, "could not get correct authorization headers from raw") - req, err = generator.Make("https://example.com", map[string]interface{}{}, "") + req, err = generator.Make("https://example.com", map[string]interface{}{}) require.Nil(t, err, "could not make http request") authorization = req.request.Header.Get("Authorization") require.Equal(t, "Basic admin:guest", authorization, "could not get correct authorization headers from raw") @@ -188,12 +188,12 @@ Accept-Encoding: gzip`}, require.Nil(t, err, "could not compile http request") generator := request.newGenerator() - req, err := generator.Make("https://example.com", map[string]interface{}{}, "") + req, err := generator.Make("https://example.com", map[string]interface{}{}) require.Nil(t, err, "could not make http request") authorization := req.request.Header.Get("Authorization") require.Equal(t, "Basic YWRtaW46YWRtaW4=", authorization, "could not get correct authorization headers from raw") - req, err = generator.Make("https://example.com", map[string]interface{}{}, "") + req, err = generator.Make("https://example.com", map[string]interface{}{}) require.Nil(t, err, "could not make http request") authorization = req.request.Header.Get("Authorization") require.Equal(t, "Basic YWRtaW46Z3Vlc3Q=", authorization, "could not get correct authorization headers from raw") diff --git a/v2/pkg/protocols/http/request.go b/v2/pkg/protocols/http/request.go index b11340bf..c3472b45 100644 --- a/v2/pkg/protocols/http/request.go +++ b/v2/pkg/protocols/http/request.go @@ -41,7 +41,7 @@ func (request *Request) executeRaceRequest(reqURL string, previous output.Intern // Requests within race condition should be dumped once and the output prefilled to allow DSL language to work // This will introduce a delay and will populate in hacky way the field "request" of outputEvent generator := request.newGenerator() - requestForDump, err := generator.Make(reqURL, nil, "") + requestForDump, err := generator.Make(reqURL, nil) if err != nil { return err } @@ -59,7 +59,7 @@ func (request *Request) executeRaceRequest(reqURL string, previous output.Intern // Pre-Generate requests for i := 0; i < request.RaceNumberRequests; i++ { generator := request.newGenerator() - generatedRequest, err := generator.Make(reqURL, nil, "") + generatedRequest, err := generator.Make(reqURL, nil) if err != nil { return err } @@ -98,7 +98,7 @@ func (request *Request) executeParallelHTTP(reqURL string, dynamicValues output. var requestErr error mutex := &sync.Mutex{} for { - generatedHttpRequest, err := generator.Make(reqURL, dynamicValues, "") + generatedHttpRequest, err := generator.Make(reqURL, dynamicValues) if err == io.EOF { break } @@ -161,7 +161,7 @@ func (request *Request) executeTurboHTTP(reqURL string, dynamicValues, previous var requestErr error mutex := &sync.Mutex{} for { - generatedHttpRequest, err := generator.Make(reqURL, dynamicValues, "") + generatedHttpRequest, err := generator.Make(reqURL, dynamicValues) if err == io.EOF { break } @@ -215,11 +215,7 @@ func (request *Request) ExecuteWithResults(reqURL string, dynamicValues, previou for { hasInteractMarkers := interactsh.HasMatchers(request.CompiledOperators) - var interactURL string - if request.options.Interactsh != nil && hasInteractMarkers { - interactURL = request.options.Interactsh.URL() - } - generatedHttpRequest, err := generator.Make(reqURL, dynamicValues, interactURL) + generatedHttpRequest, err := generator.Make(reqURL, dynamicValues) if err == io.EOF { break } @@ -245,7 +241,7 @@ func (request *Request) ExecuteWithResults(reqURL string, dynamicValues, previou dynamicValues = generators.MergeMaps(dynamicValues, event.OperatorsResult.DynamicValues) } if hasInteractMarkers && request.options.Interactsh != nil { - request.options.Interactsh.RequestEvent(interactURL, &interactsh.RequestData{ + request.options.Interactsh.RequestEvent(generatedHttpRequest.interactshURLs, &interactsh.RequestData{ MakeResultFunc: request.MakeResultEvent, Event: event, Operators: request.CompiledOperators, diff --git a/v2/pkg/protocols/http/request_generator.go b/v2/pkg/protocols/http/request_generator.go index 90c5d0fb..78337583 100644 --- a/v2/pkg/protocols/http/request_generator.go +++ b/v2/pkg/protocols/http/request_generator.go @@ -16,6 +16,7 @@ type requestGenerator struct { request *Request options *protocols.ExecuterOptions payloadIterator *generators.Iterator + interactshURLs []string } // newGenerator creates a new request generator instance diff --git a/v2/pkg/protocols/network/request.go b/v2/pkg/protocols/network/request.go index 76135085..7903ce4f 100644 --- a/v2/pkg/protocols/network/request.go +++ b/v2/pkg/protocols/network/request.go @@ -120,11 +120,7 @@ func (request *Request) executeRequestWithPayloads(actualAddress, address, input defer conn.Close() _ = conn.SetReadDeadline(time.Now().Add(time.Duration(request.options.Options.Timeout) * time.Second)) - hasInteractMarkers := interactsh.HasMatchers(request.CompiledOperators) - var interactURL string - if request.options.Interactsh != nil && hasInteractMarkers { - interactURL = request.options.Interactsh.URL() - } + var interactshURLs []string responseBuilder := &strings.Builder{} reqBuilder := &strings.Builder{} @@ -137,9 +133,7 @@ func (request *Request) executeRequestWithPayloads(actualAddress, address, input case "hex": data, err = hex.DecodeString(input.Data) default: - if interactURL != "" { - input.Data = request.options.Interactsh.ReplaceMarkers(input.Data, interactURL) - } + input.Data, interactshURLs = request.options.Interactsh.ReplaceMarkers(input.Data, []string{}) data = []byte(input.Data) } if err != nil { @@ -261,14 +255,14 @@ func (request *Request) executeRequestWithPayloads(actualAddress, address, input } var event *output.InternalWrappedEvent - if interactURL == "" { + if len(interactshURLs) == 0 { event = eventcreator.CreateEventWithAdditionalOptions(request, outputEvent, request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) { wrappedEvent.OperatorsResult.PayloadValues = payloads }) callback(event) } else if request.options.Interactsh != nil { event = &output.InternalWrappedEvent{InternalEvent: outputEvent} - request.options.Interactsh.RequestEvent(interactURL, &interactsh.RequestData{ + request.options.Interactsh.RequestEvent(interactshURLs, &interactsh.RequestData{ MakeResultFunc: request.MakeResultEvent, Event: event, Operators: request.CompiledOperators, From 4d8eaad0a3050a308f308e168b85b6537e5bab56 Mon Sep 17 00:00:00 2001 From: Sajad Parra Date: Fri, 5 Nov 2021 15:27:49 +0530 Subject: [PATCH 2/3] add unit test for unique interactsh url #1068 --- v2/cmd/nuclei/main.go | 2 +- v2/internal/runner/runner.go | 2 +- v2/internal/testutils/testutils.go | 77 ++++++++++--------- .../protocols/common/interactsh/interactsh.go | 6 +- v2/pkg/protocols/http/build_request_test.go | 44 ++++++++++- v2/pkg/types/types.go | 4 +- 6 files changed, 88 insertions(+), 47 deletions(-) diff --git a/v2/cmd/nuclei/main.go b/v2/cmd/nuclei/main.go index 7f43868f..16997964 100644 --- a/v2/cmd/nuclei/main.go +++ b/v2/cmd/nuclei/main.go @@ -106,7 +106,7 @@ on extensive configurability, massive extensibility and ease of use.`) flagSet.IntVar(&options.InteractionsCacheSize, "interactions-cache-size", 5000, "number of requests to keep in the interactions cache"), flagSet.IntVar(&options.InteractionsEviction, "interactions-eviction", 60, "number of seconds to wait before evicting requests from cache"), flagSet.IntVar(&options.InteractionsPollDuration, "interactions-poll-duration", 5, "number of seconds to wait before each interaction poll request"), - flagSet.IntVar(&options.InteractionsColldownPeriod, "interactions-cooldown-period", 5, "extra time for interaction polling before exiting"), + flagSet.IntVar(&options.InteractionsCooldownPeriod, "interactions-cooldown-period", 5, "extra time for interaction polling before exiting"), flagSet.BoolVarP(&options.NoInteractsh, "no-interactsh", "ni", false, "disable interactsh server for OAST testing, exclude OAST based templates"), ) diff --git a/v2/internal/runner/runner.go b/v2/internal/runner/runner.go index de1caff9..c9a0ab84 100644 --- a/v2/internal/runner/runner.go +++ b/v2/internal/runner/runner.go @@ -249,7 +249,7 @@ func New(options *types.Options) (*Runner, error) { Authorization: options.InteractshToken, CacheSize: int64(options.InteractionsCacheSize), Eviction: time.Duration(options.InteractionsEviction) * time.Second, - ColldownPeriod: time.Duration(options.InteractionsColldownPeriod) * time.Second, + ColldownPeriod: time.Duration(options.InteractionsCooldownPeriod) * time.Second, PollDuration: time.Duration(options.InteractionsPollDuration) * time.Second, Output: runner.output, IssuesClient: runner.issuesClient, diff --git a/v2/internal/testutils/testutils.go b/v2/internal/testutils/testutils.go index d62b1d63..79e42642 100644 --- a/v2/internal/testutils/testutils.go +++ b/v2/internal/testutils/testutils.go @@ -22,42 +22,47 @@ func Init(options *types.Options) { // DefaultOptions is the default options structure for nuclei during mocking. var DefaultOptions = &types.Options{ - Metrics: false, - Debug: false, - DebugRequests: false, - DebugResponse: false, - Silent: false, - Version: false, - Verbose: false, - NoColor: true, - UpdateTemplates: false, - JSON: false, - JSONRequests: false, - EnableProgressBar: false, - TemplatesVersion: false, - TemplateList: false, - Stdin: false, - StopAtFirstMatch: false, - NoMeta: false, - Project: false, - MetricsPort: 0, - BulkSize: 25, - TemplateThreads: 10, - Timeout: 5, - Retries: 1, - RateLimit: 150, - ProjectPath: "", - Severities: severity.Severities{}, - Targets: []string{}, - TargetsFilePath: "", - Output: "", - ProxyURL: "", - ProxySocksURL: "", - TemplatesDirectory: "", - TraceLogFile: "", - Templates: []string{}, - ExcludedTemplates: []string{}, - CustomHeaders: []string{}, + Metrics: false, + Debug: false, + DebugRequests: false, + DebugResponse: false, + Silent: false, + Version: false, + Verbose: false, + NoColor: true, + UpdateTemplates: false, + JSON: false, + JSONRequests: false, + EnableProgressBar: false, + TemplatesVersion: false, + TemplateList: false, + Stdin: false, + StopAtFirstMatch: false, + NoMeta: false, + Project: false, + MetricsPort: 0, + BulkSize: 25, + TemplateThreads: 10, + Timeout: 5, + Retries: 1, + RateLimit: 150, + ProjectPath: "", + Severities: severity.Severities{}, + Targets: []string{}, + TargetsFilePath: "", + Output: "", + ProxyURL: "", + ProxySocksURL: "", + TemplatesDirectory: "", + TraceLogFile: "", + Templates: []string{}, + ExcludedTemplates: []string{}, + CustomHeaders: []string{}, + InteractshURL: "https://interactsh.com", + InteractionsCacheSize: 5000, + InteractionsEviction: 60, + InteractionsCooldownPeriod: 5, + InteractionsPollDuration: 5, } // MockOutputWriter is a mocked output writer. diff --git a/v2/pkg/protocols/common/interactsh/interactsh.go b/v2/pkg/protocols/common/interactsh/interactsh.go index 5fff7ac5..2fc78857 100644 --- a/v2/pkg/protocols/common/interactsh/interactsh.go +++ b/v2/pkg/protocols/common/interactsh/interactsh.go @@ -241,16 +241,12 @@ func (c *Client) RequestEvent(interactshURLs []string, data *RequestData) { c.requests.Set(id, data, c.eviction) return } - matched := false for _, interaction := range interactions { if c.processInteractionForRequest(interaction, data) { - matched = true + c.interactions.Delete(id) break } } - if matched { - c.interactions.Delete(id) - } } else { c.requests.Set(id, data, c.eviction) } diff --git a/v2/pkg/protocols/http/build_request_test.go b/v2/pkg/protocols/http/build_request_test.go index 122d81bc..663fe109 100644 --- a/v2/pkg/protocols/http/build_request_test.go +++ b/v2/pkg/protocols/http/build_request_test.go @@ -3,12 +3,13 @@ package http import ( "net/url" "testing" - - "github.com/stretchr/testify/require" + "time" "github.com/projectdiscovery/nuclei/v2/internal/testutils" "github.com/projectdiscovery/nuclei/v2/pkg/model" "github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh" + "github.com/stretchr/testify/require" ) func TestBaseURLWithTemplatePrefs(t *testing.T) { @@ -198,3 +199,42 @@ Accept-Encoding: gzip`}, authorization = req.request.Header.Get("Authorization") require.Equal(t, "Basic YWRtaW46Z3Vlc3Q=", authorization, "could not get correct authorization headers from raw") } + +func TestMakeRequestFromModelUniqueInteractsh(t *testing.T) { + + options := testutils.DefaultOptions + + testutils.Init(options) + templateID := "testing-unique-interactsh" + request := &Request{ + ID: templateID, + Name: "testing", + Path: []string{"{{BaseURL}}/?u=http://{{interactsh-url}}/&href=http://{{interactsh-url}}/&action=http://{{interactsh-url}}/&host={{interactsh-url}}"}, + Method: "GET", + } + 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 request") + + generator := request.newGenerator() + + generator.options.Interactsh, err = interactsh.New(&interactsh.Options{ + ServerURL: options.InteractshURL, + CacheSize: int64(options.InteractionsCacheSize), + Eviction: time.Duration(options.InteractionsEviction) * time.Second, + ColldownPeriod: time.Duration(options.InteractionsCooldownPeriod) * time.Second, + PollDuration: time.Duration(options.InteractionsPollDuration) * time.Second, + }) + require.Nil(t, err, "could not create interactsh client") + + got, err := generator.Make("https://example.com", map[string]interface{}{}) + require.Nil(t, err, "could not make http request") + + //check if all the interactsh markers are replaced with unique urls + require.NotContains(t, got.request.URL.String(), "{{interactsh-url}}", "could not get correct interactsh url") + //check the length of returned urls + require.Equal(t, len(got.interactshURLs), 4, "could not get correct interactsh url") +} diff --git a/v2/pkg/types/types.go b/v2/pkg/types/types.go index c19ad18a..50a8c78a 100644 --- a/v2/pkg/types/types.go +++ b/v2/pkg/types/types.go @@ -100,9 +100,9 @@ type Options struct { // Eviction is the number of seconds after which to automatically discard // interaction requests. InteractionsEviction int - // InteractionsColldownPeriod is additional seconds to wait for interactions after closing + // InteractionsCooldownPeriod is additional seconds to wait for interactions after closing // of the poller. - InteractionsColldownPeriod int + InteractionsCooldownPeriod int // OfflineHTTP is a flag that specific offline processing of http response // using same matchers/extractors from http protocol without the need // to send a new request, reading responses from a file. From 6d0d3e1c359457bb22ee3afe380c8ebd96b9d4ee Mon Sep 17 00:00:00 2001 From: Sajad Parra Date: Fri, 5 Nov 2021 20:00:46 +0530 Subject: [PATCH 3/3] add unique check to test case --- v2/pkg/protocols/http/build_request_test.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/v2/pkg/protocols/http/build_request_test.go b/v2/pkg/protocols/http/build_request_test.go index 663fe109..0109498a 100644 --- a/v2/pkg/protocols/http/build_request_test.go +++ b/v2/pkg/protocols/http/build_request_test.go @@ -233,8 +233,22 @@ func TestMakeRequestFromModelUniqueInteractsh(t *testing.T) { got, err := generator.Make("https://example.com", map[string]interface{}{}) require.Nil(t, err, "could not make http request") - //check if all the interactsh markers are replaced with unique urls + // check if all the interactsh markers are replaced with unique urls require.NotContains(t, got.request.URL.String(), "{{interactsh-url}}", "could not get correct interactsh url") - //check the length of returned urls + // check the length of returned urls require.Equal(t, len(got.interactshURLs), 4, "could not get correct interactsh url") + // check if the interactsh urls are unique + require.True(t, areUnique(got.interactshURLs), "interactsh urls are not unique") +} + +// areUnique checks if the elements of string slice are unique +func areUnique(elements []string) bool { + encountered := map[string]bool{} + for v := range elements { + if encountered[elements[v]] { + return false + } + encountered[elements[v]] = true + } + return true }