Skip to content

Adding impacket integration#7356

Open
Mzack9999 wants to merge 4 commits intodevfrom
feat-impacket
Open

Adding impacket integration#7356
Mzack9999 wants to merge 4 commits intodevfrom
feat-impacket

Conversation

@Mzack9999
Copy link
Copy Markdown
Member

@Mzack9999 Mzack9999 commented Apr 20, 2026

Proposed changes

Closes #7355

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)

Summary by CodeRabbit

Release Notes

  • New Features

    • Added DCE/RPC client with comprehensive Active Directory operations: SMB share management, user enumeration, computer creation, service execution, scheduled task execution, file operations, and SID resolution
    • Added Kerberos ticket forging capabilities for both golden and silver tickets
    • Added ASRepRoast attack functionality to extract crackable Kerberos hashes
    • Added Kerberoast attack functionality to request service tickets
    • Added DCSync password extraction from domain controllers
    • Added WMI command execution over RPC
  • Tests

    • Added integration test for ASRepRoast operations
  • Chores

    • Updated dependencies for Kerberos and RPC protocol support

@auto-assign auto-assign Bot requested a review from dwisiswant0 April 20, 2026 13:00
@Mzack9999 Mzack9999 self-assigned this Apr 20, 2026
@Mzack9999 Mzack9999 marked this pull request as draft April 20, 2026 13:00
@Mzack9999 Mzack9999 removed the request for review from dwisiswant0 April 20, 2026 13:00
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

Walkthrough

The PR introduces comprehensive Active Directory attack primitives to Nuclei's JavaScript runtime by wrapping pure-Go Kerberos and DCE/RPC implementations. It enables ticket forging, AS-REP/Kerberoasting, DCSync, SMB operations, and WMI execution, all with integrated per-execution network policy enforcement via execution-aware dialers.

Changes

Cohort / File(s) Summary
Dependency Updates
go.mod
Added direct requirements for goimpacket and go-msrpc; bumped go-ldap/v3 from v3.4.11 to v3.4.12; added indirect dependencies including go-ebcdic, goidentity/v6, go-smb2.fork, gokrb5.fork/v9, zerolog.
JavaScript Runtime Integration
pkg/js/compiler/pool.go
Registered four new JS library bindings: libdcerpc, libkrbforge, libkrbroast, libsecretsdump.
DCE/RPC AD Operations
pkg/js/libs/dcerpc/dcerpc.go, pkg/js/libs/dcerpc/transport_init.go, pkg/js/libs/dcerpc/wmiexec.go
Implemented JavaScript-facing DCE/RPC client (Client) supporting SMB connection, RPC service binding (SAMR, LSA, SVCCTL, TSch, EPMAPPER), and endpoint operations (enumerate users, add computer, exec commands via SVCCTL/TSch/WMI, list shares, file operations). Added per-execution transport layer with host allowlisting and execution-scoped dialers.
Kerberos Attack Libraries
pkg/js/libs/krbforge/krbforge.go, pkg/js/libs/krbroast/krbroast.go
Added golden/silver ticket forging via CreateGoldenTicket and CreateSilverTicket; implemented AS-REP roasting and Kerberoasting functions with KDC host validation and per-execution dial policy.
Directory Synchronization
pkg/js/libs/secretsdump/secretsdump.go
Implemented DCSync client and Secret type exposing DRSUAPI IDL_DRSGetNCChanges for domain object replication, supporting NT/LM hash extraction with history and pass-the-hash mode.
Integration Tests
internal/tests/integration/javascript_krbroast_test.go, internal/tests/integration/javascript_test.go
Added end-to-end test for AS-REP roasting against a mock Kerberos KDC (TCP, length-prefixed KRB protocol); registered new test case in harness.

Sequence Diagram(s)

sequenceDiagram
    participant JS as JavaScript<br/>Template
    participant KRB as krbroast<br/>Module
    participant KDC as Kerberos KDC
    participant SEC as Host Policy<br/>Enforcer

    JS->>KRB: ASRepRoast(username, domain, kdcHost)
    KRB->>SEC: IsHostAllowed(execId, kdcHost)
    alt Host Denied
        SEC-->>KRB: ErrHostDenied
        KRB-->>JS: throw error
    else Host Allowed
        SEC-->>KRB: proceed
        KRB->>KDC: SendASRequest(username, domain)
        KDC-->>KRB: ASREPResponse
        KRB->>KRB: Extract & format hash
        KRB-->>JS: cracking hash string
    end
Loading
sequenceDiagram
    participant JS as JavaScript<br/>Template
    participant SD as secretsdump<br/>Module
    participant SEC as Host Policy<br/>Enforcer
    participant SMB as SMB Client
    participant DC as Domain Controller<br/>DRSUAPI

    JS->>SD: DCSync(target, dcHost)
    SD->>SEC: IsHostAllowed(execId, dcHost)
    alt Host Denied
        SEC-->>SD: ErrHostDenied
        SD-->>JS: throw error
    else Host Allowed
        SEC-->>SD: proceed
        SD->>SMB: connect(dcHost:445)
        SMB->>DC: authenticate
        DC-->>SMB: authenticated session
        SD->>DC: openRpcPipe(lsass or PIPE\lsass)
        SD->>DC: bindDRSUAPI()
        SD->>DC: DsGetNCChanges(targetDN)
        DC-->>SD: encrypted attributes
        SD->>SD: decode hashes & history
        SD-->>JS: Secret{SAMName, hashes, history...}
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Poem

🐰 A hops-and-bounds ballad of AD affairs...

Through goimpacket's burrows I bound,
With Kerberos tickets and SMB sound,
DCSync secrets the DC unfurled,
While host-policy keeps the bad folks quarled,
JavaScript templates now raid the realm! 🔐

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Adding impacket integration' clearly summarizes the main objective of the changeset, which adds impacket-style AD attack primitives to Nuclei via JavaScript modules.
Linked Issues check ✅ Passed The PR successfully implements all requirements from issue #7355: JS modules wrapping goimpacket fork [#7355], AD red-team primitives via SMB/DCERPC/Kerberoasting/DCSync/ticket-forging [#7355], and fastdialer integration for network connections [#7355].
Out of Scope Changes check ✅ Passed All changes are scoped to implementing impacket integration: new JS libraries (dcerpc, krbforge, krbroast, secretsdump), integration tests, dependency updates, and compiler pool registration. No unrelated modifications found.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-impacket

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.

@Mzack9999 Mzack9999 marked this pull request as ready for review April 22, 2026 12:16
Copy link
Copy Markdown
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: 10

🧹 Nitpick comments (2)
pkg/js/libs/dcerpc/dcerpc.go (2)

434-440: FileEntry type declaration is oddly placed inside the SmbLs documentation block.

The FileEntry struct is inserted between SmbLs's godoc comment (lines 426-433) and its function signature (line 440). go doc will not associate the comment with SmbLs correctly and the type appears as if documented by the SmbLs example. Move the FileEntry declaration above the doc comment (or to the top of the file with the other type aliases) so godoc renders as intended.

🔧 Suggested change
+type FileEntry struct {
+	Name  string `json:"name"`
+	Size  int64  `json:"size"`
+	IsDir bool   `json:"is_dir"`
+}
+
 // SmbLs lists files under dir on the given share. dir = "" lists the root.
 //
 // `@example`
 // ```javascript
 // const c = new dcerpc.Client('dc01', 'acme.local', 'admin', 'P@ss');
 // const entries = c.SmbLs('backup', '');
 // for (const e of entries) { log(e.Name + (e.IsDir ? '/' : '')); }
 // ```
-type FileEntry struct {
-	Name  string `json:"name"`
-	Size  int64  `json:"size"`
-	IsDir bool   `json:"is_dir"`
-}
-
 func (c *Client) SmbLs(share, dir string) ([]FileEntry, error) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/js/libs/dcerpc/dcerpc.go` around lines 434 - 440, The FileEntry struct is
placed between the SmbLs godoc and its function signature which breaks godoc
association; move the FileEntry type declaration (type FileEntry struct { Name
string; Size int64; IsDir bool }) out of the SmbLs comment block — either place
it immediately above the SmbLs godoc or with the file's other type
declarations/aliases — so the existing godoc comment attaches to func (c
*Client) SmbLs(share, dir string) ([]FileEntry, error) correctly.

321-325: 10s SmbExec timeout is likely to truncate real-world output.

gpsmbexec.Exec is used to create a service, start it, and poll back stdout/stderr via SMB. Commands like whoami /all, PowerShell one-liners, or anything that touches WMI routinely take longer than 10s end-to-end on busy DCs. This short timeout will frequently return a partial Output or a timeout error for otherwise-successful executions, which templates will report as scan failures. AtExec uses 15s on line 382 which has the same concern but is less aggressive.

Consider either raising the default (30–60s) or exposing an options argument so callers can size the timeout for their workload.

🔧 Suggested change
 	res, err := gpsmbexec.Exec(sc, c.smb, command, gpsmbexec.Options{
 		Share:   share,
 		Mode:    gpsmbexec.ModeShare,
-		Timeout: 10 * time.Second,
+		Timeout: 30 * time.Second,
 	})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/js/libs/dcerpc/dcerpc.go` around lines 321 - 325, The current
gpsmbexec.Exec call in dcerpc.go uses a 10s Timeout which often truncates
real-world command output; update the call that constructs gpsmbexec.Options
(used in the Exec invocation) to use a larger default (e.g., 30–60s, recommend
60s) or refactor to accept a configurable timeout parameter from the caller path
so callers can size it; also align the AtExec usage (which currently uses 15s)
to the same approach so both Exec and AtExec use a consistent, higher timeout or
accept the same configurable option.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/js/libs/dcerpc/dcerpc.go`:
- Around line 414-424: SmbCat currently reads and returns an entire file into
memory via c.smb.Cat without size or time limits; update Client.SmbCat to
enforce a maximum byte cap (e.g., 10 MiB default) and a timeout/ctx-aware
operation: replace the direct call to c.smb.Cat with a streamed read using an
io.LimitedReader (or by adding a size limit parameter to the underlying smb read
method), stop reading and return a clear "size limit exceeded" error if the
limit is hit, and ensure the operation respects a context or connection deadline
(pass a context.Context or set a read timeout on the connection) so untrusted
templates cannot OOM or hang; optionally expose a size-limit argument on SmbCat
to allow callers to override the default.
- Around line 128-131: SetPort currently accepts any int and can allow
out-of-range values; change its signature from func (c *Client) SetPort(port
int) to return an error (func (c *Client) SetPort(port int) error), validate
that port is within 1..65535 (or 0..65535 if 0 is intentionally allowed —
confirm policy) before assigning, and return a descriptive error if invalid; on
success set c.Port and c.target.Port (matching gpsession.Target.Port type) and
return nil. Update any callers to handle the returned error.
- Around line 153-158: The Close method on Client currently calls c.smb.Close()
but leaves c.smb non-nil, risking double-close and leaks; update Client.Close
(and ensure consistency with connect) to set c.smb = nil after successfully
closing the underlying *gpsmb.Client and keep c.started = false so repeated
Close() calls become no-ops and subsequent connect() won't leak the old
reference.

In `@pkg/js/libs/dcerpc/transport_init.go`:
- Around line 35-38: NewExecDialer currently returns a zero-value
gptransport.Dialer when execID == "", which relies on zero-value semantics;
change it to return a failing/closed dialer by setting DialFn to a function that
always returns an error (similar to the global init hook). Update NewExecDialer
to detect execID == "" and return &gptransport.Dialer{DialFn: func(ctx
context.Context, addr string) (net.Conn, error) { return nil, fmt.Errorf("no
execID provided") }} (use appropriate context/net/error packages already used in
the file) so callers like dcerpc.go using ExecutionId() get an explicit failure
instead of ambiguous zero-value behavior.

In `@pkg/js/libs/dcerpc/wmiexec.go`:
- Around line 59-60: Replace the hardcoded context.Background() passed to
gpwmiexec.Exec with the incoming execution context so cancellations/deadlines
propagate; wrap the provided ctx in a 15s timeout (using context.WithTimeout and
deferring cancel()) when you need the 15s limit, and pass that ctxWithTimeout to
gpwmiexec.Exec (update the call site in wmiexec.go where gpwmiexec.Exec is
invoked).

In `@pkg/js/libs/krbroast/krbroast.go`:
- Around line 106-109: Kerberoast currently calls GetTGSWithOptions without a
dialer so its TCP connection bypasses the fastdialer used by
ASRepRoast/GetASREPWithDialer; add a dialer-aware TGS API and wire it through
the JS binding: update the Go API by adding GetTGSWithDialer (or extend
GetTGSWithOptions to accept a net.Dialer-like interface) and implement the
dialer usage inside the function that currently performs the TGS request, then
change Kerberoast to call the new GetTGSWithDialer (and update any JS binding
wrappers) so the KDC connection is created via the provided execution-bound
dialer used for proxy/resolver/timeout/policy handling.

In `@pkg/js/libs/secretsdump/secretsdump.go`:
- Around line 108-110: The error uses protocolstate.ErrHostDenied.Msgf with
c.Host as the format string which can mis-handle '%' in host values; update the
call in secretsdump.go (and the similar sites in dcerpc.go) to pass a format
string and the host as an argument (e.g. use Msgf("%s", c.Host)) so the host is
treated as data, not a format specifier; search for ErrHostDenied.Msgf(...) and
replace direct host-as-format usages with a "%s" format and c.Host as the
parameter.
- Around line 118-132: The OpenPipe fallback comment is misleading and there is
a resource leak if gprpc.NewClient(pipe).BindAuth(...) fails because
rpc.Transport.Close() is deferred only after the bind succeeds; ensure the pipe
is closed on bind failure and fix the comment: after calling
smb.OpenPipe("\\PIPE\\lsass") and falling back to smb.OpenPipe("lsass") update
the comment to reflect that the fallback uses the alternate lsass pipe name (not
"drsuapi"), then either register the defer rpc.Transport.Close() immediately
after creating rpc (so it always runs) or explicitly close the
pipe/rpc.Transport when rpc.BindAuth returns an error (refer to OpenPipe,
gprpc.NewClient, rpc.BindAuth, rpc.Transport.Close and follow the
rpcOverNamedPipe pattern to close the pipe on bind failure).
- Line 112: The call to gpsmb.NewClient(c.target, c.creds) in secretsdump
bypasses nuclei’s fastdialer and per-execution network policy; replace it with
the dialer-aware constructor used by dcerpc.Client.connect() (e.g.,
gpsmb.NewClientWithDialer(c.target, c.creds, NewExecDialer(c.nj.ExecutionId())))
so DNS, proxy, resolved-IP allow/deny and timeouts are enforced at dial time,
and ensure IsHostAllowed still runs against c.target before dialing; import the
dcerpc package or move NewExecDialer to a shared helper to avoid an import
cycle.
- Around line 149-160: The DN-detection currently checks target[:3] for "CN="
which misclassifies valid DNs that start with other RDNs; update the heuristic
in the block that assigns userDN (and the conditional before calling
gpdrs.DsCrackNames) to use strings.ContainsRune(target, '=') (i.e., treat as DN
if it contains '=') instead of the prefix CN check, and add "strings" to the
import list so strings.ContainsRune can be used; keep the rest of the
DsCrackNames calls (including DS_NT4_ACCOUNT_NAME/DS_UNIQUE_ID_NAME) unchanged.

---

Nitpick comments:
In `@pkg/js/libs/dcerpc/dcerpc.go`:
- Around line 434-440: The FileEntry struct is placed between the SmbLs godoc
and its function signature which breaks godoc association; move the FileEntry
type declaration (type FileEntry struct { Name string; Size int64; IsDir bool })
out of the SmbLs comment block — either place it immediately above the SmbLs
godoc or with the file's other type declarations/aliases — so the existing godoc
comment attaches to func (c *Client) SmbLs(share, dir string) ([]FileEntry,
error) correctly.
- Around line 321-325: The current gpsmbexec.Exec call in dcerpc.go uses a 10s
Timeout which often truncates real-world command output; update the call that
constructs gpsmbexec.Options (used in the Exec invocation) to use a larger
default (e.g., 30–60s, recommend 60s) or refactor to accept a configurable
timeout parameter from the caller path so callers can size it; also align the
AtExec usage (which currently uses 15s) to the same approach so both Exec and
AtExec use a consistent, higher timeout or accept the same configurable option.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 414976f4-cb52-4f14-b483-3e52f7db98b6

📥 Commits

Reviewing files that changed from the base of the PR and between ad4f3ff and 211c6a1.

⛔ Files ignored due to path filters (14)
  • go.sum is excluded by !**/*.sum
  • internal/tests/integration/testdata/protocols/javascript/asrep-roast.yaml is excluded by !**/*.yaml
  • pkg/js/generated/go/libdcerpc/dcerpc.go is excluded by !**/generated/**
  • pkg/js/generated/go/libkrbforge/krbforge.go is excluded by !**/generated/**
  • pkg/js/generated/go/libkrbroast/krbroast.go is excluded by !**/generated/**
  • pkg/js/generated/go/librdp/rdp.go is excluded by !**/generated/**
  • pkg/js/generated/go/librsync/rsync.go is excluded by !**/generated/**
  • pkg/js/generated/go/libsecretsdump/secretsdump.go is excluded by !**/generated/**
  • pkg/js/generated/go/libtelnet/telnet.go is excluded by !**/generated/**
  • pkg/js/generated/ts/dcerpc.ts is excluded by !**/generated/**
  • pkg/js/generated/ts/index.ts is excluded by !**/generated/**
  • pkg/js/generated/ts/krbforge.ts is excluded by !**/generated/**
  • pkg/js/generated/ts/krbroast.ts is excluded by !**/generated/**
  • pkg/js/generated/ts/secretsdump.ts is excluded by !**/generated/**
📒 Files selected for processing (10)
  • go.mod
  • internal/tests/integration/javascript_krbroast_test.go
  • internal/tests/integration/javascript_test.go
  • pkg/js/compiler/pool.go
  • pkg/js/libs/dcerpc/dcerpc.go
  • pkg/js/libs/dcerpc/transport_init.go
  • pkg/js/libs/dcerpc/wmiexec.go
  • pkg/js/libs/krbforge/krbforge.go
  • pkg/js/libs/krbroast/krbroast.go
  • pkg/js/libs/secretsdump/secretsdump.go

Comment on lines +128 to +131
func (c *Client) SetPort(port int) {
c.Port = port
c.target.Port = port
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

SetPort accepts any int (including 0, negative, or >65535).

gpsession.Target.Port is presumably uint16 or validated downstream, but with an int parameter this can silently accept out-of-range ports that then either panic, wrap, or fail obscurely at connect time. Reject invalid ports up front to surface a clear JS-side error.

🔧 Suggested fix
 func (c *Client) SetPort(port int) {
+	c.nj.Require(port > 0 && port <= 65535, "port must be in range 1-65535")
 	c.Port = port
 	c.target.Port = port
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/js/libs/dcerpc/dcerpc.go` around lines 128 - 131, SetPort currently
accepts any int and can allow out-of-range values; change its signature from
func (c *Client) SetPort(port int) to return an error (func (c *Client)
SetPort(port int) error), validate that port is within 1..65535 (or 0..65535 if
0 is intentionally allowed — confirm policy) before assigning, and return a
descriptive error if invalid; on success set c.Port and c.target.Port (matching
gpsession.Target.Port type) and return nil. Update any callers to handle the
returned error.

Comment on lines +153 to +158
func (c *Client) Close() {
if c.smb != nil {
c.smb.Close()
}
c.started = false
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether gpsmb.Client.Close is safe to call multiple times.
rg -nP -C3 'func \(.*\*Client\) Close\(\)' --type=go

Repository: projectdiscovery/nuclei

Length of output: 1627


🏁 Script executed:

# Get full context around the Close() method
cat -n pkg/js/libs/dcerpc/dcerpc.go | sed -n '140,165p'

Repository: projectdiscovery/nuclei

Length of output: 996


🏁 Script executed:

# Search for usage of dcerpc.Client.Close()
rg -n 'Close\(\)' pkg/js/libs/dcerpc/ -A 2 -B 2

Repository: projectdiscovery/nuclei

Length of output: 3390


🏁 Script executed:

# Find how gpsmb is imported and used
rg -n 'gpsmb' pkg/js/libs/dcerpc/dcerpc.go -B 2 -A 2

Repository: projectdiscovery/nuclei

Length of output: 905


🏁 Script executed:

# Look for connect() method to understand the started flag behavior
rg -n 'func.*\(c \*Client\) connect' pkg/js/libs/dcerpc/dcerpc.go -A 10

Repository: projectdiscovery/nuclei

Length of output: 470


🏁 Script executed:

# Search for calls to dcerpc.Client Close method
rg -n '\.Close\(\)' pkg/js/libs/dcerpc/dcerpc.go | head -20

Repository: projectdiscovery/nuclei

Length of output: 397


🏁 Script executed:

# Check if there are JS bindings/exports and how Close is used from JS
rg -n 'NewClient\|Close' pkg/js/libs/dcerpc/dcerpc.go | grep -E '(func New|Close)'

Repository: projectdiscovery/nuclei

Length of output: 49


🏁 Script executed:

# Look for any template or deferred cleanup patterns mentioned in the comment
rg -n 'defer.*Close\|finally' pkg/js/libs/dcerpc/ --type=go

Repository: projectdiscovery/nuclei

Length of output: 49


🏁 Script executed:

# Check if this Client and Close are exported/exposed to JS
rg -n 'Register\|Export\|Runtime' pkg/js/libs/dcerpc/dcerpc.go | head -20

Repository: projectdiscovery/nuclei

Length of output: 49


🏁 Script executed:

# Look at the top of the file for JS runtime bindings
head -80 pkg/js/libs/dcerpc/dcerpc.go

Repository: projectdiscovery/nuclei

Length of output: 2980


🏁 Script executed:

# Search for any JS template examples or usage patterns of dcerpc Client
find . -name '*.yaml' -o -name '*.yml' | xargs grep -l 'dcerpc' 2>/dev/null | head -5

Repository: projectdiscovery/nuclei

Length of output: 49


🏁 Script executed:

# Check if there are tests that show how Close() should be used
rg -n 'dcerpc.*Close\|Close.*dcerpc' . --type=go | head -10

Repository: projectdiscovery/nuclei

Length of output: 49


🏁 Script executed:

# Look for any documentation or comments about idempotence or double-close
rg -n 'idempotent\|double.*close\|close.*twice' pkg/js/libs/dcerpc/ --type=go

Repository: projectdiscovery/nuclei

Length of output: 49


🏁 Script executed:

# Look at the entire Client struct and any cleanup patterns
sed -n '48,80p' pkg/js/libs/dcerpc/dcerpc.go

Repository: projectdiscovery/nuclei

Length of output: 1068


🏁 Script executed:

# Check if there are any defer statements that use this client or call Close
rg -n 'defer' pkg/js/libs/dcerpc/dcerpc.go

Repository: projectdiscovery/nuclei

Length of output: 319


🏁 Script executed:

# Verify the suggested fix would compile and make sense
cat -n pkg/js/libs/dcerpc/dcerpc.go | sed -n '150,165p'

Repository: projectdiscovery/nuclei

Length of output: 600


🏁 Script executed:

# Check if gpsmb.Client is from an external package and verify it's not safe for double-close
rg -n 'github.com/Mzack9999/goimpacket' pkg/js/libs/dcerpc/dcerpc.go | head -5

Repository: projectdiscovery/nuclei

Length of output: 366


🏁 Script executed:

# Look for any linter/fmt/vet issues in the file
go fmt -d pkg/js/libs/dcerpc/dcerpc.go 2>&1 || echo "No fmt issues"

Repository: projectdiscovery/nuclei

Length of output: 180


🏁 Script executed:

# Run go vet on this file to check for any obvious issues
go vet ./pkg/js/libs/dcerpc/ 2>&1 | head -20

Repository: projectdiscovery/nuclei

Length of output: 49


🏁 Script executed:

# Verify the fix compiles by checking syntax
cat > /tmp/test_close.go << 'EOF'
package dcerpc

type smb struct{}
func (s *smb) Close() {}

type Client struct {
	smb     *smb
	started bool
}

func (c *Client) Close() {
	if c.smb != nil {
		c.smb.Close()
		c.smb = nil
	}
	c.started = false
}
EOF
go fmt /tmp/test_close.go && echo "Fix syntax is valid"

Repository: projectdiscovery/nuclei

Length of output: 105


Clear the SMB client pointer after closing to ensure idempotence.

The Close() method does not set c.smb = nil after calling c.smb.Close(). This creates two problems:

  1. Calling Close() a second time will invoke Close() on an already-closed *gpsmb.Client from the external goimpacket package, which may panic or error.
  2. If connect() is called after Close() with c.smb still non-nil, it will overwrite the pointer without cleaning up the old reference, creating a resource leak.

Since this is a JS binding exposed to templates, user code may call Close() from both finally blocks and deferred cleanup paths, making defensive programming essential.

🔧 Suggested fix
 func (c *Client) Close() {
 	if c.smb != nil {
 		c.smb.Close()
+		c.smb = nil
 	}
 	c.started = false
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/js/libs/dcerpc/dcerpc.go` around lines 153 - 158, The Close method on
Client currently calls c.smb.Close() but leaves c.smb non-nil, risking
double-close and leaks; update Client.Close (and ensure consistency with
connect) to set c.smb = nil after successfully closing the underlying
*gpsmb.Client and keep c.started = false so repeated Close() calls become no-ops
and subsequent connect() won't leak the old reference.

Comment on lines +414 to +424
func (c *Client) SmbCat(share, file string) (string, error) {
c.nj.Require(share != "", "share cannot be empty")
c.nj.Require(file != "", "file cannot be empty")
if err := c.connect(); err != nil {
return "", err
}
if err := c.smb.UseShare(share); err != nil {
return "", fmt.Errorf("use share %s: %w", share, err)
}
return c.smb.Cat(file)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

SmbCat has no size cap and returns the whole file as a Go string.

A malicious or misconfigured target can trivially OOM the scanner by pointing SmbCat at a multi-GB file on a share — there is no max-size limit, no streaming, and no context/timeout. Since this is invoked from untrusted template logic running against arbitrary hosts, consider capping the maximum bytes read (e.g. 10 MiB by default) and/or exposing a size-limit argument, and returning a clear error when exceeded.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/js/libs/dcerpc/dcerpc.go` around lines 414 - 424, SmbCat currently reads
and returns an entire file into memory via c.smb.Cat without size or time
limits; update Client.SmbCat to enforce a maximum byte cap (e.g., 10 MiB
default) and a timeout/ctx-aware operation: replace the direct call to c.smb.Cat
with a streamed read using an io.LimitedReader (or by adding a size limit
parameter to the underlying smb read method), stop reading and return a clear
"size limit exceeded" error if the limit is hit, and ensure the operation
respects a context or connection deadline (pass a context.Context or set a read
timeout on the connection) so untrusted templates cannot OOM or hang; optionally
expose a size-limit argument on SmbCat to allow callers to override the default.

Comment on lines +35 to +38
func NewExecDialer(execID string) *gptransport.Dialer {
if execID == "" {
return &gptransport.Dialer{}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

github.com/Mzack9999/goimpacket pkg/transport Dialer zero value DialFn behavior

💡 Result:

No public repository found at github.com/Mzack9999/goimpacket. Searches across GitHub, pkg.go.dev, and general web returned no matching results for the exact path or package with a pkg/transport Dialer DialFn. Mzack9999 has contributions to projectdiscovery projects like goflags and fastdialer (which depends on Mzack9999/gcache), but no goimpacket repo. Similar projects exist like amzza0x00/go-impacket or Enelg52/go-impacket (SMB/DCERPC implementations), but not matching the path. The repository may be private, deleted, or the path is incorrect. Without source code, cannot determine zero-value DialFn behavior for Dialer in pkg/transport.

Citations:


🏁 Script executed:

# Locate the file mentioned in the review
find . -name "transport_init.go" -type f

Repository: projectdiscovery/nuclei

Length of output: 105


🏁 Script executed:

# Check if goimpacket is imported and understand the dependency
git ls-files | grep -E "(go\.mod|go\.sum)" | head -5

Repository: projectdiscovery/nuclei

Length of output: 80


🏁 Script executed:

# Get the go.mod content to understand dependencies
cat go.mod | head -50

Repository: projectdiscovery/nuclei

Length of output: 1829


🏁 Script executed:

# Check for goimpacket in go.mod
cat go.mod | grep -i "goimpacket\|gptransport"

Repository: projectdiscovery/nuclei

Length of output: 134


🏁 Script executed:

# Read the transport_init.go file to see the actual implementation
cat -n ./pkg/js/libs/dcerpc/transport_init.go

Repository: projectdiscovery/nuclei

Length of output: 3335


🏁 Script executed:

# Search for gptransport package definition in the codebase
find . -type f -name "*.go" | xargs grep -l "gptransport" | head -10

Repository: projectdiscovery/nuclei

Length of output: 105


🏁 Script executed:

# Search for usages of NewExecDialer
rg "NewExecDialer" --type go

Repository: projectdiscovery/nuclei

Length of output: 885


🏁 Script executed:

# Check if there are any other transport initializations or gptransport.Dialer uses
rg "gptransport\.Dialer" --type go

Repository: projectdiscovery/nuclei

Length of output: 748


🏁 Script executed:

# Look for patterns where execID might be empty when NewExecDialer is called
rg -A 3 -B 3 "NewExecDialer\(\)" --type go

Repository: projectdiscovery/nuclei

Length of output: 49


🏁 Script executed:

# Check dcerpc.go to see the context of NewExecDialer usage
cat -n ./pkg/js/libs/dcerpc/dcerpc.go | grep -A 5 -B 5 "NewExecDialer"

Repository: projectdiscovery/nuclei

Length of output: 482


🏁 Script executed:

# Check krbroast.go to see the context of NewExecDialer usage
cat -n ./pkg/js/libs/krbroast/krbroast.go | grep -A 5 -B 5 "NewExecDialer"

Repository: projectdiscovery/nuclei

Length of output: 457


🏁 Script executed:

# Check what ExecutionId() returns - look for its definition
rg "ExecutionId\(" --type go -A 2 | head -40

Repository: projectdiscovery/nuclei

Length of output: 2175


🏁 Script executed:

# Check the ExecutionId() implementation to understand if it can legitimately return ""
cat -n ./pkg/js/utils/nucleijs.go | grep -A 10 "func (j \*NucleiJS) ExecutionId"

Repository: projectdiscovery/nuclei

Length of output: 452


🏁 Script executed:

# Check if there are defensive guards in dcerpc.go before calling NewExecDialer
cat -n ./pkg/js/libs/dcerpc/dcerpc.go | head -150 | tail -40

Repository: projectdiscovery/nuclei

Length of output: 1533


🏁 Script executed:

# Go fmt and go vet checks for the file
go fmt ./pkg/js/libs/dcerpc/transport_init.go && echo "Format OK" || echo "Format issues"

Repository: projectdiscovery/nuclei

Length of output: 76


🏁 Script executed:

# Run go vet on the transport_init.go file
go vet ./pkg/js/libs/dcerpc/transport_init.go

Repository: projectdiscovery/nuclei

Length of output: 49


Return an explicitly failing dialer when execID is empty.

NewExecDialer("") currently returns a zero-value gptransport.Dialer{}, leaving behavior dependent on goimpacket's internal zero-value semantics. ExecutionId() can legitimately return "" when not set in context, and dcerpc.go calls NewExecDialer(c.nj.ExecutionId()) without guarding against this case. Make the returned dialer fail closed by setting DialFn to error explicitly when execID is empty, consistent with the defensive pattern in the global init hook.

Proposed fail-closed change
 func NewExecDialer(execID string) *gptransport.Dialer {
 	if execID == "" {
-		return &gptransport.Dialer{}
+		return &gptransport.Dialer{
+			DialFn: func(_ context.Context, network, address string) (net.Conn, error) {
+				return nil, fmt.Errorf("goimpacket: refusing to dial %s/%s without an executionId", network, address)
+			},
+		}
 	}
 	return &gptransport.Dialer{
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/js/libs/dcerpc/transport_init.go` around lines 35 - 38, NewExecDialer
currently returns a zero-value gptransport.Dialer when execID == "", which
relies on zero-value semantics; change it to return a failing/closed dialer by
setting DialFn to a function that always returns an error (similar to the global
init hook). Update NewExecDialer to detect execID == "" and return
&gptransport.Dialer{DialFn: func(ctx context.Context, addr string) (net.Conn,
error) { return nil, fmt.Errorf("no execID provided") }} (use appropriate
context/net/error packages already used in the file) so callers like dcerpc.go
using ExecutionId() get an explicit failure instead of ambiguous zero-value
behavior.

Comment on lines +59 to +60
res, err := gpwmiexec.Exec(
context.Background(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Propagate the execution context instead of using context.Background().

This drops scan cancellation/deadlines, so WMI/DCOM/SMB work can continue after the nuclei execution is cancelled. Use the JS/execution context here, wrapping it with the 15s timeout if needed.

🛡️ Suggested shape
+	ctx, cancel := context.WithTimeout(executionCtx, 15*time.Second)
+	defer cancel()
+
 	res, err := gpwmiexec.Exec(
-		context.Background(),
+		ctx,
 		target,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/js/libs/dcerpc/wmiexec.go` around lines 59 - 60, Replace the hardcoded
context.Background() passed to gpwmiexec.Exec with the incoming execution
context so cancellations/deadlines propagate; wrap the provided ctx in a 15s
timeout (using context.WithTimeout and deferring cancel()) when you need the 15s
limit, and pass that ctxWithTimeout to gpwmiexec.Exec (update the call site in
wmiexec.go where gpwmiexec.Exec is invoked).

Comment on lines +106 to +109
// Note: the underlying jcmturner/gokrb5 client used by GetTGSWithOptions
// performs its own net.Dial that is not routed through nuclei's fastdialer.
// The host is still pre-validated against the per-execution network policy so
// allowlist / denylist constraints are enforced before any traffic is sent.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

github.com/Mzack9999/goimpacket kerberos GetTGSWithOptions custom dialer fastdialer

💡 Result:

No public GitHub repository exists at github.com/Mzack9999/goimpacket. Mzack9999 (likely mzack from projectdiscovery) maintains a fork of gopacket at github.com/mzack9999/gopacket, a packet decoding library unrelated to Impacket or Kerberos. Existing Go Impacket implementations like github.com/amzza0x00/go-impacket (SMB2/DCE/RPC, no Kerberos mentioned) and github.com/Enelg52/go-impacket (SMB2/DCE/RPC) do not reference GetTGSWithOptions, custom dialer, or fastdialer. Mandiant's gopacket has Kerberos tools (getTGT, getST) but no GetTGSWithOptions or dialer options. Fastdialer (github.com/projectdiscovery/fastdialer, contributed by Mzack9999) is a separate optimized net.Dialer with DNS cache, potentially usable for custom connections in network tools. The query likely refers to a private/unreleased project or internal feature for Kerberos TGS requests with customizable dialers (e.g., fastdialer for performance) in a Go Impacket-like library. No official docs or code found for the exact API.

Citations:


🏁 Script executed:

cat -n pkg/js/libs/krbroast/krbroast.go | head -180

Repository: projectdiscovery/nuclei

Length of output: 6908


Add dialer-aware TGS API to route Kerberoast through fastdialer.

ASRepRoast routes through GetASREPWithDialer (line 72) while Kerberoast calls GetTGSWithOptions (lines 153–161) with no dialer parameter. This asymmetry bypasses the fastdialer infrastructure that ASRepRoast uses for proxy/resolver/timeout/policy handling. Pre-validating the KDC host does not substitute for routing the actual connection through the execution-bound dialer.

Update GetTGSWithOptions (or add a GetTGSWithDialer variant) to accept a custom dialer parameter before wiring the JS binding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/js/libs/krbroast/krbroast.go` around lines 106 - 109, Kerberoast
currently calls GetTGSWithOptions without a dialer so its TCP connection
bypasses the fastdialer used by ASRepRoast/GetASREPWithDialer; add a
dialer-aware TGS API and wire it through the JS binding: update the Go API by
adding GetTGSWithDialer (or extend GetTGSWithOptions to accept a net.Dialer-like
interface) and implement the dialer usage inside the function that currently
performs the TGS request, then change Kerberoast to call the new
GetTGSWithDialer (and update any JS binding wrappers) so the KDC connection is
created via the provided execution-bound dialer used for
proxy/resolver/timeout/policy handling.

Comment on lines +108 to +110
if !protocolstate.IsHostAllowed(c.nj.ExecutionId(), c.Host) {
return nil, protocolstate.ErrHostDenied.Msgf(c.Host)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

ErrHostDenied.Msgf(c.Host) passes host as a format string.

Msgf is a printf-style formatter. If c.Host ever contains a literal % (rare but valid in hostnames-as-arbitrary-strings, e.g. when users paste IPv6 zone identifiers like fe80::1%eth0), this will either double-format or emit %! verbs. Pass the host as an argument instead. Same pattern appears in dcerpc.go (lines 142, 190, 297, 362).

🔧 Suggested fix
-		return nil, protocolstate.ErrHostDenied.Msgf(c.Host)
+		return nil, protocolstate.ErrHostDenied.Msgf("%s", c.Host)
📝 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 !protocolstate.IsHostAllowed(c.nj.ExecutionId(), c.Host) {
return nil, protocolstate.ErrHostDenied.Msgf(c.Host)
}
if !protocolstate.IsHostAllowed(c.nj.ExecutionId(), c.Host) {
return nil, protocolstate.ErrHostDenied.Msgf("%s", c.Host)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/js/libs/secretsdump/secretsdump.go` around lines 108 - 110, The error
uses protocolstate.ErrHostDenied.Msgf with c.Host as the format string which can
mis-handle '%' in host values; update the call in secretsdump.go (and the
similar sites in dcerpc.go) to pass a format string and the host as an argument
(e.g. use Msgf("%s", c.Host)) so the host is treated as data, not a format
specifier; search for ErrHostDenied.Msgf(...) and replace direct host-as-format
usages with a "%s" format and c.Host as the parameter.

return nil, protocolstate.ErrHostDenied.Msgf(c.Host)
}

smb := gpsmb.NewClient(c.target, c.creds)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Bypasses nuclei's fastdialer / per-execution network policy.

Unlike dcerpc.Client.connect() which uses gpsmb.NewClientWithDialer(c.target, c.creds, NewExecDialer(c.nj.ExecutionId())), this helper calls gpsmb.NewClient(c.target, c.creds) directly. The pre-flight IsHostAllowed check on line 108 only validates the originally-supplied host string; the actual TCP dial inside goimpacket will go straight through the default transport, so proxy settings, DNS cache, resolved-IP allow/deny, and timeouts enforced by nuclei's fastdialer are not applied. This contradicts the PR objective that all network connections go through the existing fastdialer infrastructure, and also means the executionId-scoped policy is not enforced at dial-time.

🔧 Suggested fix
-	smb := gpsmb.NewClient(c.target, c.creds)
+	smb := gpsmb.NewClientWithDialer(c.target, c.creds, dcerpc.NewExecDialer(c.nj.ExecutionId()))
 	if err := smb.Connect(); err != nil {
 		return nil, fmt.Errorf("smb connect: %w", err)
 	}

(and add an import for the dcerpc package, or move NewExecDialer to a shared helper so both secretsdump and dcerpc can use it without introducing a cycle.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/js/libs/secretsdump/secretsdump.go` at line 112, The call to
gpsmb.NewClient(c.target, c.creds) in secretsdump bypasses nuclei’s fastdialer
and per-execution network policy; replace it with the dialer-aware constructor
used by dcerpc.Client.connect() (e.g., gpsmb.NewClientWithDialer(c.target,
c.creds, NewExecDialer(c.nj.ExecutionId()))) so DNS, proxy, resolved-IP
allow/deny and timeouts are enforced at dial time, and ensure IsHostAllowed
still runs against c.target before dialing; import the dcerpc package or move
NewExecDialer to a shared helper to avoid an import cycle.

Comment on lines +118 to +132
pipe, err := smb.OpenPipe("\\PIPE\\lsass")
if err != nil {
// Fall back to drsuapi-named pipe; both are accepted by the DC.
pipe, err = smb.OpenPipe("lsass")
if err != nil {
return nil, fmt.Errorf("open lsass pipe: %w", err)
}
}
rpc := gprpc.NewClient(pipe)
if err := rpc.BindAuth(gpdrs.UUID, gpdrs.MajorVersion, gpdrs.MinorVersion, c.creds); err != nil {
return nil, fmt.Errorf("drsuapi bind: %w", err)
}
defer func() {
_ = rpc.Transport.Close()
}()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Pipe leak on BindAuth failure; misleading fallback comment.

Two issues in this block:

  1. If rpc.BindAuth on line 127 fails, the function returns at line 128 without closing pipe. The defer rpc.Transport.Close() on line 130-132 is registered after the error return, so the pipe file handle (and its SMB tree/session resource) is leaked. Compare with rpcOverNamedPipe in dcerpc.go (lines 170-175) which explicitly closes the pipe on bind failure.
  2. The comment on line 120 says "Fall back to drsuapi-named pipe", but the fallback value is "lsass" (same endpoint, different naming form), not drsuapi. Please correct the comment to avoid confusion for future readers.
🔧 Suggested fix
 	pipe, err := smb.OpenPipe("\\PIPE\\lsass")
 	if err != nil {
-		// Fall back to drsuapi-named pipe; both are accepted by the DC.
+		// Fall back to the unprefixed pipe name; both forms are accepted by the DC.
 		pipe, err = smb.OpenPipe("lsass")
 		if err != nil {
 			return nil, fmt.Errorf("open lsass pipe: %w", err)
 		}
 	}
 	rpc := gprpc.NewClient(pipe)
 	if err := rpc.BindAuth(gpdrs.UUID, gpdrs.MajorVersion, gpdrs.MinorVersion, c.creds); err != nil {
+		_ = pipe.Close()
 		return nil, fmt.Errorf("drsuapi bind: %w", err)
 	}
 	defer func() {
 		_ = rpc.Transport.Close()
 	}()
📝 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
pipe, err := smb.OpenPipe("\\PIPE\\lsass")
if err != nil {
// Fall back to drsuapi-named pipe; both are accepted by the DC.
pipe, err = smb.OpenPipe("lsass")
if err != nil {
return nil, fmt.Errorf("open lsass pipe: %w", err)
}
}
rpc := gprpc.NewClient(pipe)
if err := rpc.BindAuth(gpdrs.UUID, gpdrs.MajorVersion, gpdrs.MinorVersion, c.creds); err != nil {
return nil, fmt.Errorf("drsuapi bind: %w", err)
}
defer func() {
_ = rpc.Transport.Close()
}()
pipe, err := smb.OpenPipe("\\PIPE\\lsass")
if err != nil {
// Fall back to the unprefixed pipe name; both forms are accepted by the DC.
pipe, err = smb.OpenPipe("lsass")
if err != nil {
return nil, fmt.Errorf("open lsass pipe: %w", err)
}
}
rpc := gprpc.NewClient(pipe)
if err := rpc.BindAuth(gpdrs.UUID, gpdrs.MajorVersion, gpdrs.MinorVersion, c.creds); err != nil {
_ = pipe.Close()
return nil, fmt.Errorf("drsuapi bind: %w", err)
}
defer func() {
_ = rpc.Transport.Close()
}()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/js/libs/secretsdump/secretsdump.go` around lines 118 - 132, The OpenPipe
fallback comment is misleading and there is a resource leak if
gprpc.NewClient(pipe).BindAuth(...) fails because rpc.Transport.Close() is
deferred only after the bind succeeds; ensure the pipe is closed on bind failure
and fix the comment: after calling smb.OpenPipe("\\PIPE\\lsass") and falling
back to smb.OpenPipe("lsass") update the comment to reflect that the fallback
uses the alternate lsass pipe name (not "drsuapi"), then either register the
defer rpc.Transport.Close() immediately after creating rpc (so it always runs)
or explicitly close the pipe/rpc.Transport when rpc.BindAuth returns an error
(refer to OpenPipe, gprpc.NewClient, rpc.BindAuth, rpc.Transport.Close and
follow the rpcOverNamedPipe pattern to close the pipe on bind failure).

Comment on lines +149 to +160
// Resolve target -> DN if it doesn't already look like one.
userDN := target
if len(target) < 3 || (target[:3] != "CN=" && target[:3] != "cn=") {
cracked, err := gpdrs.DsCrackNames(rpc, bind.Handle, 7 /* DS_NT4_ACCOUNT_NAME */, 1 /* DS_FQDN_1779_NAME */, []string{c.Domain + "\\" + target})
if err != nil || len(cracked) == 0 || cracked[0].Name == "" {
cracked, err = gpdrs.DsCrackNames(rpc, bind.Handle, 11 /* DS_UNIQUE_ID_NAME (SID) */, 1, []string{target})
if err != nil || len(cracked) == 0 || cracked[0].Name == "" {
return nil, fmt.Errorf("could not resolve %q to a DN", target)
}
}
userDN = cracked[0].Name
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm the DN-detection call sites and whether goimpacket exposes a canonical helper.
rg -nP -C2 'DsCrackNames|GetDomainDN' --type=go

Repository: projectdiscovery/nuclei

Length of output: 1261


🏁 Script executed:

# Check the imports and full context of the secretsdump.go file
head -30 pkg/js/libs/secretsdump/secretsdump.go | cat -n

Repository: projectdiscovery/nuclei

Length of output: 1495


🏁 Script executed:

# Read the complete context around lines 149-160
sed -n '140,170p' pkg/js/libs/secretsdump/secretsdump.go | cat -n

Repository: projectdiscovery/nuclei

Length of output: 1451


🏁 Script executed:

# Search for other DN-related checks or similar heuristics in the file
rg -n "target\[:3\]|ContainsRune|DN|Distinguished" pkg/js/libs/secretsdump/secretsdump.go

Repository: projectdiscovery/nuclei

Length of output: 841


🏁 Script executed:

# Check if there are other DN validation patterns elsewhere in the codebase
rg -n "CN=.*OU=|RDN|Distinguished.*Name" --type=go

Repository: projectdiscovery/nuclei

Length of output: 689


Resolve DN heuristic to handle any RDN attribute type, not just CN=.

The check target[:3] != "CN=" && target[:3] != "cn=" incorrectly treats legitimate DNs starting with other RDN attributes (OU=, DC=, UID=, O=, etc.) as non-DNs, routing them through DsCrackNames with DS_NT4_ACCOUNT_NAME. Well-formed DNs that don't start with CN= will fail the first crack, fall through to the SID cracker, and then error out with "could not resolve".

Replace with !strings.ContainsRune(target, '=') since all valid DNs contain at least one = character. Add "strings" to the import list.

🔧 Suggested fix
-	userDN := target
-	if len(target) < 3 || (target[:3] != "CN=" && target[:3] != "cn=") {
+	userDN := target
+	if !strings.ContainsRune(target, '=') {
 		cracked, err := gpdrs.DsCrackNames(rpc, bind.Handle, 7 /* DS_NT4_ACCOUNT_NAME */, 1 /* DS_FQDN_1779_NAME */, []string{c.Domain + "\\" + target})

(Add "strings" to the import list.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/js/libs/secretsdump/secretsdump.go` around lines 149 - 160, The
DN-detection currently checks target[:3] for "CN=" which misclassifies valid DNs
that start with other RDNs; update the heuristic in the block that assigns
userDN (and the conditional before calling gpdrs.DsCrackNames) to use
strings.ContainsRune(target, '=') (i.e., treat as DN if it contains '=') instead
of the prefix CN check, and add "strings" to the import list so
strings.ContainsRune can be used; keep the rest of the DsCrackNames calls
(including DS_NT4_ACCOUNT_NAME/DS_UNIQUE_ID_NAME) unchanged.

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.

Impacket-style AD attack primitives via JavaScript

1 participant