Skip to content

Commit b63a23b

Browse files
authored
fix(http): pass dynamicValues to EvaluateWithInteractsh (#6685)
* fix(http): pass `dynamicValues` to `EvaluateWithInteractsh` When `LazyEval` is true (triggered by `variables` containing `BaseURL`, `Hostname`, `interactsh-url`, etc.), variable expressions are not eval'ed during YAML parsing & remain as raw exprs like "{{rand_base(5)}}". At request build time, `EvaluateWithInteractsh()` checks if a variable already has a value in the passed map before re-evaluating its expression. But, `dynamicValues` (which contains the template context with previously eval'ed values) was not being passed, causing exprs like `rand_*` to be re-evaluated on each request, producing different values. Fixes #6684 by including `dynamicValues` in the map passed to `EvaluateWithInteractsh()`, so variables evaluated in earlier requests retain their values in subsequent requests. Signed-off-by: Dwi Siswanto <[email protected]> * chore(http): rm early eval in `(*Request).ExecuteWithResults()` Signed-off-by: Dwi Siswanto <[email protected]> * test: adds variables-threads-previous integration test Signed-off-by: Dwi Siswanto <[email protected]> * test: adds constants-with-threads integration test Signed-off-by: Dwi Siswanto <[email protected]> * test: adds race-with-variables integration test Signed-off-by: Dwi Siswanto <[email protected]> --------- Signed-off-by: Dwi Siswanto <[email protected]>
1 parent 8e535f6 commit b63a23b

File tree

6 files changed

+163
-5
lines changed

6 files changed

+163
-5
lines changed

cmd/integration-test/http.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,11 @@ var httpTestcases = []TestCaseInfo{
6262
{Path: "protocols/http/dsl-functions.yaml", TestCase: &httpDSLFunctions{}},
6363
{Path: "protocols/http/race-simple.yaml", TestCase: &httpRaceSimple{}},
6464
{Path: "protocols/http/race-multiple.yaml", TestCase: &httpRaceMultiple{}},
65+
{Path: "protocols/http/race-with-variables.yaml", TestCase: &httpRaceWithVariables{}},
6566
{Path: "protocols/http/stop-at-first-match.yaml", TestCase: &httpStopAtFirstMatch{}},
6667
{Path: "protocols/http/stop-at-first-match-with-extractors.yaml", TestCase: &httpStopAtFirstMatchWithExtractors{}},
6768
{Path: "protocols/http/variables.yaml", TestCase: &httpVariables{}},
69+
{Path: "protocols/http/variables-threads-previous.yaml", TestCase: &httpVariablesThreadsPrevious{}},
6870
{Path: "protocols/http/variable-dsl-function.yaml", TestCase: &httpVariableDSLFunction{}},
6971
{Path: "protocols/http/get-override-sni.yaml", TestCase: &httpSniAnnotation{}},
7072
{Path: "protocols/http/get-sni.yaml", TestCase: &customCLISNI{}},
@@ -77,6 +79,7 @@ var httpTestcases = []TestCaseInfo{
7779
{Path: "protocols/http/cl-body-without-header.yaml", TestCase: &httpCLBodyWithoutHeader{}},
7880
{Path: "protocols/http/cl-body-with-header.yaml", TestCase: &httpCLBodyWithHeader{}},
7981
{Path: "protocols/http/cli-with-constants.yaml", TestCase: &ConstantWithCliVar{}},
82+
{Path: "protocols/http/constants-with-threads.yaml", TestCase: &constantsWithThreads{}},
8083
{Path: "protocols/http/matcher-status.yaml", TestCase: &matcherStatusTest{}},
8184
{Path: "protocols/http/disable-path-automerge.yaml", TestCase: &httpDisablePathAutomerge{}},
8285
{Path: "protocols/http/http-preprocessor.yaml", TestCase: &httpPreprocessor{}},
@@ -1153,6 +1156,26 @@ func (h *httpRaceMultiple) Execute(filePath string) error {
11531156
return expectResultsCount(results, 5)
11541157
}
11551158

1159+
type httpRaceWithVariables struct{}
1160+
1161+
// Execute tests that variables and constants are properly resolved in race mode.
1162+
func (h *httpRaceWithVariables) Execute(filePath string) error {
1163+
router := httprouter.New()
1164+
router.GET("/race", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
1165+
// Echo back the API key header so we can match on it
1166+
_, _ = fmt.Fprint(w, r.Header.Get("X-API-Key"))
1167+
})
1168+
ts := httptest.NewServer(router)
1169+
defer ts.Close()
1170+
1171+
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
1172+
if err != nil {
1173+
return err
1174+
}
1175+
1176+
return expectResultsCount(results, 3)
1177+
}
1178+
11561179
type httpStopAtFirstMatch struct{}
11571180

11581181
// Execute executes a test case and returns an error if occurred
@@ -1220,6 +1243,30 @@ func (h *httpVariables) Execute(filePath string) error {
12201243
return expectResultsCount(results, 0)
12211244
}
12221245

1246+
type httpVariablesThreadsPrevious struct{}
1247+
1248+
// Execute tests that variables can reference data extracted from previous requests
1249+
// when using threads mode (parallel execution).
1250+
func (h *httpVariablesThreadsPrevious) Execute(filePath string) error {
1251+
router := httprouter.New()
1252+
router.GET("/login", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
1253+
_, _ = fmt.Fprint(w, "token=secret123")
1254+
})
1255+
router.GET("/api", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
1256+
// Echo back the Authorization header so we can match on it
1257+
_, _ = fmt.Fprint(w, r.Header.Get("Authorization"))
1258+
})
1259+
ts := httptest.NewServer(router)
1260+
defer ts.Close()
1261+
1262+
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
1263+
if err != nil {
1264+
return err
1265+
}
1266+
1267+
return expectResultsCount(results, 1)
1268+
}
1269+
12231270
type httpVariableDSLFunction struct{}
12241271

12251272
// Execute executes a test case and returns an error if occurred
@@ -1466,6 +1513,26 @@ func (h *ConstantWithCliVar) Execute(filePath string) error {
14661513
return expectResultsCount(got, 1)
14671514
}
14681515

1516+
type constantsWithThreads struct{}
1517+
1518+
// Execute tests that constants are properly resolved when using threads mode.
1519+
func (h *constantsWithThreads) Execute(filePath string) error {
1520+
router := httprouter.New()
1521+
router.GET("/api/:version", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
1522+
// Echo back the API key header and version so we can match on them
1523+
_, _ = fmt.Fprintf(w, "%s %s", r.Header.Get("X-API-Key"), p.ByName("version"))
1524+
})
1525+
ts := httptest.NewServer(router)
1526+
defer ts.Close()
1527+
1528+
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
1529+
if err != nil {
1530+
return err
1531+
}
1532+
1533+
return expectResultsCount(results, 1)
1534+
}
1535+
14691536
type matcherStatusTest struct{}
14701537

14711538
// Execute executes a test case and returns an error if occurred
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
id: constants-with-threads
2+
3+
info:
4+
name: Constants with Threads
5+
author: pdteam
6+
severity: info
7+
description: |
8+
Test that constants are properly resolved when using threads mode.
9+
10+
constants:
11+
api_key: "supersecretkey123"
12+
api_version: "v2"
13+
14+
http:
15+
- method: GET
16+
path:
17+
- "{{BaseURL}}/api/{{api_version}}"
18+
threads: 5
19+
headers:
20+
X-API-Key: "{{api_key}}"
21+
22+
matchers:
23+
- type: word
24+
words:
25+
- "supersecretkey123"
26+
- "v2"
27+
condition: and
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
id: race-with-variables
2+
3+
info:
4+
name: Race Condition with Variables
5+
author: pdteam
6+
severity: info
7+
description: |
8+
Test that variables and constants are properly resolved in race mode.
9+
10+
variables:
11+
random_id: "{{rand_base(8)}}"
12+
13+
constants:
14+
api_key: "racekey123"
15+
16+
http:
17+
- raw:
18+
- |
19+
GET /race HTTP/1.1
20+
Host: {{Hostname}}
21+
X-Request-Id: {{random_id}}
22+
X-API-Key: {{api_key}}
23+
24+
race: true
25+
race_count: 3
26+
27+
matchers:
28+
- type: word
29+
words:
30+
- "racekey123"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
id: variables-threads-previous
2+
3+
info:
4+
name: Variables with Threads and Previous Request Data
5+
author: pdteam
6+
severity: info
7+
description: |
8+
Test that variables can reference data extracted from previous requests
9+
when using threads mode (parallel execution).
10+
11+
variables:
12+
auth_header: "Bearer {{extracted_token}}"
13+
14+
http:
15+
- method: GET
16+
path:
17+
- "{{BaseURL}}/login"
18+
19+
extractors:
20+
- type: regex
21+
name: extracted_token
22+
part: body
23+
regex:
24+
- 'token=([a-z0-9]+)'
25+
group: 1
26+
internal: true
27+
28+
- method: GET
29+
path:
30+
- "{{BaseURL}}/api"
31+
threads: 5
32+
headers:
33+
Authorization: "{{auth_header}}"
34+
35+
matchers:
36+
- type: word
37+
words:
38+
- "Bearer secret123"

pkg/protocols/http/build_request.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context,
209209
// optionvars are vars passed from CLI or env variables
210210
optionVars := generators.BuildPayloadFromOptions(r.request.options.Options)
211211

212-
variablesMap, interactURLs := r.options.Variables.EvaluateWithInteractsh(generators.MergeMaps(defaultReqVars, optionVars), r.options.Interactsh)
212+
variablesMap, interactURLs := r.options.Variables.EvaluateWithInteractsh(generators.MergeMaps(dynamicValues, defaultReqVars, optionVars), r.options.Interactsh)
213213
if len(interactURLs) > 0 {
214214
r.interactshURLs = append(r.interactshURLs, interactURLs...)
215215
}

pkg/protocols/http/request.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -486,10 +486,6 @@ func (request *Request) executeTurboHTTP(input *contextargs.Context, dynamicValu
486486

487487
// ExecuteWithResults executes the final request on a URL
488488
func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
489-
if request.Pipeline || request.Race && request.RaceNumberRequests > 0 || request.Threads > 0 {
490-
variablesMap := request.options.Variables.Evaluate(generators.MergeMaps(dynamicValues, previous))
491-
dynamicValues = generators.MergeMaps(variablesMap, dynamicValues, request.options.Constants)
492-
}
493489
// verify if pipeline was requested
494490
if request.Pipeline {
495491
return request.executeTurboHTTP(input, dynamicValues, previous, callback)

0 commit comments

Comments
 (0)