Skip to content

Commit 5356ebe

Browse files
tarunKoyalwarclaude
andcommitted
Add wildcard certificate detection in JSON output
This commit implements wildcard certificate detection for subdomains that are covered by wildcard certificates (e.g., *.example.com). When sources return results containing wildcard patterns, the subdomain is now marked with a wildcard_certificate field in JSON output mode. Key changes: - Added WildcardCertificate field to HostEntry and Result structs - Detection logic: checks if result.Value contains "*.subdomain" pattern - Propagates wildcard flag through resolution pipeline - JSON output includes wildcard_certificate field (omitted if false) - Updates version to v2.9.1-dev - Fix .goreleaser.yml 386 architecture quoting - Add /subfinder to .gitignore Note: wildcard_certificate field is not included when using -cs flag to avoid breaking changes to the library API. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 2ba3645 commit 5356ebe

6 files changed

Lines changed: 46 additions & 28 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ vendor/
88
.idea
99
.devcontainer
1010
.vscode
11-
dist
11+
dist
12+
/subfinder

.goreleaser.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ builds:
1313
- darwin
1414
goarch:
1515
- amd64
16-
- 386
16+
- '386'
1717
- arm
1818
- arm64
1919

pkg/resolve/resolve.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,20 @@ type ResolutionPool struct {
2525

2626
// HostEntry defines a host with the source
2727
type HostEntry struct {
28-
Domain string
29-
Host string
30-
Source string
28+
Domain string
29+
Host string
30+
Source string
31+
WildcardCertificate bool
3132
}
3233

3334
// Result contains the result for a host resolution
3435
type Result struct {
35-
Type ResultType
36-
Host string
37-
IP string
38-
Error error
39-
Source string
36+
Type ResultType
37+
Host string
38+
IP string
39+
Error error
40+
Source string
41+
WildcardCertificate bool
4042
}
4143

4244
// ResultType is the type of result found
@@ -92,13 +94,13 @@ func (r *ResolutionPool) InitWildcards(domain string) error {
9294
func (r *ResolutionPool) resolveWorker() {
9395
for task := range r.Tasks {
9496
if !r.removeWildcard {
95-
r.Results <- Result{Type: Subdomain, Host: task.Host, IP: "", Source: task.Source}
97+
r.Results <- Result{Type: Subdomain, Host: task.Host, IP: "", Source: task.Source, WildcardCertificate: task.WildcardCertificate}
9698
continue
9799
}
98100

99101
hosts, err := r.DNSClient.Lookup(task.Host)
100102
if err != nil {
101-
r.Results <- Result{Type: Error, Host: task.Host, Source: task.Source, Error: err}
103+
r.Results <- Result{Type: Error, Host: task.Host, Source: task.Source, Error: err, WildcardCertificate: task.WildcardCertificate}
102104
continue
103105
}
104106

@@ -116,7 +118,7 @@ func (r *ResolutionPool) resolveWorker() {
116118
}
117119

118120
if !skip {
119-
r.Results <- Result{Type: Subdomain, Host: task.Host, IP: hosts[0], Source: task.Source}
121+
r.Results <- Result{Type: Subdomain, Host: task.Host, IP: hosts[0], Source: task.Source, WildcardCertificate: task.WildcardCertificate}
120122
}
121123
}
122124
r.wg.Done()

pkg/runner/banners.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const banner = `
1717
const ToolName = `subfinder`
1818

1919
// Version is the current version of subfinder
20-
const version = `v2.9.0`
20+
const version = `v2.9.1-dev`
2121

2222
// showBanner is used to show the banner to the user
2323
func showBanner() {

pkg/runner/enumerate.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ func (r *Runner) EnumerateSingleDomainWithCtx(ctx context.Context, domain string
6767
gologger.Warning().Msgf("Encountered an error with source %s: %s\n", result.Source, result.Error)
6868
case subscraping.Subdomain:
6969
subdomain := replacer.Replace(result.Value)
70+
// check if this subdomain is actually a wildcard subdomain
71+
// that may have furthur subdomains associated with it
72+
isWildcard := strings.Contains(result.Value, "*."+subdomain)
7073

7174
// Validate the subdomain found and remove wildcards from
7275
if !strings.HasSuffix(subdomain, "."+domain) {
@@ -90,10 +93,17 @@ func (r *Runner) EnumerateSingleDomainWithCtx(ctx context.Context, domain string
9093
// send the subdomain for resolution.
9194
if _, ok := uniqueMap[subdomain]; ok {
9295
skippedCounts[result.Source]++
96+
// even if it is duplicate if it was not marked as wildcard before but this source says it is wildcard
97+
// then we should mark it as wildcard
98+
if !uniqueMap[subdomain].WildcardCertificate && isWildcard {
99+
val := uniqueMap[subdomain]
100+
val.WildcardCertificate = true
101+
uniqueMap[subdomain] = val
102+
}
93103
continue
94104
}
95105

96-
hostEntry := resolve.HostEntry{Domain: domain, Host: subdomain, Source: result.Source}
106+
hostEntry := resolve.HostEntry{Domain: domain, Host: subdomain, Source: result.Source, WildcardCertificate: isWildcard}
97107
if r.options.ResultCallback != nil && !r.options.RemoveWildcard {
98108
r.options.ResultCallback(&hostEntry)
99109
}
@@ -112,6 +122,7 @@ func (r *Runner) EnumerateSingleDomainWithCtx(ctx context.Context, domain string
112122
if r.options.RemoveWildcard {
113123
close(resolutionPool.Tasks)
114124
}
125+
115126
wg.Done()
116127
}()
117128

@@ -129,7 +140,7 @@ func (r *Runner) EnumerateSingleDomainWithCtx(ctx context.Context, domain string
129140
if _, ok := foundResults[result.Host]; !ok {
130141
foundResults[result.Host] = result
131142
if r.options.ResultCallback != nil {
132-
r.options.ResultCallback(&resolve.HostEntry{Domain: domain, Host: result.Host, Source: result.Source})
143+
r.options.ResultCallback(&resolve.HostEntry{Domain: domain, Host: result.Host, Source: result.Source, WildcardCertificate: result.WildcardCertificate})
133144
}
134145
}
135146
}

pkg/runner/outputter.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,25 @@ type OutputWriter struct {
1919
}
2020

2121
type jsonSourceResult struct {
22-
Host string `json:"host"`
23-
Input string `json:"input"`
24-
Source string `json:"source"`
22+
Host string `json:"host"`
23+
Input string `json:"input"`
24+
Source string `json:"source"`
25+
WildcardCertificate bool `json:"wildcard_certificate,omitempty"`
2526
}
2627

2728
type jsonSourceIPResult struct {
28-
Host string `json:"host"`
29-
IP string `json:"ip"`
30-
Input string `json:"input"`
31-
Source string `json:"source"`
29+
Host string `json:"host"`
30+
IP string `json:"ip"`
31+
Input string `json:"input"`
32+
Source string `json:"source"`
33+
WildcardCertificate bool `json:"wildcard_certificate,omitempty"`
3234
}
3335

3436
type jsonSourcesResult struct {
35-
Host string `json:"host"`
36-
Input string `json:"input"`
37-
Sources []string `json:"sources"`
37+
Host string `json:"host"`
38+
Input string `json:"input"`
39+
Sources []string `json:"sources"`
40+
WildcardCertificate bool `json:"wildcard_certificate,omitempty"`
3841
}
3942

4043
// NewOutputWriter creates a new OutputWriter
@@ -117,7 +120,7 @@ func writeJSONHostIP(input string, results map[string]resolve.Result, writer io.
117120
data.IP = result.IP
118121
data.Input = input
119122
data.Source = result.Source
120-
123+
data.WildcardCertificate = result.WildcardCertificate
121124
err := encoder.Encode(&data)
122125
if err != nil {
123126
return err
@@ -130,7 +133,7 @@ func writeJSONHostIP(input string, results map[string]resolve.Result, writer io.
130133
func (o *OutputWriter) WriteHostNoWildcard(input string, results map[string]resolve.Result, writer io.Writer) error {
131134
hosts := make(map[string]resolve.HostEntry)
132135
for host, result := range results {
133-
hosts[host] = resolve.HostEntry{Domain: host, Host: result.Host, Source: result.Source}
136+
hosts[host] = resolve.HostEntry{Domain: host, Host: result.Host, Source: result.Source, WildcardCertificate: result.WildcardCertificate}
134137
}
135138

136139
return o.WriteHost(input, hosts, writer)
@@ -175,6 +178,7 @@ func writeJSONHost(input string, results map[string]resolve.HostEntry, writer io
175178
data.Host = result.Host
176179
data.Input = input
177180
data.Source = result.Source
181+
data.WildcardCertificate = result.WildcardCertificate
178182
err := encoder.Encode(data)
179183
if err != nil {
180184
return err

0 commit comments

Comments
 (0)