Files
projectdiscovery-subfinder/pkg/subscraping/sources/securitytrails/securitytrails.go
T
Mikel Olasagasti Uranga 6867e076f1 refactor: move v2 module code to root directory for cleaner structure
Keeps module path as github.com/projectdiscovery/subfinder/v2 to
preserve import compatibility.

Signed-off-by: Mikel Olasagasti Uranga <mikel@olasagasti.info>
2025-07-10 15:27:42 +02:00

147 lines
3.4 KiB
Go

// Package securitytrails logic
package securitytrails
import (
"bytes"
"context"
"fmt"
"net/http"
"strings"
"time"
jsoniter "github.com/json-iterator/go"
"github.com/projectdiscovery/subfinder/v2/pkg/subscraping"
"github.com/projectdiscovery/utils/ptr"
)
type response struct {
Meta struct {
ScrollID string `json:"scroll_id"`
} `json:"meta"`
Records []struct {
Hostname string `json:"hostname"`
} `json:"records"`
Subdomains []string `json:"subdomains"`
}
// Source is the passive scraping agent
type Source struct {
apiKeys []string
timeTaken time.Duration
errors int
results int
skipped bool
}
// Run function returns all subdomains found with the service
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
}
var scrollId string
headers := map[string]string{"Content-Type": "application/json", "APIKEY": randomApiKey}
for {
var resp *http.Response
var err error
if scrollId == "" {
var requestBody = fmt.Appendf(nil, `{"query":"apex_domain='%s'"}`, domain)
resp, err = session.Post(ctx, "https://api.securitytrails.com/v1/domains/list?include_ips=false&scroll=true", "",
headers, bytes.NewReader(requestBody))
} else {
resp, err = session.Get(ctx, fmt.Sprintf("https://api.securitytrails.com/v1/scroll/%s", scrollId), "", headers)
}
if err != nil && ptr.Safe(resp).StatusCode == 403 {
resp, err = session.Get(ctx, fmt.Sprintf("https://api.securitytrails.com/v1/domain/%s/subdomains", domain), "", headers)
}
if err != nil {
results <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: err}
s.errors++
session.DiscardHTTPResponse(resp)
return
}
var securityTrailsResponse response
err = jsoniter.NewDecoder(resp.Body).Decode(&securityTrailsResponse)
if err != nil {
results <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: err}
s.errors++
session.DiscardHTTPResponse(resp)
return
}
session.DiscardHTTPResponse(resp)
for _, record := range securityTrailsResponse.Records {
results <- subscraping.Result{Source: s.Name(), Type: subscraping.Subdomain, Value: record.Hostname}
s.results++
}
for _, subdomain := range securityTrailsResponse.Subdomains {
if strings.HasSuffix(subdomain, ".") {
subdomain += domain
} else {
subdomain = subdomain + "." + domain
}
results <- subscraping.Result{Source: s.Name(), Type: subscraping.Subdomain, Value: subdomain}
s.results++
}
scrollId = securityTrailsResponse.Meta.ScrollID
if scrollId == "" {
break
}
}
}()
return results
}
// Name returns the name of the source
func (s *Source) Name() string {
return "securitytrails"
}
func (s *Source) IsDefault() bool {
return true
}
func (s *Source) HasRecursiveSupport() bool {
return true
}
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,
}
}