mirror of
https://github.com/projectdiscovery/subfinder
synced 2026-06-21 14:05:24 +00:00
3cb054d976
* 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> * Fix wildcard certificate flag propagation with -nW flag Fixed a bug where the wildcard_certificate field was being lost when using the -nW flag (DNS resolution with wildcard filtering). The issue occurred because when multiple sources find the same subdomain, only the first occurrence is sent to the resolution pool. If a later source marks the subdomain as having a wildcard certificate, that information was stored in uniqueMap but never propagated to foundResults. Solution: After resolution completes, merge wildcard certificate information from uniqueMap into foundResults. This ensures that if any source marked a subdomain as having a wildcard certificate, that flag is preserved in the final output. This ensures consistent behavior - wildcard_certificate field appears in JSON output regardless of whether -nW flag is used. Validation: - Without -nW: api.nuclei.sh shows wildcard_certificate:true ✓ - With -nW: api.nuclei.sh shows wildcard_certificate:true ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
126 lines
2.8 KiB
Go
126 lines
2.8 KiB
Go
package resolve
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/rs/xid"
|
|
)
|
|
|
|
const (
|
|
maxWildcardChecks = 3
|
|
)
|
|
|
|
// ResolutionPool is a pool of resolvers created for resolving subdomains
|
|
// for a given host.
|
|
type ResolutionPool struct {
|
|
*Resolver
|
|
Tasks chan HostEntry
|
|
Results chan Result
|
|
wg *sync.WaitGroup
|
|
removeWildcard bool
|
|
|
|
wildcardIPs map[string]struct{}
|
|
}
|
|
|
|
// HostEntry defines a host with the source
|
|
type HostEntry struct {
|
|
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
|
|
WildcardCertificate bool
|
|
}
|
|
|
|
// ResultType is the type of result found
|
|
type ResultType int
|
|
|
|
// Types of data result can return
|
|
const (
|
|
Subdomain ResultType = iota
|
|
Error
|
|
)
|
|
|
|
// NewResolutionPool creates a pool of resolvers for resolving subdomains of a given domain
|
|
func (r *Resolver) NewResolutionPool(workers int, removeWildcard bool) *ResolutionPool {
|
|
resolutionPool := &ResolutionPool{
|
|
Resolver: r,
|
|
Tasks: make(chan HostEntry),
|
|
Results: make(chan Result),
|
|
wg: &sync.WaitGroup{},
|
|
removeWildcard: removeWildcard,
|
|
wildcardIPs: make(map[string]struct{}),
|
|
}
|
|
|
|
go func() {
|
|
for range workers {
|
|
resolutionPool.wg.Add(1)
|
|
go resolutionPool.resolveWorker()
|
|
}
|
|
resolutionPool.wg.Wait()
|
|
close(resolutionPool.Results)
|
|
}()
|
|
|
|
return resolutionPool
|
|
}
|
|
|
|
// InitWildcards inits the wildcard ips array
|
|
func (r *ResolutionPool) InitWildcards(domain string) error {
|
|
for range maxWildcardChecks {
|
|
uid := xid.New().String()
|
|
|
|
hosts, _ := r.DNSClient.Lookup(uid + "." + domain)
|
|
if len(hosts) == 0 {
|
|
return fmt.Errorf("%s is not a wildcard domain", domain)
|
|
}
|
|
|
|
// Append all wildcard ips found for domains
|
|
for _, host := range hosts {
|
|
r.wildcardIPs[host] = struct{}{}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *ResolutionPool) resolveWorker() {
|
|
for task := range r.Tasks {
|
|
if !r.removeWildcard {
|
|
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, WildcardCertificate: task.WildcardCertificate}
|
|
continue
|
|
}
|
|
|
|
if len(hosts) == 0 {
|
|
continue
|
|
}
|
|
|
|
var skip bool
|
|
for _, host := range hosts {
|
|
// Ignore the host if it exists in wildcard ips map
|
|
if _, ok := r.wildcardIPs[host]; ok {
|
|
skip = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !skip {
|
|
r.Results <- Result{Type: Subdomain, Host: task.Host, IP: hosts[0], Source: task.Source, WildcardCertificate: task.WildcardCertificate}
|
|
}
|
|
}
|
|
r.wg.Done()
|
|
}
|