diff --git a/.img/set-dehashed.png b/.img/set-dehashed.png new file mode 100644 index 0000000..3931a55 Binary files /dev/null and b/.img/set-dehashed.png differ diff --git a/.img/set-hunter.png b/.img/set-hunter.png new file mode 100644 index 0000000..063b34a Binary files /dev/null and b/.img/set-hunter.png differ diff --git a/.img/simple_creds_query.png b/.img/simple_creds_query.png new file mode 100644 index 0000000..6aa7473 Binary files /dev/null and b/.img/simple_creds_query.png differ diff --git a/.img/simple_query.png b/.img/simple_query.png new file mode 100644 index 0000000..9ec61d9 Binary files /dev/null and b/.img/simple_query.png differ diff --git a/.img/wildcard_query.png b/.img/wildcard_query.png new file mode 100644 index 0000000..df331a6 Binary files /dev/null and b/.img/wildcard_query.png differ diff --git a/Makefile b/Makefile index 0de3316..31187b9 100644 --- a/Makefile +++ b/Makefile @@ -30,14 +30,18 @@ clean: # Build for current platform build: - $(GO) build -o $(BUILD_DIR)/$(BINARY_NAME) -ldflags "-X main.version=$(VERSION)" crowsnest.go + CGO_ENABLED=1 $(GO) build -o $(BUILD_DIR)/$(BINARY_NAME) -ldflags "-X main.version=$(VERSION)" crowsnest.go # Build for all platforms build-all: clean @for platform in $(PLATFORMS); do \ for arch in $(ARCHS); do \ echo "Building for $$platform/$$arch..."; \ - GOOS=$$platform GOARCH=$$arch $(GO) build -o $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch -ldflags "-X main.version=$(VERSION)" crowsnest.go; \ + if [ "$$platform" = "windows" ] || [ "$$platform" = "darwin" ]; then \ + GOOS=$$platform GOARCH=$$arch CGO_ENABLED=0 $(GO) build -o $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch -ldflags "-X main.version=$(VERSION)" crowsnest.go; \ + else \ + GOOS=$$platform GOARCH=$$arch CGO_ENABLED=1 $(GO) build -o $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch -ldflags "-X main.version=$(VERSION)" crowsnest.go; \ + fi; \ if [ "$$platform" = "windows" ]; then \ mv $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch.exe; \ fi; \ diff --git a/README.md b/README.md index ae7cd3b..2aee8b9 100644 --- a/README.md +++ b/README.md @@ -61,30 +61,25 @@ To configure the database location: ###️ Initial Setup CrowsNest requires an API key from Dehashed. Set it up with: +![Alt text](.img/set-dehashed.png "Set Dehashed Key") ```bash ar1ste1a@kali:~$ crowsnest set-dehashed ``` ### Simple Query CrowsNest can be used simply for example to query for credentials matching a given email domain. +![Alt text](.img/simple_query.png "Simple Query") ``` go # Provide credentials for domains matching target.com -crowsnest api -D target.com -C +crowsnest dehashed -D target.com ``` ### Simple Credentials Query CrowsNest can also be used to return only credentials for a given query. +![Alt text](.img/simple_creds_query.png "Creds Only Query") ``` go # Provide credentials for emails matching @target.com -crowsnest api -E @target.com -C -``` - -### Multiple Match Query -CrowsNest is capable of handling multiple queries for the same field. -This is useful for when you want to search for multiple domains, or multiple usernames. -``` go -# Provide credentials for domains matching target.com and target2.com, retrieving only credentials -crowsnest api -D target.com,target2.com -C +crowsnest dehashed -D @target.com -C ``` ### Wildcard Query @@ -92,34 +87,46 @@ CrowsNest is capable of handling wildcard queries. A wildcard query cannot begin with a wildcard. This is a limitation of the Dehashed API. An asterisk can be used to denote multiple characters, and a question mark can be used to denote a single character. -![Alt text](.img/wildcard_sample.png "Wildcard Query") +![Alt text](.img/wildcard_query.png "Wildcard Query") ``` go # Provide credentials for emails matching @target.com and @target2.com -crowsnest api -E @target?.com -C -W +crowsnest dehashed -E @target?.com -C -W ``` ### Email Query Dehashed has dictated that emails should be searched in the following format: `email:target.name&domain:target.com`. As such, to query an email, please use the following format (note, wildcard is not required but can be useful): +
+*see photo above in Wildcard Query* ``` go # Provide credentials for emails matching target.*@target.com -crowsnest api -W -E 'target*' -D target.com +crowsnest dehashed -W -E 'target*' -D target.com ``` You may also query the domain and find emails as well ``` go # Provide credentials for emails matching target.com -crowsnest api -D target.com -C +crowsnest dehashed -D target.com -C ``` +### Combining Queries +CrowsNest is capable of combining queries. +This is useful for when you want to query for credentials matching a given email or domain, but only for a specific username. +![Alt text](.img/combining_queries.png "Combined Query") +``` go +# Provide credentials for emails matching @target.com and username containing 'admin' +crowsnest dehashed -D target.com -U admin +``` ### Regex Query CrowsNest is capable of handling regex queries. Simply denote regex queries with the `-R` flag. Place all regex queries in quotes with the corresponding query flag in single quotes. +
+!!!! *Currently, the Regex Operators appear to be broken. I am waiting on a response from Dehashed* !!!! ``` go # Return matches for emails matching this given regex query -crowsnest api -R -E '[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)?@target.com' +crowsnest dehashed -R -E 'joh?n(ath[oa]n)' -D hotmail.com' ``` ### Output Text (default JSON) @@ -129,7 +136,7 @@ To change the output format, use the `-f` flag. CrowsNest currently supports JSON, YAML, XML, and TEXT output formats. ``` go # Return matches for usernames exactly matching "admin" and write to text file 'admins_file.txt' -crowsnest api -U admin -o admins_file -f txt +crowsnest dehashed -U admin -o admins_file -f txt ``` --- @@ -141,7 +148,7 @@ The WhoIs Lookups require a separate API Credit from the Dehashed API. ### Domain Lookup CrowsNest can perform a domain lookup for a given domain. This provides a tree view of the domain's WHOIS information. -![Alt text](.img/tree_whois_lookup.png "WhoIs Tree View") +![Alt text](.img/whois_domain.png "WhoIs Tree View") ```bash # Perform a WHOIS lookup for example.com crowsnest whois -d example.com @@ -156,10 +163,20 @@ The history lookup is immediately written to file and not displayed in the termi crowsnest whois -d example.com -H ``` +### Subdomain Scan +CrowsNest can perform a subdomain scan for a given domain. +This provides a list of all subdomains that match the given query. +![Alt text](.img/whois_subdomain.png "WhoIs Tree View") +```bash +# Perform a WHOIS subdomain scan for google.com +crowsnest whois -d google.com -s +``` + ### Reverse WHOIS Lookup CrowsNest can perform a reverse WHOIS lookup for given criteria. This provides a list of all domains that match the given query. -The reverse WHOIS lookup is immediately written to file and not displayed in the terminal or stored in the database. +The reverse WHOIS lookup is immediately written to file and not stored in the database. +![Alt text](.img/whois_reverse.png "WhoIs Tree View") ```bash # Perform a reverse WHOIS lookup for example.com crowsnest whois -I example.com @@ -168,7 +185,7 @@ crowsnest whois -I example.com ### IP Lookup CrowsNest can perform a reverse IP lookup for a given IP address. This provides a list of all domains that match the given query. -![Alt text](.img/reverse_ip_lookup.png "WhoIs Tree View") +![Alt text](.img/whois_ip.png "WhoIs View") ```bash # Perform a reverse IP lookup for 8.8.8.8 crowsnest whois -i 8.8.8.8 @@ -177,28 +194,21 @@ crowsnest whois -i 8.8.8.8 ### MX Lookup CrowsNest can perform an MX lookup for a given MX hostname. This provides a list of all domains that match the given query. -![Alt text](.img/mx_lookup.png "WhoIs Tree View") +![Alt text](.img/whois_mx.png "WhoIs Tree View") ```bash # Perform a reverse MX lookup for google.com -crowsnest whois -m google.com +crowsnest whois -m stmp.google.com ``` ### NS Lookup CrowsNest can perform an NS lookup for a given NS hostname. This provides a list of all domains that match the given query. The picture below also includes the --debug global flag. -![Alt text](.img/debug_ns_search.png "WhoIs Tree View") +![Alt text](.img/whois_ns.png "WhoIs Tree View") ```bash # Perform a reverse NS lookup for google.com crowsnest whois -n google.com ``` -### Subdomain Scan -CrowsNest can perform a subdomain scan for a given domain. -This provides a list of all subdomains that match the given query. -![Alt text](.img/subdomains_lookup.png "WhoIs Tree View") -```bash -# Perform a WHOIS subdomain scan for google.com -crowsnest whois -d google.com -s -``` + --- @@ -206,6 +216,7 @@ crowsnest whois -d google.com -s CrowsNest supports Hunter.io lookups. Hunter.io lookups require a separate API Key from the Dehashed API. This can be set using the `set-hunter` command. +![Alt text](.img/set-hunter.png "Set Dehashed Key") ```bash # Set the Hunter.io API key crowsnest set-hunter @@ -214,25 +225,16 @@ crowsnest set-hunter ### Domain Search CrowsNest can perform a domain search for a given domain. This provides information about company including a description, social media information and any technologies in use. -![Alt text](.img/hunter_domain_search.png "Hunter.io Domain Search") +![Alt text](.img/hunter_domain.png "Hunter.io Domain Search") ```bash # Perform a Hunter.io domain search for example.com crowsnest hunter -d example.com -D ``` -### Email Finder -CrowsNest can perform an email finder search for a given domain, first name, and last name. -This provides information about a user including a confidence score, and any social media accounts linked to a first name, last name and email. -![Alt text](.img/hunter_email_finder.png "Hunter.io Email Finder") -```bash -# Perform a Hunter.io email finder search for example.com -crowsnest hunter -d example.com -F John -L Doe -E -``` - ### Email Verification CrowsNest can perform an email verification search for a given email. This provides a verification and score of a given email address. -![Alt text](.img/email_verification.png "Hunter.io Email Verification") +![Alt text](.img/hunter_emailverification.png "Hunter.io Email Verification") ```bash # Perform a Hunter.io email verification search for example@target.com crowsnest hunter -e example@target.com -V @@ -241,16 +243,25 @@ crowsnest hunter -e example@target.com -V ### Company Enrichment CrowsNest can perform a company enrichment search for a given domain. This provides information about a company given its domain. -![Alt text](.img/company_enrichment.png "Hunter.io Company Enrichment") +![Alt text](.img/hunter_company.png "Hunter.io Company Enrichment") ```bash # Perform a Hunter.io company enrichment search for example.com crowsnest hunter -d example.com -C ``` +### Email Finder +CrowsNest can perform an email finder search for a given domain, first name, and last name. +This provides information about a user including a confidence score, and any social media accounts linked to a first name, last name and email. +![Alt text](.img/hunter_emailfind.png "Hunter.io Email Finder") +```bash +# Perform a Hunter.io email finder search for example.com +crowsnest hunter -d example.com -F John -L Doe -E +``` + ### Person Enrichment CrowsNest can perform a person enrichment search for a given email. This provides information about a user given an email address.. -![Alt text](.img/person_enrichment.png "Hunter.io Person Enrichment") +![Alt text](.img/hunter_person.png "Hunter.io Person Enrichment") ```bash # Perform a Hunter.io person enrichment search for example@target.com crowsnest hunter -e example@target.com -P @@ -376,7 +387,7 @@ crowsnest logs -s "05-01-2025" -v error,fatal ## 🎉 Sample Run ```bash -ar1ste1a@kali:~$ crowsnest api -D .com -o -f json +ar1ste1a@kali:~$ crowsnest dehashed -D .com -o -f json Making 3 Requests for 10000 Records (30000 Total) [*] Querying Dehashed API... [*] Performing Request... diff --git a/cmd/hunter.go b/cmd/hunter.go index af3fb7d..7d2fa9b 100644 --- a/cmd/hunter.go +++ b/cmd/hunter.go @@ -166,7 +166,7 @@ var ( fmt.Println("Email Find Result:") var ( - headers = []string{"Email", "Score", "Domain", "Accept All", "Position", "Twitter", "Linkedin", "Phone Number", "Company", "Sources", "Verification"} + headers = []string{"Email", "Score", "Domain", "Accept All", "Position", "Twitter", "Linkedin", "Phone Number", "Company", "Verification"} rows [][]string ) @@ -180,7 +180,6 @@ var ( result.LinkedinURL, result.PhoneNumber, result.Company, - fmt.Sprintf("%v", result.Sources), fmt.Sprintf("%v", result.Verification), }) diff --git a/cmd/whois.go b/cmd/whois.go index cddf5a2..20b964b 100644 --- a/cmd/whois.go +++ b/cmd/whois.go @@ -26,7 +26,7 @@ func init() { whoisCmd.Flags().StringVarP(&whoisNSAddress, "ns", "n", "", "NS hostname for reverse NS lookup") whoisCmd.Flags().StringVarP(&whoisInclude, "include", "I", "", "Up to 4 Terms to include in reverse WHOIS search (comma-separated)") whoisCmd.Flags().StringVarP(&whoisExclude, "exclude", "E", "", "Up to 4 Terms to exclude in reverse WHOIS search (comma-separated)") - whoisCmd.Flags().StringVarP(&whoisReverseType, "type", "t", "registrant", "Type of reverse WHOIS search ([default] current or historic)") + whoisCmd.Flags().StringVarP(&whoisReverseType, "type", "t", "current", "Type of reverse WHOIS search ([default] current or historic)") whoisCmd.Flags().StringVarP(&whoisOutputFormat, "format", "f", "text", "Output format (text, json)") whoisCmd.Flags().StringVarP(&whoisOutputFile, "output", "o", "whois", "File to output results to including extension") whoisCmd.Flags().BoolVarP(&whoisShowCredits, "credits", "c", false, "Show remaining WHOIS credits") @@ -118,57 +118,59 @@ var ( if whoisDomain != "" { fmt.Println("[*] Performing WHOIS lookup...") - // Domain lookup - result, err := w.WhoisSearch(whoisDomain) - if err != nil { - if debugGlobal { - debug.PrintInfo("failed to perform whois search") - debug.PrintError(err) + if !whoisHistory && !whoisSubdomainScan { + // Domain lookup + result, err := w.WhoisSearch(whoisDomain) + if err != nil { + if debugGlobal { + debug.PrintInfo("failed to perform whois search") + debug.PrintError(err) + } + zap.L().Error("whois_search", + zap.String("message", "failed to perform whois search"), + zap.Error(err), + ) + fmt.Printf("Error performing WHOIS lookup: %v\n", err) + return } - zap.L().Error("whois_search", - zap.String("message", "failed to perform whois search"), - zap.Error(err), - ) - fmt.Printf("Error performing WHOIS lookup: %v\n", err) - return - } - if whoisShowCredits { - checkBalance(w) - } - - // Fix the output format to use proper formatting - fmt.Println("WHOIS Lookup Result:") - - // Store the record - err = sqlite.StoreWhoisRecord(result) - if err != nil { - if debugGlobal { - debug.PrintInfo("failed to store whois record") - debug.PrintError(err) + if whoisShowCredits { + checkBalance(w) } - zap.L().Error("store_whois_record", - zap.String("message", "failed to store whois record"), - zap.Error(err), - ) - fmt.Printf("Error storing WHOIS record: %v\n", err) - // Continue execution even if storage fails - } - // Pretty Print WhoIs Record - pretty.WhoIsTree(whoisDomain, result) + // Fix the output format to use proper formatting + fmt.Println("WHOIS Lookup Result:") - // Write WhoIs Record to file - if len(result.DomainName) != 0 { - fmt.Printf("[*] Writing WHOIS record to file: %s%s\n", whoisOutputFile, fType.Extension()) - err = export.WriteWhoIsRecordToFile(result, whoisOutputFile, fType) - } else { - if debugGlobal { - debug.PrintInfo("no whois record to write to file") + // Store the record + err = sqlite.StoreWhoisRecord(result) + if err != nil { + if debugGlobal { + debug.PrintInfo("failed to store whois record") + debug.PrintError(err) + } + zap.L().Error("store_whois_record", + zap.String("message", "failed to store whois record"), + zap.Error(err), + ) + fmt.Printf("Error storing WHOIS record: %v\n", err) + // Continue execution even if storage fails + } + + // Pretty Print WhoIs Record + pretty.WhoIsTree(whoisDomain, result) + + // Write WhoIs Record to file + if len(result.DomainName) != 0 { + fmt.Printf("[*] Writing WHOIS record to file: %s%s\n", whoisOutputFile, fType.Extension()) + err = export.WriteWhoIsRecordToFile(result, whoisOutputFile, fType) + } else { + if debugGlobal { + debug.PrintInfo("no whois record to write to file") + } + zap.L().Info("write_whois_record", + zap.String("message", "no whois record to write to file"), + ) } - zap.L().Info("write_whois_record", - zap.String("message", "no whois record to write to file"), - ) } if whoisHistory { @@ -253,7 +255,6 @@ var ( ) fmt.Printf("Error performing subdomain scan: %v\n", err) } else { - fmt.Println("Subdomain Scan:") err = sqlite.StoreWhoisSubdomainRecords(subdomains) if err != nil { if debugGlobal { @@ -290,6 +291,7 @@ var ( } // Store the subdomains + fmt.Println("Subdomain Scan:") pretty.Table(headers, rows) } else { @@ -492,6 +494,12 @@ var ( } if whoisInclude != "" || whoisExclude != "" { + if debugGlobal { + debug.PrintInfo("performing reverse whois") + debug.PrintInfo("include: " + whoisInclude) + debug.PrintInfo("exclude: " + whoisExclude) + debug.PrintInfo("reverse type: " + whoisReverseType) + } // Reverse WHOIS includeTerms := []string{} if whoisInclude != "" { @@ -511,17 +519,10 @@ var ( } } - if whoisReverseType == "" { - if debugGlobal { - debug.PrintInfo("reverse type not specified, using default") - } - whoisReverseType = "current" - } else { - toLower := strings.ToLower(whoisReverseType) - if toLower != "current" && toLower != "historic" { - fmt.Println("[!] Error: Invalid reverse type. Must be 'current' or 'historic'.") - return - } + toLower := strings.ToLower(whoisReverseType) + if toLower != "current" && toLower != "historic" { + fmt.Println("[!] Error: Invalid reverse type. Must be 'current' or 'historic'.") + return } fmt.Println("[*] Performing reverse WHOIS lookup...") @@ -538,8 +539,42 @@ var ( fmt.Printf("Error performing reverse WHOIS: %v\n", err) return } - fmt.Println("Reverse WHOIS Result:") - fmt.Println(result) + + // Write to file + if len(result.DomainsList) > 0 { + fmt.Printf("[*] Writing reverse WHOIS results to file: %s%s\n", whoisOutputFile, fType.Extension()) + err = export.WriteIStringToFile(result, whoisOutputFile, fType) + if err != nil { + if debugGlobal { + debug.PrintInfo("failed to write reverse whois to file") + debug.PrintError(err) + } + zap.L().Error("write_reverse_whois", + zap.String("message", "failed to write reverse whois to file"), + zap.Error(err), + ) + fmt.Printf("Error writing reverse WHOIS to file: %v\n", err) + } + + fmt.Println("Reverse WHOIS Result:") + fmt.Printf("Total Domains: %d\n", result.DomainsCount) + + var ( + headers = []string{"Domain"} + rows [][]string + ) + + for _, r := range result.DomainsList { + rows = append(rows, []string{r}) + } + + pretty.Table(headers, rows) + } else { + fmt.Println("[!] No results found") + zap.L().Info("reverse_whois", + zap.String("message", "no results found"), + ) + } if whoisShowCredits { checkBalance(w) diff --git a/internal/dehashed/clientv2.go b/internal/dehashed/clientv2.go index f2087ae..5828a1d 100644 --- a/internal/dehashed/clientv2.go +++ b/internal/dehashed/clientv2.go @@ -166,7 +166,11 @@ func (dcv2 *DehashedClientV2) Search(searchRequest DehashedSearchRequest) (int, zap.String("message", "preparing search request"), ) } - reqBody, _ := json.Marshal(searchRequest) + + // Create a copy of the search request to avoid modifying the original + requestCopy := searchRequest + + reqBody, _ := json.Marshal(requestCopy) if dcv2.debug { j := string(reqBody) diff --git a/internal/dehashed/dehashed.go b/internal/dehashed/dehashed.go index 6c8a7cf..b67f1ba 100644 --- a/internal/dehashed/dehashed.go +++ b/internal/dehashed/dehashed.go @@ -3,11 +3,12 @@ package dehashed import ( "crowsnest/internal/debug" "crowsnest/internal/export" + "crowsnest/internal/pretty" "crowsnest/internal/sqlite" - "encoding/json" "fmt" "go.uber.org/zap" "os" + "strings" ) // Dehasher is a struct for querying the Dehashed API @@ -208,12 +209,10 @@ func (dh *Dehasher) buildRequest() { // parseResults parses the results and writes them to a file func (dh *Dehasher) parseResults() { - var data []byte - zap.L().Info("extracting_credentials") results := dh.client.GetResults() creds := results.ExtractCredentials() - fmt.Printf("\n\t[+] Discovered %d Credentials", len(creds)) + fmt.Printf(" [+] Discovered %d Credentials\n", len(creds)) err := sqlite.StoreDehashedCreds(creds) if err != nil { zap.L().Error("store_creds", @@ -234,28 +233,93 @@ func (dh *Dehasher) parseResults() { zap.L().Info("results_stored", zap.Int("count", len(results.Results))) if len(results.Results) > 0 { - fmt.Printf("\n\t[*] Writing entries to file: %s.%s", dh.options.OutputFile, dh.options.OutputFormat.String()) + var ( + headers = []string{"Email", "Username", "Password"} + rows [][]string + ) + + fmt.Printf(" [*] Writing entries to file: %s.%s\n", dh.options.OutputFile, dh.options.OutputFormat.String()) if !dh.options.CredsOnly { err := export.WriteToFile(results, dh.options.OutputFile, dh.options.OutputFormat) if err != nil { - fmt.Printf("\n[!] Error Writing to file: %v\n\tOutputting to terminal.", err) - data, err = json.MarshalIndent(results, "", " ") - fmt.Println(string(data)) - os.Exit(0) + fmt.Printf("[!] Error Writing to file: %v Outputting to terminal.\n", err) + zap.L().Error("write_results", + zap.String("message", "failed to write results to file"), + zap.Error(err), + ) } else { - fmt.Println("\n\t\t[*] Success\n") + fmt.Println(" [*] Success") } + + if dh.debug { + debug.PrintInfo("printing results table") + } + + headers = []string{"Name", "Email", "Username", "Password", "Address", "Phone", "Social", "Crypto Address", "Company"} + if len(results.Results) > 50 { + fmt.Println(" [-] Large number of results recovered, displaying first 50...") + for i := 0; i < 50; i++ { + r := results.Results[i] + rows = append(rows, []string{ + strings.Join(r.Name, ", "), strings.Join(r.Email, ", "), + strings.Join(r.Username, ", "), strings.Join(r.Password, ", "), + strings.Join(r.Address, ", "), strings.Join(r.Phone, ", "), + strings.Join(r.Social, ", "), strings.Join(r.CryptoCurrencyAddress, ", "), + strings.Join(r.Company, ", ")}) + } + } else { + for _, r := range results.Results { + rows = append(rows, []string{ + strings.Join(r.Name, ", "), strings.Join(r.Email, ", "), + strings.Join(r.Username, ", "), strings.Join(r.Password, ", "), + strings.Join(r.Address, ", "), strings.Join(r.Phone, ", "), + strings.Join(r.Social, ", "), strings.Join(r.CryptoCurrencyAddress, ", "), + strings.Join(r.Company, ", ")}) + } + } + + // Print Table + pretty.Table(headers, rows) } else { + if dh.debug { + debug.PrintInfo("extracting credentials") + } creds := results.ExtractCredentials() + if dh.debug { + debug.PrintInfo("writing credentials to file") + } err := export.WriteCredsToFile(creds, dh.options.OutputFile, dh.options.OutputFormat) if err != nil { - fmt.Printf("\n[!] Error Writing to file: %v\n\tOutputting to terminal.", err) - data, err = json.MarshalIndent(creds, "", " ") - fmt.Println(string(data)) - os.Exit(0) + fmt.Printf("[!] Error Writing to file: %v\n Outputting to terminal.", err) + zap.L().Error("write_creds", + zap.String("message", "failed to write creds to file"), + zap.Error(err), + ) } else { - fmt.Println("\n\t\t[*] Success\n") + fmt.Println(" [*] Success") } + + if dh.debug { + debug.PrintInfo("printing credentials table") + } + + headers = []string{"Email", "Username", "Password"} + if len(creds) > 50 { + fmt.Println(" [-] Large number of results recovered, displaying first 50...") + for i := 0; i < 50; i++ { + c := creds[i] + rows = append(rows, []string{c.Email, c.Username, c.Password}) + } + } else { + for _, c := range creds { + rows = append(rows, []string{c.Email, c.Username, c.Password}) + } + } + + // Print Table + pretty.Table(headers, rows) } + } else { + fmt.Println(" [-] No results found") } } diff --git a/internal/sqlite/whois.go b/internal/sqlite/whois.go index 201791f..adc9d59 100644 --- a/internal/sqlite/whois.go +++ b/internal/sqlite/whois.go @@ -628,3 +628,20 @@ func StoreWhoisLookup(lookup []LookupResult) error { return lastErr } + +// ReverseWhoisResponse represents the response from a reverse WHOIS lookup +type ReverseWhoisResponse struct { + RemainingCredits int `json:"remaining_credits"` + Data ReverseWhoisData `json:"data"` +} + +// ReverseWhoisData contains the domain count and list from a reverse WHOIS lookup +type ReverseWhoisData struct { + DomainsCount int `json:"domainsCount"` + DomainsList []string `json:"domainsList"` + NextPageSearchAfter *string `json:"nextPageSearchAfter"` +} + +func (rwd ReverseWhoisData) String() string { + return fmt.Sprintf("Domains Count: %d\nDomains List: %v\nNext Page Search After: %v\n", rwd.DomainsCount, rwd.DomainsList, rwd.NextPageSearchAfter) +} diff --git a/internal/whois/whois.go b/internal/whois/whois.go index 86fa787..928b961 100644 --- a/internal/whois/whois.go +++ b/internal/whois/whois.go @@ -298,7 +298,9 @@ func (w *DehashedWhoIs) WhoisHistory(domain string) ([]sqlite.HistoryRecord, err return whois.Data.Records, nil } -func (w *DehashedWhoIs) ReverseWHOIS(include []string, exclude []string, reverseType string) (string, error) { +func (w *DehashedWhoIs) ReverseWHOIS(include []string, exclude []string, reverseType string) (sqlite.ReverseWhoisData, error) { + var whois sqlite.ReverseWhoisData + if w.debug { debug.PrintInfo("performing reverse whois search") zap.L().Info("reverse_whois_debug", @@ -329,7 +331,7 @@ func (w *DehashedWhoIs) ReverseWHOIS(include []string, exclude []string, reverse zap.String("message", "failed to create request"), zap.Error(err), ) - return "", err + return whois, err } req.Header.Set("Content-Type", "application/json") @@ -356,7 +358,7 @@ func (w *DehashedWhoIs) ReverseWHOIS(include []string, exclude []string, reverse zap.String("message", "failed to perform request"), zap.Error(err), ) - return "", err + return whois, err } if res == nil { if w.debug { @@ -365,7 +367,7 @@ func (w *DehashedWhoIs) ReverseWHOIS(include []string, exclude []string, reverse zap.L().Error("reverse_whois", zap.String("message", "response was nil"), ) - return "", errors.New("response was nil") + return whois, errors.New("response was nil") } b, err := io.ReadAll(res.Body) @@ -378,7 +380,7 @@ func (w *DehashedWhoIs) ReverseWHOIS(include []string, exclude []string, reverse zap.String("message", "failed to read response body"), zap.Error(err), ) - return "", err + return whois, err } // Check for HTTP status code errors @@ -396,7 +398,7 @@ func (w *DehashedWhoIs) ReverseWHOIS(include []string, exclude []string, reverse zap.String("error", dhErr.Error()), zap.String("body_error", string(b)), ) - return "", &dhErr + return whois, &dhErr } if w.debug { @@ -404,7 +406,30 @@ func (w *DehashedWhoIs) ReverseWHOIS(include []string, exclude []string, reverse debug.PrintJson(fmt.Sprintf("Body: %s\n", string(b[:]))) } - return string(b), nil + var whoisResponse sqlite.ReverseWhoisResponse + err = json.Unmarshal(b, &whoisResponse) + if err != nil { + if w.debug { + debug.PrintInfo("failed to unmarshal response body") + debug.PrintError(err) + } + zap.L().Error("reverse_whois", + zap.String("message", "failed to unmarshal response body"), + zap.Error(err), + ) + return whois, err + } + + if w.debug { + debug.PrintInfo("unmarshalled response body") + debug.PrintJson(fmt.Sprintf("Remaining Credits: %d\n", whoisResponse.RemainingCredits)) + debug.PrintJson(fmt.Sprintf("Data: %v\n", whoisResponse.Data)) + } + w.balance = whoisResponse.RemainingCredits + + whois = whoisResponse.Data + + return whois, nil } func (w *DehashedWhoIs) WhoisIP(ipAddress string) ([]sqlite.LookupResult, error) {