From 8117b1acf3f46f087c55f0890eca0c3a2c17051f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Thu, 16 Oct 2025 14:56:57 +0300 Subject: [PATCH 1/2] add windvane --- pkg/passive/sources.go | 2 + pkg/passive/sources_test.go | 2 + pkg/subscraping/sources/windvane/windvane.go | 146 +++++++++++++++++++ 3 files changed, 150 insertions(+) create mode 100644 pkg/subscraping/sources/windvane/windvane.go diff --git a/pkg/passive/sources.go b/pkg/passive/sources.go index e74d874be..44be7b8d6 100644 --- a/pkg/passive/sources.go +++ b/pkg/passive/sources.go @@ -51,6 +51,7 @@ import ( "github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/virustotal" "github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/waybackarchive" "github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/whoisxmlapi" + "github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/windvane" "github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/zoomeyeapi" mapsutil "github.com/projectdiscovery/utils/maps" ) @@ -95,6 +96,7 @@ var AllSources = [...]subscraping.Source{ &virustotal.Source{}, &waybackarchive.Source{}, &whoisxmlapi.Source{}, + &windvane.Source{}, &zoomeyeapi.Source{}, &facebook.Source{}, // &threatminer.Source{}, // failing api diff --git a/pkg/passive/sources_test.go b/pkg/passive/sources_test.go index 4febbf9fa..a90ff8111 100644 --- a/pkg/passive/sources_test.go +++ b/pkg/passive/sources_test.go @@ -48,6 +48,7 @@ var ( "virustotal", "waybackarchive", "whoisxmlapi", + "windvane", "zoomeyeapi", "hunter", "leakix", @@ -85,6 +86,7 @@ var ( "rsecloud", "securitytrails", "shodan", + "windvane", "virustotal", "whoisxmlapi", "hunter", diff --git a/pkg/subscraping/sources/windvane/windvane.go b/pkg/subscraping/sources/windvane/windvane.go new file mode 100644 index 000000000..194804d9b --- /dev/null +++ b/pkg/subscraping/sources/windvane/windvane.go @@ -0,0 +1,146 @@ +// Package windvane logic +package windvane + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/projectdiscovery/subfinder/v2/pkg/subscraping" +) + +type response struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data responseData `json:"data"` +} + +type responseData struct { + List []domainEntry `json:"list"` + PageResponse pageInfo `json:"page_response"` +} + +type domainEntry struct { + Domain string `json:"domain"` +} + +type pageInfo struct { + Total string `json:"total"` + Count string `json:"count"` + TotalPage string `json:"total_page"` +} + +type Source struct { + apiKeys []string + timeTaken time.Duration + errors int + results int + skipped bool +} + +func (s *Source) Run(ctx context.Context, domain string, session *subscraping.Session) <-chan subscraping.Result { + results := make(chan subscraping.Result) + s.errors = 0 + s.results = 0 + + go func() { + defer func(startTime time.Time) { + s.timeTaken = time.Since(startTime) + close(results) + }(time.Now()) + + randomApiKey := subscraping.PickRandom(s.apiKeys, s.Name()) + if randomApiKey == "" { + s.skipped = true + return + } + + headers := map[string]string{"Content-Type": "application/json", "X-Api-Key": randomApiKey} + + page := 1 + count := 1000 + for { + var resp *http.Response + var err error + + requestBody := fmt.Appendf(nil, `{"domain":"%s","page_request":{"page":%d,"count":%d}}`, domain, page, count) + resp, err = session.Post(ctx, "https://windvane.lichoin.com/trpc.backendhub.public.WindvaneService/ListSubDomain", + "", headers, bytes.NewReader(requestBody)) + + if err != nil { + results <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: err} + s.errors++ + session.DiscardHTTPResponse(resp) + return + } + + defer session.DiscardHTTPResponse(resp) + + var windvaneResponse response + err = json.NewDecoder(resp.Body).Decode(&windvaneResponse) + if err != nil { + results <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: err} + s.errors++ + session.DiscardHTTPResponse(resp) + return + } + + for _, record := range windvaneResponse.Data.List { + results <- subscraping.Result{Source: s.Name(), Type: subscraping.Subdomain, Value: record.Domain} + s.results++ + } + + pageInfo := windvaneResponse.Data.PageResponse + var totalRecords, recordsPerPage int + + if totalRecords, err = strconv.Atoi(pageInfo.Total); err != nil { + break + } + if recordsPerPage, err = strconv.Atoi(pageInfo.Count); err != nil { + break + } + + if (page-1)*recordsPerPage >= totalRecords { + break + } + + page++ + } + + }() + + return results +} + +func (s *Source) Name() string { + return "windvane" +} + +func (s *Source) IsDefault() bool { + return true +} + +func (s *Source) HasRecursiveSupport() bool { + return false +} + +func (s *Source) NeedsKey() bool { + return true +} + +func (s *Source) AddApiKeys(keys []string) { + s.apiKeys = keys +} + +func (s *Source) Statistics() subscraping.Statistics { + return subscraping.Statistics{ + Errors: s.errors, + Results: s.results, + TimeTaken: s.timeTaken, + Skipped: s.skipped, + } +} From 26a221fffeebcf57a6d24984fd9264818b62d64b Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Sun, 19 Oct 2025 15:03:08 +0400 Subject: [PATCH 2/2] using json marshal --- pkg/subscraping/sources/windvane/windvane.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/subscraping/sources/windvane/windvane.go b/pkg/subscraping/sources/windvane/windvane.go index 194804d9b..768cc3fd0 100644 --- a/pkg/subscraping/sources/windvane/windvane.go +++ b/pkg/subscraping/sources/windvane/windvane.go @@ -5,7 +5,6 @@ import ( "bytes" "context" "encoding/json" - "fmt" "net/http" "strconv" "time" @@ -67,7 +66,7 @@ func (s *Source) Run(ctx context.Context, domain string, session *subscraping.Se var resp *http.Response var err error - requestBody := fmt.Appendf(nil, `{"domain":"%s","page_request":{"page":%d,"count":%d}}`, domain, page, count) + requestBody, _ := json.Marshal(map[string]interface{}{"domain": domain, "page_request": map[string]int{"page": page, "count": count}}) resp, err = session.Post(ctx, "https://windvane.lichoin.com/trpc.backendhub.public.WindvaneService/ListSubDomain", "", headers, bytes.NewReader(requestBody))