Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions cmd/integration-test/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ var httpTestcases = []TestCaseInfo{
{Path: "protocols/http/disable-path-automerge.yaml", TestCase: &httpDisablePathAutomerge{}},
{Path: "protocols/http/http-preprocessor.yaml", TestCase: &httpPreprocessor{}},
{Path: "protocols/http/multi-request.yaml", TestCase: &httpMultiRequest{}},
{Path: "protocols/http/multi-request-host-variable-scope.yaml", TestCase: &httpMultiRequestHostVariableScope{}},
{Path: "protocols/http/http-matcher-extractor-dy-extractor.yaml", TestCase: &httpMatcherExtractorDynamicExtractor{}},
{Path: "protocols/http/multi-http-var-sharing.yaml", TestCase: &httpMultiVarSharing{}},
{Path: "protocols/http/raw-path-single-slash.yaml", TestCase: &httpRawPathSingleSlash{}},
Expand Down Expand Up @@ -1707,6 +1708,18 @@ func (h *httpMultiRequest) Execute(filePath string) error {
return expectResultsCount(results, 1)
}

type httpMultiRequestHostVariableScope struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpMultiRequestHostVariableScope) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "http://scanme.sh/?foo=bar", debug)
if err != nil {
return err
}

return expectResultsCount(results, 1)
}

type httpRawPathSingleSlash struct{}

func (h *httpRawPathSingleSlash) Execute(filepath string) error {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
id: http-multi-request-host-variable-scope

info:
name: HTTP multi request host variable scope
author: pdteam
severity: info
description: Ensures request-scoped host variables are derived from each request URL.

http:
- method: GET
path:
- "{{BaseURL}}"

- method: GET
path:
- "http://honey.scanme.sh"
matchers:
- type: dsl
dsl:
- http_2_host == "http://honey.scanme.sh"
10 changes: 7 additions & 3 deletions pkg/protocols/http/build_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,13 @@ func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context,
return nil, errkit.Newf("failed to parse url %v while creating http request", reqData)
}
// while merging parameters first preference is given to target params
finalparams := parsed.Params
finalparams.Merge(reqURL.Params.Encode())
reqURL.Params = finalparams
// only merge target params when the evaluated request host matches input host
// absolute cross-domain URLs should not inherit query params from the original input
if reqURL.Host == parsed.Host {
finalparams := parsed.Params
finalparams.Merge(reqURL.Params.Encode())
reqURL.Params = finalparams
}
Comment on lines +255 to +259
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Normalize host comparison before deciding param merge.

Line 255 compares raw host strings, so equivalent hosts like example.com and example.com:80 can fail the check and skip intended same-host query merging.

💡 Suggested adjustment
-	if reqURL.Host == parsed.Host {
+	normalizeHost := func(scheme, host string) string {
+		h := strings.ToLower(strings.TrimSpace(host))
+		switch scheme {
+		case "http":
+			return strings.TrimSuffix(h, ":80")
+		case "https":
+			return strings.TrimSuffix(h, ":443")
+		default:
+			return h
+		}
+	}
+	if normalizeHost(reqURL.Scheme, reqURL.Host) == normalizeHost(parsed.Scheme, parsed.Host) {
 		finalparams := parsed.Params
 		finalparams.Merge(reqURL.Params.Encode())
 		reqURL.Params = finalparams
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if reqURL.Host == parsed.Host {
finalparams := parsed.Params
finalparams.Merge(reqURL.Params.Encode())
reqURL.Params = finalparams
}
if reqURL.Host == parsed.Host {
normalizeHost := func(scheme, host string) string {
h := strings.ToLower(strings.TrimSpace(host))
switch scheme {
case "http":
return strings.TrimSuffix(h, ":80")
case "https":
return strings.TrimSuffix(h, ":443")
default:
return h
}
}
if normalizeHost(reqURL.Scheme, reqURL.Host) == normalizeHost(parsed.Scheme, parsed.Host) {
finalparams := parsed.Params
finalparams.Merge(reqURL.Params.Encode())
reqURL.Params = finalparams
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/protocols/http/build_request.go` around lines 255 - 259, The host
comparison using reqURL.Host == parsed.Host can mismatch equivalent hosts (e.g.,
example.com vs example.com:80); update the check to normalize hostnames and
ports before merging params: extract hostname via Hostname() and port via Port()
from both reqURL and parsed, treat empty ports as the scheme default (http->80,
https->443), and compare hostname+effectivePort equality; only when they match
call parsed.Params.Merge(reqURL.Params.Encode()) and assign to reqURL.Params
(referencing reqURL, parsed, parsed.Host, reqURL.Host, finalparams,
Params.Merge, Encode).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous implementation didn't consider this. So be it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dwisiswant0, understood! The current fix addresses the main scoping issue effectively. The host normalization edge case can be addressed separately if it becomes a practical concern.


🧠 Learnings used
Learnt from: ayuxsec
Repo: projectdiscovery/nuclei PR: 6769
File: pkg/installer/template.go:217-217
Timestamp: 2026-01-15T10:44:22.186Z
Learning: In projectdiscovery/gologger, the Print() function writes to stderr and Silent() writes to stdout. During reviews, ensure code uses Print() for error/output going to stderr and Silent() for stdout; verify the chosen function matches the intended destination and that logs are directed to the correct stream for proper piping/redirects.

return r.generateHttpRequest(ctx, reqURL, finalVars, payloads)
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/protocols/http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -900,7 +900,7 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
// In case of interactsh markers and request times out, still send
// a callback event so in case we receive an interaction, correlation is possible.
// Also, to log failed use-cases.
outputEvent := request.responseToDSLMap(&http.Response{}, input.MetaInput.Input, formedURL, convUtil.String(dumpedRequest), "", "", "", 0, generatedRequest.meta)
outputEvent := request.responseToDSLMap(&http.Response{}, formedURL, formedURL, convUtil.String(dumpedRequest), "", "", "", 0, generatedRequest.meta)
if i := strings.LastIndex(hostname, ":"); i != -1 {
hostname = hostname[:i]
}
Expand Down Expand Up @@ -1024,7 +1024,7 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
}
}

outputEvent := request.responseToDSLMap(respChain.Response(), input.MetaInput.Input, matchedURL, convUtil.String(dumpedRequest), fullResponseStr, bodyStr, headersStr, duration, generatedRequest.meta)
outputEvent := request.responseToDSLMap(respChain.Response(), matchedURL, matchedURL, convUtil.String(dumpedRequest), fullResponseStr, bodyStr, headersStr, duration, generatedRequest.meta)
// add response fields to template context and merge templatectx variables to output event
request.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, outputEvent)
if request.options.HasTemplateCtx(input.MetaInput) {
Expand Down
Loading