diff --git a/.gitignore b/.gitignore index b0dd47ba..155676bf 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ vendor/ .idea .devcontainer .vscode -dist \ No newline at end of file +dist +/subfinder \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml index 867267d2..e34562f0 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -13,7 +13,7 @@ builds: - darwin goarch: - amd64 - - 386 + - '386' - arm - arm64 diff --git a/pkg/resolve/resolve.go b/pkg/resolve/resolve.go index d7e4d887..38055b89 100644 --- a/pkg/resolve/resolve.go +++ b/pkg/resolve/resolve.go @@ -25,18 +25,20 @@ type ResolutionPool struct { // HostEntry defines a host with the source type HostEntry struct { - Domain string - Host string - Source string + Domain string + Host string + Source string + WildcardCertificate bool } // Result contains the result for a host resolution type Result struct { - Type ResultType - Host string - IP string - Error error - Source string + Type ResultType + Host string + IP string + Error error + Source string + WildcardCertificate bool } // ResultType is the type of result found @@ -92,13 +94,13 @@ func (r *ResolutionPool) InitWildcards(domain string) error { func (r *ResolutionPool) resolveWorker() { for task := range r.Tasks { if !r.removeWildcard { - r.Results <- Result{Type: Subdomain, Host: task.Host, IP: "", Source: task.Source} + r.Results <- Result{Type: Subdomain, Host: task.Host, IP: "", Source: task.Source, WildcardCertificate: task.WildcardCertificate} continue } hosts, err := r.DNSClient.Lookup(task.Host) if err != nil { - r.Results <- Result{Type: Error, Host: task.Host, Source: task.Source, Error: err} + r.Results <- Result{Type: Error, Host: task.Host, Source: task.Source, Error: err, WildcardCertificate: task.WildcardCertificate} continue } @@ -116,7 +118,7 @@ func (r *ResolutionPool) resolveWorker() { } if !skip { - r.Results <- Result{Type: Subdomain, Host: task.Host, IP: hosts[0], Source: task.Source} + r.Results <- Result{Type: Subdomain, Host: task.Host, IP: hosts[0], Source: task.Source, WildcardCertificate: task.WildcardCertificate} } } r.wg.Done() diff --git a/pkg/runner/banners.go b/pkg/runner/banners.go index d0233478..7e52f2b6 100644 --- a/pkg/runner/banners.go +++ b/pkg/runner/banners.go @@ -17,7 +17,7 @@ const banner = ` const ToolName = `subfinder` // Version is the current version of subfinder -const version = `v2.9.0` +const version = `v2.9.1-dev` // showBanner is used to show the banner to the user func showBanner() { diff --git a/pkg/runner/enumerate.go b/pkg/runner/enumerate.go index f39eab06..bd8953e5 100644 --- a/pkg/runner/enumerate.go +++ b/pkg/runner/enumerate.go @@ -67,6 +67,9 @@ func (r *Runner) EnumerateSingleDomainWithCtx(ctx context.Context, domain string gologger.Warning().Msgf("Encountered an error with source %s: %s\n", result.Source, result.Error) case subscraping.Subdomain: subdomain := replacer.Replace(result.Value) + // check if this subdomain is actually a wildcard subdomain + // that may have furthur subdomains associated with it + isWildcard := strings.Contains(result.Value, "*."+subdomain) // Validate the subdomain found and remove wildcards from if !strings.HasSuffix(subdomain, "."+domain) { @@ -90,10 +93,17 @@ func (r *Runner) EnumerateSingleDomainWithCtx(ctx context.Context, domain string // send the subdomain for resolution. if _, ok := uniqueMap[subdomain]; ok { skippedCounts[result.Source]++ + // even if it is duplicate if it was not marked as wildcard before but this source says it is wildcard + // then we should mark it as wildcard + if !uniqueMap[subdomain].WildcardCertificate && isWildcard { + val := uniqueMap[subdomain] + val.WildcardCertificate = true + uniqueMap[subdomain] = val + } continue } - hostEntry := resolve.HostEntry{Domain: domain, Host: subdomain, Source: result.Source} + hostEntry := resolve.HostEntry{Domain: domain, Host: subdomain, Source: result.Source, WildcardCertificate: isWildcard} if r.options.ResultCallback != nil && !r.options.RemoveWildcard { r.options.ResultCallback(&hostEntry) } @@ -112,6 +122,7 @@ func (r *Runner) EnumerateSingleDomainWithCtx(ctx context.Context, domain string if r.options.RemoveWildcard { close(resolutionPool.Tasks) } + wg.Done() }() @@ -129,11 +140,21 @@ func (r *Runner) EnumerateSingleDomainWithCtx(ctx context.Context, domain string if _, ok := foundResults[result.Host]; !ok { foundResults[result.Host] = result if r.options.ResultCallback != nil { - r.options.ResultCallback(&resolve.HostEntry{Domain: domain, Host: result.Host, Source: result.Source}) + r.options.ResultCallback(&resolve.HostEntry{Domain: domain, Host: result.Host, Source: result.Source, WildcardCertificate: result.WildcardCertificate}) } } } } + + // Merge wildcard certificate information from uniqueMap into foundResults + // This handles cases where a later source marked a subdomain as wildcard + // after it was already sent to the resolution pool + for host, result := range foundResults { + if entry, ok := uniqueMap[host]; ok && entry.WildcardCertificate && !result.WildcardCertificate { + result.WildcardCertificate = true + foundResults[host] = result + } + } } wg.Wait() outputWriter := NewOutputWriter(r.options.JSON) diff --git a/pkg/runner/outputter.go b/pkg/runner/outputter.go index cfa4fc1a..e3f1839c 100644 --- a/pkg/runner/outputter.go +++ b/pkg/runner/outputter.go @@ -19,22 +19,25 @@ type OutputWriter struct { } type jsonSourceResult struct { - Host string `json:"host"` - Input string `json:"input"` - Source string `json:"source"` + Host string `json:"host"` + Input string `json:"input"` + Source string `json:"source"` + WildcardCertificate bool `json:"wildcard_certificate,omitempty"` } type jsonSourceIPResult struct { - Host string `json:"host"` - IP string `json:"ip"` - Input string `json:"input"` - Source string `json:"source"` + Host string `json:"host"` + IP string `json:"ip"` + Input string `json:"input"` + Source string `json:"source"` + WildcardCertificate bool `json:"wildcard_certificate,omitempty"` } type jsonSourcesResult struct { - Host string `json:"host"` - Input string `json:"input"` - Sources []string `json:"sources"` + Host string `json:"host"` + Input string `json:"input"` + Sources []string `json:"sources"` + WildcardCertificate bool `json:"wildcard_certificate,omitempty"` } // NewOutputWriter creates a new OutputWriter @@ -117,7 +120,7 @@ func writeJSONHostIP(input string, results map[string]resolve.Result, writer io. data.IP = result.IP data.Input = input data.Source = result.Source - + data.WildcardCertificate = result.WildcardCertificate err := encoder.Encode(&data) if err != nil { return err @@ -130,7 +133,7 @@ func writeJSONHostIP(input string, results map[string]resolve.Result, writer io. func (o *OutputWriter) WriteHostNoWildcard(input string, results map[string]resolve.Result, writer io.Writer) error { hosts := make(map[string]resolve.HostEntry) for host, result := range results { - hosts[host] = resolve.HostEntry{Domain: host, Host: result.Host, Source: result.Source} + hosts[host] = resolve.HostEntry{Domain: host, Host: result.Host, Source: result.Source, WildcardCertificate: result.WildcardCertificate} } return o.WriteHost(input, hosts, writer) @@ -175,6 +178,7 @@ func writeJSONHost(input string, results map[string]resolve.HostEntry, writer io data.Host = result.Host data.Input = input data.Source = result.Source + data.WildcardCertificate = result.WildcardCertificate err := encoder.Encode(data) if err != nil { return err