From 9b3a5b75dddd3b5891f7af2fabd1dfab7343f4b2 Mon Sep 17 00:00:00 2001 From: Samuel Campos Date: Fri, 7 May 2021 18:01:45 +0200 Subject: [PATCH] add bind IP option it can be very useful to choose one's local IP if one have several network interfaces --- README.md | 25 +++++++++++++++++++++++++ v2/pkg/passive/passive.go | 5 +++-- v2/pkg/runner/enumerate.go | 2 +- v2/pkg/runner/options.go | 4 ++++ v2/pkg/runner/validate.go | 9 +++++++++ v2/pkg/subscraping/agent.go | 10 +++++++++- 6 files changed, 51 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 32603a4f5..06ef3fe11 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ This will display help for the tool. Here are all the switches it supports. | Flag | Description | Example | | ---------------- | ---------------------------------------------------------- | -------------------------------------- | | -all | Use all sources (slow) for enumeration | subfinder -d uber.com -all | +| -b | IP address to be used as local bind | subfinder -b 172.16.0.1 | | -config | Configuration file for API Keys, etc | subfinder -config config.yaml | | -d | Domain to find subdomains for | subfinder -d uber.com | | -dL | File containing list of domains to enumerate | subfinder -dL hackerone-hosts.txt | @@ -210,6 +211,30 @@ https://docs.hackerone.com http://mta-sts.managed.hackerone.com ``` +If your enterprise uses source routing to choose network output, or your computer has many public network interfaces (eg: public Wi-Fi + 4G connection + Ethernet Wire + VPN), you might want to choose your output network by binding IP source. In this case, you can use `-b` option. +In the example below, we have 3 network interfaces able to communicate to the Internet through 3 different outputs. Each output is chosen by binding one source IP with `-b` option. +```sh +▶ ip addr +[...] +3: wlp3s0: mtu 1500 qdisc mq state UP group default qlen 1000 + link/ether e8:b1:fc:50:90:a0 brd ff:ff:ff:ff:ff:ff + inet 192.168.1.87/24 brd 192.168.1.255 scope global dynamic noprefixroute wlp3s0 + valid_lft 62538sec preferred_lft 62538sec +4: tun0: mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 100 + link/none + inet 192.168.254.70 peer 192.168.254.69/32 scope global tun0 + valid_lft forever preferred_lft forever +5: enx0c5b8f279a64: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 + link/ether 0c:5b:8f:a5:63:25 brd ff:ff:ff:ff:ff:ff + inet 192.168.8.100/24 brd 192.168.8.255 scope global dynamic noprefixroute enx0c5b8f279a64 + valid_lft 86396sec preferred_lft 86396sec + +▶ subfinder -d hackerone.com -b 192.168.1.87 +▶ subfinder -d hackerone.com -b 192.168.254.70 +▶ subfinder -d hackerone.com -b 192.168.8.100 +``` + +
diff --git a/v2/pkg/passive/passive.go b/v2/pkg/passive/passive.go index 5dfbc7db9..132f224b0 100644 --- a/v2/pkg/passive/passive.go +++ b/v2/pkg/passive/passive.go @@ -3,6 +3,7 @@ package passive import ( "context" "fmt" + "net" "sync" "time" @@ -11,11 +12,11 @@ import ( ) // EnumerateSubdomains enumerates all the subdomains for a given domain -func (a *Agent) EnumerateSubdomains(domain string, keys *subscraping.Keys, timeout int, maxEnumTime time.Duration) chan subscraping.Result { +func (a *Agent) EnumerateSubdomains(domain string, keys *subscraping.Keys, timeout int, maxEnumTime time.Duration, localIP net.IP) chan subscraping.Result { results := make(chan subscraping.Result) go func() { - session, err := subscraping.NewSession(domain, keys, timeout) + session, err := subscraping.NewSession(domain, keys, timeout, localIP) if err != nil { results <- subscraping.Result{Type: subscraping.Error, Error: fmt.Errorf("could not init passive session for %s: %s", domain, err)} } diff --git a/v2/pkg/runner/enumerate.go b/v2/pkg/runner/enumerate.go index 28bf37f36..30f814249 100644 --- a/v2/pkg/runner/enumerate.go +++ b/v2/pkg/runner/enumerate.go @@ -37,7 +37,7 @@ func (r *Runner) EnumerateSingleDomain(ctx context.Context, domain string, outpu // Run the passive subdomain enumeration now := time.Now() - passiveResults := r.passiveAgent.EnumerateSubdomains(domain, &keys, r.options.Timeout, time.Duration(r.options.MaxEnumerationTime)*time.Minute) + passiveResults := r.passiveAgent.EnumerateSubdomains(domain, &keys, r.options.Timeout, time.Duration(r.options.MaxEnumerationTime)*time.Minute, r.options.LocalIP) wg := &sync.WaitGroup{} wg.Add(1) diff --git a/v2/pkg/runner/options.go b/v2/pkg/runner/options.go index bf906152a..dce3cfb68 100644 --- a/v2/pkg/runner/options.go +++ b/v2/pkg/runner/options.go @@ -3,6 +3,7 @@ package runner import ( "flag" "io" + "net" "os" "path" "reflect" @@ -38,6 +39,8 @@ type Options struct { ExcludeSources string // ExcludeSources contains the comma-separated sources to not include in the enumeration process Resolvers string // Resolvers is the comma-separated resolvers to use for enumeration ResolverList string // ResolverList is a text file containing list of resolvers to use for enumeration + LocalIP net.IP // LocalIP is the IP address used as local bind + LocalIPString string // LocalIPString is the IP address in string format got from command line ConfigFile string // ConfigFile contains the location of the config file YAMLConfig ConfigFile // YAMLConfig contains the unmarshalled yaml config file @@ -75,6 +78,7 @@ func ParseOptions() *Options { flag.StringVar(&options.Resolvers, "r", "", "Comma-separated list of resolvers to use") flag.StringVar(&options.ResolverList, "rL", "", "Text file containing list of resolvers to use") flag.BoolVar(&options.RemoveWildcard, "nW", false, "Remove Wildcard & Dead Subdomains from output") + flag.StringVar(&options.LocalIPString, "b", "", "IP address to be used as local bind") flag.StringVar(&options.ConfigFile, "config", path.Join(config, "config.yaml"), "Configuration file for API Keys, etc") flag.BoolVar(&options.Version, "version", false, "Show version of subfinder") flag.Parse() diff --git a/v2/pkg/runner/validate.go b/v2/pkg/runner/validate.go index 1f833caf4..8b2396dca 100644 --- a/v2/pkg/runner/validate.go +++ b/v2/pkg/runner/validate.go @@ -2,6 +2,7 @@ package runner import ( "errors" + "net" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/gologger/formatter" @@ -34,6 +35,14 @@ func (options *Options) validateOptions() error { return errors.New("hostip flag must be used with RemoveWildcard option") } + // Check local IP address + if options.LocalIPString != "" { + options.LocalIP = net.ParseIP(options.LocalIPString) + if options.LocalIP == nil { + return errors.New("local bind ip is malformed") + } + } + return nil } diff --git a/v2/pkg/subscraping/agent.go b/v2/pkg/subscraping/agent.go index b4c89ecfb..2a38c7114 100755 --- a/v2/pkg/subscraping/agent.go +++ b/v2/pkg/subscraping/agent.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "io/ioutil" + "net" "net/http" "net/url" "time" @@ -14,7 +15,13 @@ import ( ) // NewSession creates a new session object for a domain -func NewSession(domain string, keys *Keys, timeout int) (*Session, error) { +func NewSession(domain string, keys *Keys, timeout int, localIP net.IP) (*Session, error) { + dialer := &net.Dialer{ + LocalAddr: &net.TCPAddr{ + IP: localIP, + }, + } + client := &http.Client{ Transport: &http.Transport{ MaxIdleConns: 100, @@ -22,6 +29,7 @@ func NewSession(domain string, keys *Keys, timeout int) (*Session, error) { TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, + DialContext: dialer.DialContext, }, Timeout: time.Duration(timeout) * time.Second, }