diff --git a/cmd/integration-test/http.go b/cmd/integration-test/http.go index 17effbd21..d5d2e0a14 100644 --- a/cmd/integration-test/http.go +++ b/cmd/integration-test/http.go @@ -11,6 +11,7 @@ import ( "regexp" "strconv" "strings" + "sync" "time" "github.com/julienschmidt/httprouter" @@ -62,6 +63,7 @@ var httpTestcases = []TestCaseInfo{ {Path: "protocols/http/dsl-functions.yaml", TestCase: &httpDSLFunctions{}}, {Path: "protocols/http/race-simple.yaml", TestCase: &httpRaceSimple{}}, {Path: "protocols/http/race-multiple.yaml", TestCase: &httpRaceMultiple{}}, + {Path: "protocols/http/race-condition-with-delay.yaml", TestCase: &httpRaceWithDelay{}}, {Path: "protocols/http/race-with-variables.yaml", TestCase: &httpRaceWithVariables{}}, {Path: "protocols/http/stop-at-first-match.yaml", TestCase: &httpStopAtFirstMatch{}}, {Path: "protocols/http/stop-at-first-match-with-extractors.yaml", TestCase: &httpStopAtFirstMatchWithExtractors{}}, @@ -1156,6 +1158,51 @@ func (h *httpRaceMultiple) Execute(filePath string) error { return expectResultsCount(results, 5) } +type httpRaceWithDelay struct{} + +// Execute executes a test case and returns an error if occurred +func (h *httpRaceWithDelay) Execute(filePath string) error { + var requestTimes []time.Time + var mu sync.Mutex + + router := httprouter.New() + router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + mu.Lock() + requestTimes = append(requestTimes, time.Now()) + mu.Unlock() + time.Sleep(2 * time.Second) + w.WriteHeader(http.StatusOK) + }) + ts := httptest.NewServer(router) + defer ts.Close() + + results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug) + if err != nil { + return err + } + if err := expectResultsCount(results, 3); err != nil { + return err + } + + mu.Lock() + defer mu.Unlock() + if len(requestTimes) != 3 { + return fmt.Errorf("expected 3 requests, got %d", len(requestTimes)) + } + + // Check concurrency of first two requests (should be very close) + if diff := requestTimes[1].Sub(requestTimes[0]); diff > 500*time.Millisecond { + return fmt.Errorf("expected first 2 requests to be concurrent, diff: %v", diff) + } + + // Check delay of third request (should be after ~2s) + if diff := requestTimes[2].Sub(requestTimes[0]); diff < 1500*time.Millisecond { + return fmt.Errorf("expected 3rd request to be delayed, diff: %v", diff) + } + + return nil +} + type httpRaceWithVariables struct{} // Execute tests that variables and constants are properly resolved in race mode. diff --git a/integration_tests/protocols/http/race-condition-with-delay.yaml b/integration_tests/protocols/http/race-condition-with-delay.yaml new file mode 100644 index 000000000..fd54941c6 --- /dev/null +++ b/integration_tests/protocols/http/race-condition-with-delay.yaml @@ -0,0 +1,28 @@ +id: race-condition-with-delay + +info: + name: Race Condition Testing with Delay + author: pdteam + severity: info + description: | + Test race condition handling with induced server delay. + tags: test + +http: + - raw: + - | + GET / HTTP/1.1 + Host: {{Hostname}} + - | + GET / HTTP/1.1 + Host: {{Hostname}} + - | + GET / HTTP/1.1 + Host: {{Hostname}} + + threads: 2 + race: true + matchers: + - type: status + status: + - 200 diff --git a/pkg/protocols/http/request.go b/pkg/protocols/http/request.go index 0b7a35bc4..222020ebf 100644 --- a/pkg/protocols/http/request.go +++ b/pkg/protocols/http/request.go @@ -501,7 +501,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa } // verify if parallel elaboration was requested - if request.Threads > 0 && len(request.Payloads) > 0 { + if request.Threads > 0 && (len(request.Payloads) > 0 || request.Race) { return request.executeParallelHTTP(input, dynamicValues, callback) }