├── img ├── a ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── cf-heroo.jpg └── gopher-cf-hero.png ├── cmd └── cf-hero │ └── main.go ├── internal ├── utils │ └── utils.go ├── dns │ └── dns.go ├── config │ └── config.go ├── http │ └── client.go └── scanner │ └── scanner.go ├── pkg └── models │ └── models.go ├── go.mod ├── go.sum └── README.md /img/a: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musana/CF-Hero/HEAD/img/1.png -------------------------------------------------------------------------------- /img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musana/CF-Hero/HEAD/img/2.png -------------------------------------------------------------------------------- /img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musana/CF-Hero/HEAD/img/3.png -------------------------------------------------------------------------------- /img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musana/CF-Hero/HEAD/img/4.png -------------------------------------------------------------------------------- /img/cf-heroo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musana/CF-Hero/HEAD/img/cf-heroo.jpg -------------------------------------------------------------------------------- /img/gopher-cf-hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musana/CF-Hero/HEAD/img/gopher-cf-hero.png -------------------------------------------------------------------------------- /cmd/cf-hero/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/gammazero/workerpool" 8 | "github.com/musana/cf-hero/internal/config" 9 | "github.com/musana/cf-hero/internal/scanner" 10 | "github.com/musana/cf-hero/internal/utils" 11 | ) 12 | 13 | func main() { 14 | fmt.Print(utils.Banner()) 15 | 16 | options := config.ParseOptions() 17 | var urls []string 18 | var domainList []string 19 | 20 | if options.File != "" && options.DomainList == "" { 21 | urls = utils.ReadFromFile(options.File) 22 | } else if options.File == "" && options.DomainList != "" { 23 | urls = append(urls, options.TargetDomain) 24 | domainList = utils.ReadFromFile(options.DomainList) 25 | } else { 26 | fi, _ := os.Stdin.Stat() 27 | if fi.Mode()&os.ModeNamedPipe == 0 { 28 | fmt.Println("[!] No data found in pipe. Urls must be given using pipe or f parameter!") 29 | os.Exit(1) 30 | } else { 31 | urls = utils.ReadFromStdin() 32 | } 33 | } 34 | 35 | scanner := scanner.New(options, urls, domainList) 36 | scanner.PreScan() 37 | 38 | wp := workerpool.New(options.Worker) 39 | for _, url := range urls { 40 | url := url 41 | wp.Submit(func() { 42 | scanner.Start(url) 43 | }) 44 | } 45 | wp.StopWait() 46 | } 47 | -------------------------------------------------------------------------------- /internal/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | ) 7 | 8 | func ReadFromStdin() []string { 9 | var urls []string 10 | scanner := bufio.NewScanner(os.Stdin) 11 | scanner.Split(bufio.ScanLines) 12 | 13 | for scanner.Scan() { 14 | urls = append(urls, scanner.Text()) 15 | } 16 | return urls 17 | } 18 | 19 | func ReadFromFile(fileName string) []string { 20 | var fileContent []string 21 | file, err := os.Open(fileName) 22 | if err != nil { 23 | return nil 24 | } 25 | defer file.Close() 26 | 27 | scanner := bufio.NewScanner(file) 28 | for scanner.Scan() { 29 | fileContent = append(fileContent, scanner.Text()) 30 | } 31 | return fileContent 32 | } 33 | 34 | func Contains(slice []string, elements string) bool { 35 | for _, s := range slice { 36 | if elements == s { 37 | return true 38 | } 39 | } 40 | return false 41 | } 42 | 43 | func Banner() string { 44 | return ` 45 | ____ __ 46 | _____/ __/ / /_ ___ _________ 47 | / ___/ /__ ___ / __ \/ _ \/ ___/ __ \ 48 | / /__/ ___/ (___) / / / / __/ / / /_/ / 49 | \___/_/ /_/ /_/\___/_/ \____/ 50 | 51 | @musana 52 | _____________________________________________ 53 | 54 | ` 55 | } 56 | -------------------------------------------------------------------------------- /internal/dns/dns.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | "regexp" 6 | 7 | "github.com/miekg/dns" 8 | "github.com/projectdiscovery/retryabledns" 9 | ) 10 | 11 | func GetARecords(domain string) ([]net.IP, []net.IP) { 12 | var cfIPs []net.IP 13 | var nonCFIPs []net.IP 14 | 15 | ips, _ := net.LookupIP(domain) 16 | if len(ips) > 0 { 17 | for _, ip := range ips { 18 | if ip.To4() != nil { 19 | result, _ := IsInCloudflareIPRange(ip) 20 | if result { 21 | cfIPs = append(cfIPs, ip) 22 | } else { 23 | nonCFIPs = append(nonCFIPs, ip) 24 | } 25 | } 26 | } 27 | } 28 | return cfIPs, nonCFIPs 29 | } 30 | 31 | func GetTXTRecords(domain string) ([]string, error) { 32 | resolvers := []string{"1.1.1.1:53", "8.8.8.8:53", "8.8.4.4:53", "1.0.0.1:53"} 33 | retries := 3 34 | dnsClient, err := retryabledns.New(resolvers, retries) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | TXTRecords, err := dnsClient.Query(domain, dns.TypeTXT) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | return TXTRecords.TXT, nil 45 | } 46 | 47 | func ExtractIPAddresses(input string) []string { 48 | ipPattern := `\b(?:\d{1,3}\.){3}\d{1,3}\b` 49 | re := regexp.MustCompile(ipPattern) 50 | return re.FindAllString(input, -1) 51 | } 52 | 53 | func IsInCloudflareIPRange(aIP net.IP) (bool, net.IP) { 54 | cloudflareRanges := []string{ 55 | "173.245.48.0/20", 56 | "103.21.244.0/22", 57 | "103.22.200.0/22", 58 | "103.31.4.0/22", 59 | "141.101.64.0/18", 60 | "108.162.192.0/18", 61 | "190.93.240.0/20", 62 | "188.114.96.0/20", 63 | "197.234.240.0/22", 64 | "198.41.128.0/17", 65 | "162.158.0.0/15", 66 | "104.16.0.0/13", 67 | "104.24.0.0/14", 68 | "172.64.0.0/13", 69 | "131.0.72.0/22", 70 | } 71 | 72 | for _, rangeStr := range cloudflareRanges { 73 | _, cidr, _ := net.ParseCIDR(rangeStr) 74 | if cidr.Contains(aIP) { 75 | return true, aIP 76 | } 77 | } 78 | 79 | return false, aIP 80 | } 81 | -------------------------------------------------------------------------------- /pkg/models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | ) 7 | 8 | type Options struct { 9 | File string 10 | Worker int 11 | Version bool 12 | HTTPMethod string 13 | UserAgent string 14 | Proxy string 15 | DomainList string 16 | TargetDomain string 17 | JA3 string 18 | CF bool 19 | NCF bool 20 | Censys bool 21 | SecurityTrails bool 22 | Shodan bool 23 | Zoomeye bool 24 | Verbose bool 25 | Title string 26 | } 27 | 28 | type CensysJSON struct { 29 | Code int `json:"code"` 30 | Status string `json:"status"` 31 | Result struct { 32 | Hits []struct { 33 | IP string `json:"ip"` 34 | LastUpdatedAt time.Time `json:"last_updated_at"` 35 | } `json:"hits"` 36 | Links struct { 37 | Next string `json:"next"` 38 | Prev string `json:"prev"` 39 | } `json:"links"` 40 | } `json:"result"` 41 | } 42 | 43 | type SecurityTrailsResponse struct { 44 | Endpoint string `json:"endpoint"` 45 | Pages int `json:"pages"` 46 | Records []struct { 47 | Values []struct { 48 | IP string `json:"ip"` 49 | IPCount int `json:"ip_count"` 50 | } `json:"values"` 51 | Type string `json:"type"` 52 | FirstSeen string `json:"first_seen"` 53 | LastSeen string `json:"last_seen"` 54 | Organizations []string `json:"organizations"` 55 | } `json:"records"` 56 | Type string `json:"type"` 57 | } 58 | 59 | type ShodanDNSHistoryResponse struct { 60 | Data []struct { 61 | Type string `json:"type"` 62 | Value string `json:"value"` 63 | LastSeen string `json:"last_seen"` 64 | FirstSeen string `json:"first_seen"` 65 | } `json:"data"` 66 | } 67 | 68 | type ZoomeyeResponse struct { 69 | Code int `json:"code"` 70 | Message string `json:"message"` 71 | Query string `json:"query"` 72 | Total int `json:"total"` 73 | Data []struct { 74 | IP string `json:"ip"` 75 | Port json.RawMessage `json:"port"` 76 | Domain string `json:"domain"` 77 | UpdateTime string `json:"update_time"` 78 | } `json:"data"` 79 | Facets map[string]interface{} `json:"facets"` 80 | } 81 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/musana/cf-hero 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/Danny-Dasilva/CycleTLS/cycletls v0.0.0-20220620102923-c84d740b4757 7 | github.com/gammazero/workerpool v1.1.3 8 | github.com/hashicorp/go-retryablehttp v0.7.4 9 | github.com/miekg/dns v1.1.55 10 | github.com/projectdiscovery/goflags v0.1.11 11 | github.com/projectdiscovery/retryabledns v1.0.24 12 | github.com/schollz/progressbar/v3 v3.13.1 13 | golang.org/x/net v0.12.0 14 | gopkg.in/yaml.v2 v2.4.0 15 | ) 16 | 17 | require ( 18 | github.com/Danny-Dasilva/fhttp v0.0.0-20220524230104-f801520157d6 // indirect 19 | github.com/Danny-Dasilva/utls v0.0.0-20220604023528-30cb107b834e // indirect 20 | github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect 21 | github.com/andybalholm/brotli v1.0.4 // indirect 22 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect 23 | github.com/aymerick/douceur v0.2.0 // indirect 24 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect 25 | github.com/dsnet/compress v0.0.1 // indirect 26 | github.com/fatih/color v1.18.0 // indirect 27 | github.com/gammazero/deque v0.2.0 // indirect 28 | github.com/gorilla/css v1.0.0 // indirect 29 | github.com/gorilla/websocket v1.5.0 // indirect 30 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 31 | github.com/mattn/go-colorable v0.1.13 // indirect 32 | github.com/mattn/go-isatty v0.0.20 // indirect 33 | github.com/mattn/go-runewidth v0.0.14 // indirect 34 | github.com/microcosm-cc/bluemonday v1.0.24 // indirect 35 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect 36 | github.com/pkg/errors v0.9.1 // indirect 37 | github.com/projectdiscovery/blackrock v0.0.1 // indirect 38 | github.com/projectdiscovery/retryablehttp-go v1.0.15 // indirect 39 | github.com/projectdiscovery/utils v0.0.40-0.20230627061640-8ec2b35f851c // indirect 40 | github.com/rivo/uniseg v0.4.4 // indirect 41 | github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect 42 | go.uber.org/multierr v1.11.0 // indirect 43 | golang.org/x/crypto v0.11.0 // indirect 44 | golang.org/x/exp v0.0.0-20221019170559-20944726eadf // indirect 45 | golang.org/x/mod v0.8.0 // indirect 46 | golang.org/x/sys v0.25.0 // indirect 47 | golang.org/x/term v0.10.0 // indirect 48 | golang.org/x/text v0.11.0 // indirect 49 | golang.org/x/tools v0.6.0 // indirect 50 | gopkg.in/yaml.v3 v3.0.1 // indirect 51 | ) 52 | -------------------------------------------------------------------------------- /internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/musana/cf-hero/pkg/models" 8 | "github.com/projectdiscovery/goflags" 9 | "gopkg.in/yaml.v2" 10 | ) 11 | 12 | func ParseOptions() *models.Options { 13 | options := &models.Options{} 14 | flagSet := goflags.NewFlagSet() 15 | flagSet.SetDescription(`Unmask the origin IPs of Cloudflare-protected domains`) 16 | 17 | createGroup(flagSet, "General Options", "GENERAL OPTIONS", 18 | flagSet.IntVar(&options.Worker, "w", 16, "Worker count"), 19 | flagSet.StringVar(&options.File, "f", "", "Input file containing list of host/domain"), 20 | flagSet.BoolVar(&options.Verbose, "v", false, "Enable verbose output"), 21 | flagSet.StringVar(&options.Title, "title", "", "Specify HTML title to match (skip fetching from Cloudflare domain)"), 22 | ) 23 | 24 | createGroup(flagSet, "print options", "PRINT OPTIONS", 25 | flagSet.BoolVar(&options.CF, "cf", false, "Print domains behind Cloudflare"), 26 | flagSet.BoolVar(&options.NCF, "non-cf", false, "Print domains not behind Cloudflare"), 27 | ) 28 | 29 | createGroup(flagSet, "sources", "SOURCES", 30 | flagSet.BoolVar(&options.Censys, "censys", false, "Include Censys in scanning"), 31 | flagSet.BoolVar(&options.SecurityTrails, "securitytrails", false, "Include SecurityTrails historical DNS records in scanning"), 32 | flagSet.BoolVar(&options.Shodan, "shodan", false, "Include Shodan historical DNS records in scanning"), 33 | flagSet.BoolVar(&options.Zoomeye, "zoomeye", false, "Include Zoomeye in scanning"), 34 | flagSet.StringVar(&options.DomainList, "dl", "", "Domain list for sub/domain scanning"), 35 | flagSet.StringVar(&options.TargetDomain, "td", "", "Target domain for sub/domain scanning"), 36 | ) 37 | 38 | createGroup(flagSet, "configuration", "CONFIGURATION", 39 | flagSet.StringVar(&options.HTTPMethod, "hm", "GET", "HTTP method."), 40 | flagSet.StringVar(&options.JA3, "ja3", "772,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,18-10-16-23-45-35-5-11-13-65281-0-51-43-17513-27,29-23-24,0", "JA3 String"), 41 | flagSet.StringVar(&options.UserAgent, "ua", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/113.0", "HTTP User-Agent"), 42 | flagSet.StringVar(&options.Proxy, "px", "", "HTTP proxy URL"), 43 | ) 44 | 45 | _ = flagSet.Parse() 46 | 47 | return options 48 | } 49 | 50 | func createGroup(flagSet *goflags.FlagSet, groupName, description string, flags ...*goflags.FlagData) { 51 | flagSet.SetGroup(groupName, description) 52 | for _, currentFlag := range flags { 53 | currentFlag.Group(groupName) 54 | } 55 | } 56 | 57 | func ReadAPIKeys(source string) []string { 58 | home := os.Getenv("HOME") 59 | if home == "" { 60 | home = os.Getenv("USERPROFILE") // Windows için 61 | } 62 | 63 | configPath := home + "/.config/cf-hero.yaml" 64 | f, err := os.ReadFile(configPath) 65 | if err != nil { 66 | fmt.Printf("[!] Error reading config file %s: %v\n", configPath, err) 67 | return nil 68 | } 69 | 70 | var apiKeys map[string][]string 71 | err = yaml.Unmarshal(f, &apiKeys) 72 | if err != nil { 73 | fmt.Printf("[!] Error parsing YAML from %s: %v\n", configPath, err) 74 | return nil 75 | } 76 | 77 | keys, ok := apiKeys[source] 78 | if !ok { 79 | fmt.Printf("[!] No API keys found for source '%s' in %s\n", source, configPath) 80 | return nil 81 | } 82 | 83 | return keys 84 | } 85 | -------------------------------------------------------------------------------- /internal/http/client.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "strings" 9 | "time" 10 | 11 | "github.com/Danny-Dasilva/CycleTLS/cycletls" 12 | "golang.org/x/net/html" 13 | ) 14 | 15 | func NewHTTPClient(proxy string, targetURL string) *http.Client { 16 | transport := &http.Transport{ 17 | DialContext: (&net.Dialer{ 18 | Timeout: 30 * time.Second, 19 | KeepAlive: 30 * time.Second, 20 | }).DialContext, 21 | MaxIdleConns: 100, 22 | IdleConnTimeout: 90 * time.Second, 23 | TLSHandshakeTimeout: 10 * time.Second, 24 | ExpectContinueTimeout: 1 * time.Second, 25 | ResponseHeaderTimeout: 30 * time.Second, 26 | DisableKeepAlives: false, 27 | TLSClientConfig: &tls.Config{ 28 | InsecureSkipVerify: true, 29 | }, 30 | } 31 | 32 | if proxy != "" { 33 | transport.Proxy = http.ProxyFromEnvironment 34 | } 35 | 36 | return &http.Client{ 37 | Transport: transport, 38 | Timeout: 60 * time.Second, 39 | } 40 | } 41 | 42 | func RequestBuilder(url string, token string, method string, userAgent string) *http.Request { 43 | req, _ := http.NewRequest(method, url, nil) 44 | if token != "" { 45 | req.Header.Set("Authorization", "Basic "+token) 46 | } 47 | if userAgent != "" { 48 | req.Header.Set("User-Agent", userAgent) 49 | } else { 50 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") 51 | } 52 | req.Header.Set("Accept", "*/*") 53 | req.Header.Set("Accept-Language", "en-US,en;q=0.9") 54 | req.Header.Set("Connection", "keep-alive") 55 | return req 56 | } 57 | 58 | func RequestBuilderWithHost(url, hostHeader, httpMethod, userAgent string) *http.Request { 59 | req, _ := http.NewRequest(httpMethod, url, nil) 60 | req.Header.Add("User-Agent", userAgent) 61 | req.Header.Add("Connection", "Close") 62 | req.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8") 63 | req.Host = hostHeader 64 | 65 | return req 66 | } 67 | 68 | func CycleTLSforJA3(url, ja3, userAgent, proxy string) (cycletls.Response, error) { 69 | client := cycletls.Init() 70 | 71 | response, err := client.Do(url, cycletls.Options{ 72 | Body: "", 73 | Ja3: ja3, 74 | UserAgent: userAgent, 75 | Timeout: 5, 76 | Proxy: proxy, 77 | DisableRedirect: false, 78 | Headers: map[string]string{}, 79 | }, "GET") 80 | 81 | return response, err 82 | } 83 | 84 | func GetHTMLTitle(doc *html.Node) string { 85 | var title string 86 | var traverse func(*html.Node) 87 | traverse = func(n *html.Node) { 88 | if n.Type == html.ElementNode && n.Data == "title" && n.FirstChild != nil { 89 | title = n.FirstChild.Data 90 | return 91 | } 92 | for c := n.FirstChild; c != nil; c = c.NextSibling { 93 | traverse(c) 94 | } 95 | } 96 | traverse(doc) 97 | return title 98 | } 99 | 100 | // CheckPort checks if a port is open on a host 101 | func CheckPort(host string, port string) bool { 102 | timeout := time.Second * 2 103 | conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, port), timeout) 104 | if err != nil { 105 | return false 106 | } 107 | if conn != nil { 108 | conn.Close() 109 | return true 110 | } 111 | return false 112 | } 113 | 114 | // GetHTMLTitleWithPortCheck tries to get HTML title with port checking 115 | func GetHTMLTitleWithPortCheck(ip string, ja3, userAgent, proxy string) (string, error) { 116 | // First try HTTP (port 80) 117 | if CheckPort(ip, "80") { 118 | resp, err := CycleTLSforJA3("http://"+ip, ja3, userAgent, proxy) 119 | if err == nil && resp.Body != "" { 120 | reader := strings.NewReader(resp.Body) 121 | doc, err := html.Parse(reader) 122 | if err == nil { 123 | title := GetHTMLTitle(doc) 124 | if title != "" { 125 | return title, nil 126 | } 127 | } 128 | } 129 | } 130 | 131 | // If HTTP fails or returns empty title, try HTTPS (port 443) 132 | if CheckPort(ip, "443") { 133 | resp, err := CycleTLSforJA3("https://"+ip, ja3, userAgent, proxy) 134 | if err == nil && resp.Body != "" { 135 | reader := strings.NewReader(resp.Body) 136 | doc, err := html.Parse(reader) 137 | if err == nil { 138 | title := GetHTMLTitle(doc) 139 | if title != "" { 140 | return title, nil 141 | } 142 | } 143 | } 144 | } 145 | 146 | // Try with standard HTTP client to follow redirects 147 | client := NewHTTPClient(proxy, "") 148 | client.CheckRedirect = func(req *http.Request, via []*http.Request) error { 149 | if len(via) >= 10 { 150 | return fmt.Errorf("stopped after 10 redirects") 151 | } 152 | return nil 153 | } 154 | 155 | // Try HTTP first 156 | if CheckPort(ip, "80") { 157 | resp, err := client.Get("http://" + ip) 158 | if err == nil { 159 | defer resp.Body.Close() 160 | doc, err := html.Parse(resp.Body) 161 | if err == nil { 162 | title := GetHTMLTitle(doc) 163 | if title != "" { 164 | return title, nil 165 | } 166 | } 167 | } 168 | } 169 | 170 | // Try HTTPS if HTTP fails 171 | if CheckPort(ip, "443") { 172 | resp, err := client.Get("https://" + ip) 173 | if err == nil { 174 | defer resp.Body.Close() 175 | doc, err := html.Parse(resp.Body) 176 | if err == nil { 177 | title := GetHTMLTitle(doc) 178 | if title != "" { 179 | return title, nil 180 | } 181 | } 182 | } 183 | } 184 | 185 | return "", fmt.Errorf("no accessible ports found or no title available") 186 | } 187 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Danny-Dasilva/CycleTLS/cycletls v0.0.0-20220620102923-c84d740b4757 h1:QH22vGS2DO07khPzKe3/CgFevznQkip5WNGEsQX7mFI= 2 | github.com/Danny-Dasilva/CycleTLS/cycletls v0.0.0-20220620102923-c84d740b4757/go.mod h1:R4Hj85bdRH8zqymQ/oZUCmEsODgP3NpUvTEJtaVai7Y= 3 | github.com/Danny-Dasilva/fhttp v0.0.0-20220418170016-5ea1c560e6a8/go.mod h1:t534vrahRNn9ax1tRiYSUvwJSa9jWaYYgETlfodBPm4= 4 | github.com/Danny-Dasilva/fhttp v0.0.0-20220524230104-f801520157d6 h1:Wzbitazy0HugGNRACX7ZB1En21LT/TiVF6YbxoTTqN8= 5 | github.com/Danny-Dasilva/fhttp v0.0.0-20220524230104-f801520157d6/go.mod h1:2IT2IFG+d+zzFuj3+ksGtVytcCBsF402zMNWHsWhD2U= 6 | github.com/Danny-Dasilva/utls v0.0.0-20220418055514-7c61e0dbb504/go.mod h1:A2g8gPTJWDD3Y4iCTNon2vG3VcjdTBcgWBlZtopfNxU= 7 | github.com/Danny-Dasilva/utls v0.0.0-20220418175931-f38e470e04f2/go.mod h1:A2g8gPTJWDD3Y4iCTNon2vG3VcjdTBcgWBlZtopfNxU= 8 | github.com/Danny-Dasilva/utls v0.0.0-20220604023528-30cb107b834e h1:tqiguW0yAcIwQBQtD+d2rjBnboqB7CwG1OZ12F8avX8= 9 | github.com/Danny-Dasilva/utls v0.0.0-20220604023528-30cb107b834e/go.mod h1:ssfbVNUfWJVRfW41RTpedOUlGXSq3J6aLmirUVkDgJk= 10 | github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 h1:ZbFL+BDfBqegi+/Ssh7im5+aQfBRx6it+kHnC7jaDU8= 11 | github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809/go.mod h1:upgc3Zs45jBDnBT4tVRgRcgm26ABpaP7MoTSdgysca4= 12 | github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= 13 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 14 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 15 | github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= 16 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= 17 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 18 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 19 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 20 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ= 21 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4= 22 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 23 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 24 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 25 | github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= 26 | github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= 27 | github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= 28 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 29 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 30 | github.com/gammazero/deque v0.2.0 h1:SkieyNB4bg2/uZZLxvya0Pq6diUlwx7m2TeT7GAIWaA= 31 | github.com/gammazero/deque v0.2.0/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= 32 | github.com/gammazero/workerpool v1.1.3 h1:WixN4xzukFoN0XSeXF6puqEqFTl2mECI9S6W44HWy9Q= 33 | github.com/gammazero/workerpool v1.1.3/go.mod h1:wPjyBLDbyKnUn2XwwyD3EEwo9dHutia9/fwNmSHWACc= 34 | github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= 35 | github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= 36 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 37 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 38 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 39 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 40 | github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= 41 | github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 42 | github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= 43 | github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= 44 | github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= 45 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 46 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 47 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 48 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 49 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 50 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 51 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 52 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 53 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= 54 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 55 | github.com/microcosm-cc/bluemonday v1.0.24 h1:NGQoPtwGVcbGkKfvyYk1yRqknzBuoMiUrO6R7uFTPlw= 56 | github.com/microcosm-cc/bluemonday v1.0.24/go.mod h1:ArQySAMps0790cHSkdPEJ7bGkF2VePWH773hsJNSHf8= 57 | github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= 58 | github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= 59 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= 60 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= 61 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 62 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 63 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 64 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 65 | github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ= 66 | github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss= 67 | github.com/projectdiscovery/goflags v0.1.11 h1:C4UTO3SM5Vfy1J2sdhukm7wONW/tljMpUMNKue5ie00= 68 | github.com/projectdiscovery/goflags v0.1.11/go.mod h1:wC5uJonjddDcCqDNfPq+03nRessSB/LLaaIea4w47ws= 69 | github.com/projectdiscovery/retryabledns v1.0.24 h1:CbC0a1EcyRDBcGFHZDGfW5orkWkOCfa0mAMF060XJpI= 70 | github.com/projectdiscovery/retryabledns v1.0.24/go.mod h1:bCmv0neiqgemgmFChevfX2BgCxIp8sn5OnbwL1Gov9M= 71 | github.com/projectdiscovery/retryablehttp-go v1.0.15 h1:kP9x9f++QimRwb8ABqnI1dhEymvnZXS2Wp2Zs4rWk/c= 72 | github.com/projectdiscovery/retryablehttp-go v1.0.15/go.mod h1:+OzSFUv3sQcPt+MgbNx6X/Q3ESxqPUQSphqG5kxoIgI= 73 | github.com/projectdiscovery/utils v0.0.40-0.20230627061640-8ec2b35f851c h1:mNV/VSMi9wVpq3gcz4km2oUml9M+La20GaFoJPe3Ils= 74 | github.com/projectdiscovery/utils v0.0.40-0.20230627061640-8ec2b35f851c/go.mod h1:rrd8dTBuKEScNMLgs1Xiu8rPCVeR0QTzmRcQ5iM3ymo= 75 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 76 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= 77 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 78 | github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI= 79 | github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= 80 | github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE= 81 | github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ= 82 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 83 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 84 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 85 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 86 | github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= 87 | gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= 88 | go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= 89 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 90 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 91 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 92 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 93 | golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 94 | golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 95 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 96 | golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= 97 | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= 98 | golang.org/x/exp v0.0.0-20221019170559-20944726eadf h1:nFVjjKDgNY37+ZSYCJmtYf7tOlfQswHqplG2eosjOMg= 99 | golang.org/x/exp v0.0.0-20221019170559-20944726eadf/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= 100 | golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= 101 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 102 | golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 103 | golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 104 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 105 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 106 | golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 107 | golang.org/x/net v0.0.0-20220615171555-694bf12d69de/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 108 | golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= 109 | golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= 110 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 111 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 112 | golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 113 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 114 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 115 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 116 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 117 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 118 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 119 | golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 120 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 121 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 122 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= 123 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 124 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= 125 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 126 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 127 | golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 128 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 129 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 130 | golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= 131 | golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= 132 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 133 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 134 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 135 | golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= 136 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 137 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 138 | golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= 139 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 140 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 141 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 142 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 143 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 144 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 145 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## CF-Hero 2 | 3 |

4 | CF-Hero 5 |
6 |

7 | 8 |

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

17 | 18 |

19 | What's it? • 20 | Features • 21 | Background • 22 | Installation • 23 | Usage • 24 | Running cf-hero • 25 | ZoomEye (Sponsor) • 26 | To Do 27 |

28 | 29 |

30 | 31 |

32 | 33 | # What's it? 34 | CF-Hero is a comprehensive reconnaissance tool developed to discover the real IP addresses of web applications protected by Cloudflare. It performs multi-source intelligence gathering through various methods. 35 | 36 | ### DNS Reconnaissance 37 | - Current DNS records (A, TXT) 38 | - Historical DNS data analysis 39 | - Associated domain discovery 40 | 41 | ### Intelligence Sources 42 | - ZoomEye search engine 43 | - Censys search engine 44 | - Shodan search engine 45 | - SecurityTrails historical records 46 | - Active DNS enumeration 47 | - Related domain correlation 48 | 49 | The tool analyzes data from these sources to identify potential origin IP addresses of Cloudflare-protected targets. It validates findings through response analysis to minimize false positives. 50 | 51 | **a simple flowchart of the tool** 52 | 53 | ``` 54 | 55 | ┌──────────┐ 56 | │ │ ┌─────────┐ 57 | │ Domain │───────►│ Check A │ 58 | │ │ │ Records │ 59 | └──────────┘ └────┬────┘ 60 | │ 61 | ┌──────────────────┘ 62 | │ 63 | ▼ 64 | ┌─────────────┐ 65 | │Is it behind │ YES 66 | │ CloudFlare │────────────────────────────────────────┐ 67 | └─────────────┘ │ 68 | │ │ 69 | │ │ 70 | │ ▼ 71 | │ ┌──────────────────────────┐ 72 | │ │ Check the domain from │ 73 | │ ┌─────────────────│ various sources │───────────────────┐ 74 | │ │ └──────────────────────────┘ │ 75 | │ │ │ │ │ 76 | │ │ │ │ │ 77 | │ │ │ │ │ 78 | │ ▼ ▼ ▼ ▼ 79 | │ ┌────────────────┐ ┌─────────────┐ ┌───────────┐ ┌─────────────┐ 80 | │ │ Historical DNS │ │ Current DNS │ ┌─┤ OSINT │ │ Sub/domains │ 81 | │ ┌──│ Records │ ┌─│ Records │ │ └───────────┘ ┌─│ │ 82 | │ │ └────────────────┘ │ └─────────────┘ │ ┌─────────┐ │ └─────────────┘ 83 | │ │ ┌──────────────┐ │ ┌───────────┐ ├──►│ ZoomEye │ │ ┌─────────────┐ 84 | │ ├───►│SecurityTrails│ ├──►│ TXT │ │ └─────────┘ │ │ sub(domains)│ 85 | │ │ └──────────────┘ │ └───────────┘ │ ┌─────────┐ └──►│ used by the │ 86 | │ │ ┌──────────────┐ │ ┌───────────┐ ├──►│ Shodan │ │ same company│ 87 | │ │ └───►│Completedns │ └──►│ A │ │ └─────────┘ └─────────────┘ │ 88 | │ │ └──────────────┘ └───────────┘ │ ┌─────────┐ │ 89 | │ │ └──►│ Censys │ │ 90 | │ │ └─────────┘ │ 91 | │ │ │ 92 | │ │ │ 93 | │ └────────────────────────────────────────────┬────────────────────────────────────────────┘ 94 | │ │ 95 | NO │ │ 96 | │ │ 97 | │ │ 98 | │ ▼ 99 | │ ┌──────────────────────────────────────┐ 100 | │ │ Establish direct HTTP connections to │ 101 | │ │ each discovered IP address │ 102 | │ └──────────────────────────────────────┘ 103 | │ │ 104 | │ │ 105 | │ │ 106 | │ ▼ 107 | │ ┌─────────────────────────────┐ 108 | │ │ Compare the HTML title with │ 109 | │ │ the target's title │ 110 | │ └─────────────────────────────┘ 111 | │ │ 112 | │ │ 113 | │ │ 114 | │ ▼ 115 | │ ┌─────────────────────┐ 116 | │ │ │ YES 117 | │ │ Are they the same ? │─────────────────────┐ 118 | │ │ │ │ 119 | │ └─────────────────────┘ ▼ 120 | │ │ ┌───────────────┐ 121 | │ │NO │ Real IP found │ 122 | │ │ └───────┬───────┘ 123 | │ ▼ │ 124 | │ ┌──────────┐ │ 125 | └───────────────────────────────────────────►│ FINISH │◄───────────────────────────┘ 126 | └──────────┘ 127 | 128 | 129 | ``` 130 | 131 | # Feautures 132 | ### Features 133 | 134 | - DNS Reconnaissance 135 | - Checks current DNS records (A, TXT) 136 | - Extracts domains behind Cloudflare 137 | - Extracts domains not behind Cloudflare 138 | - User-provided HTML title (in case of CF blocks you) 139 | - Smart colouring 140 | 141 | - Third-party Intelligence 142 | - ZoomEye integration 143 | - Censys integration 144 | - Shodan integration 145 | - SecurityTrails integration 146 | - Reverse IP lookup for associated domains 147 | 148 | - Advanced Features 149 | - Custom JA3 fingerprint support 150 | - Concurrent scanning capabilities 151 | - Standard input support (piping) 152 | - HTML title comparison for validation 153 | - Proxy support 154 | - Custom User-Agent configuration 155 | 156 | # Background 157 | ## Current DNS Records 158 | Let's take look at some use-case with misconfigured DNS settings. 159 | 160 | As you can see, a regular DNS query returns the IP address of the domain. For example, musana.net is behind Cloudflare (CF), but sometimes the domain has multiple A records, and some of them may not correspond to IP addresses associated with CF. (This DNS output is merely an illustrative example and may not represent the exact DNS answer for musana.net.) 161 | 162 | ``` 163 | ;; ANSWER SECTION: 164 | musana.net. 300 IN A 104.16.42.102 165 | musana.net. 300 IN A 104.16.43.102 166 | musana.net. 300 IN A 123.45.67.89 (Real IP exposed) 167 | musana.net. 300 IN A 123.45.76.98 (Real IP exposed) 168 | ``` 169 | 170 | The another case is related to TXT records. Sometimes domain is behind of CF but real IP of the domain may used in TXT records. CF-Hero check all TXT records then extract all IP address finally it try to connect IP which it found via HTTP. 171 | 172 | Let's say we have like DNS TXT records. As seen in the TXT records, there is SPF record. Some company can host own mail server and TXT records may contain IP which points to target domain. 173 | 174 | As you can see in the following DNS answer SPF record has some IP addresses. Cf-Hero also checks these. 175 | 176 | ``` 177 | ;; ANSWER SECTION: 178 | musana.net. 115 IN TXT "1password-site-verification=LROK6G5XFJG5NF76TE2FBTABUA" 179 | musana.net. 115 IN TXT "5fG-7tA-G4V" 180 | musana.net. 115 IN TXT "MS=ms16524910" 181 | musana.net. 115 IN TXT "OSSRH-74956" 182 | musana.net. 115 IN TXT "docker-verification=6910d334-a3fc-419c-89ac-57668af5bf0d" 183 | musana.net. 115 IN TXT "docusign=4c6d27bb-572e-4fd4-896c-81bfb0af0aa1" 184 | musana.net. 115 IN TXT "shopify-verification-code=1Ww5VsPpkIf32cJ5PdDHdguRk22K2R" 185 | musana.net. 115 IN TXT "shopify-verification-code=NM243t2faQbaJs8SRFMSEQAc4J9UQf" 186 | musana.net. 115 IN TXT "v=spf1 include:_spf.google.com include:cust-spf.exacttarget.com include:amazonses.com include:mail.zendesk.com include:servers.mcsv.net include:spf.mailjet.com ip4:216.74.162.13 ip4:216.74.162.14 ip4:153.95.95.86 ip4:18.197.36.5 -all" 187 | 188 | 189 | ``` 190 | 191 | ## OSINT 192 | 193 | OSINT is another technique to find real IP of any domain which is behind of CF. There are lots of special search engine for special purpose. Shodan and Censys are two of these. They provide more detail and technical information. These search engine scan whole internet continously and discover new assets or monitor and log changing in assets. When a domain which is not behind of CF get up, bot of these engine can log Real IP of the domain. After a while if the domain will take behind of cloudflare, their IP can be found using these search engine. 194 | 195 | CF-Hero checks censys and shodan too. (Note that when you use these services you have some limit due to API quota.) 196 | 197 | 198 | ## (Sub)Domains 199 | The other trick way is (sub)domain technique. Actually It doesn't have to be a subdomain It can be domain as well. The key point is here; domains should belong to same company. 200 | 201 | Let's say we have 2 domain. One of them is behind of CF but the otner is not. In this case, you connect to the domain which is not behind of CF then you change host header with domain which is behind of CF. If you get response of application's which is behind of CF, congruculations you bypassed CF. You can access web application from IP directly anymore. (and of course that's also depends on the configuration) 202 | 203 | 204 | Let's take look at closer 205 | 206 | 207 | ``` 208 | 209 | --> TCP --> blog.musana.net [123.45.67.89] ---> HTTPs -------------\ 210 | \ 211 | --> TCP --> api.musana.net [123.67.45.98] ----> HTTPs -----------\ \ 212 | \ \ 213 | --> TCP --> test.musana.net [123.89.44.88] ---> HTTPs -------------\  \ 214 | \___\____________________ 215 | --> TCP --> tools.musana.net [123.44.55.66] --> HTTPs -------------> | GET / HTTP/2 | 216 | | Host: musana.net | ====> Check & Compare Responses 217 | --> TCP --> admin.musana.net [33.44.123.45] --> HTTPs -------------->|_______________________| 218 | / / 219 | --> TCP --> ... [...] ------------------------> HTTPs ------------------>/ / 220 | / / 221 | --> TCP --> ... [...] ------------------------> HTTPs ---------------->/ / 222 | / / 223 | --> TCP --> random-test.com [55.44.11.33] ----> HTTPs -------------->/ / 224 | / 225 | --> TCP --> fsubsidiary.net [66.77.22.123] ---> HTTPs ----------------->/ 226 | 227 | 228 | ``` 229 | 230 | 231 | ## Historical DNS Records 232 | Historical DNS records services try to discover all domains on the Internet and record changes in the DNS records of these domains.The best known of these services is securitytrails. If a domain is published on internet with its real IP address these service's bot can log it's real IP address after then if the domain is taken behind of cloudflare, the real IP addresses can find out using these services. Thus, we can find the real ip address of a domain that has broadcast over the real ip address in the past. 233 | 234 | It uses the security trails service for historical DNS records. You can perform this scan using the `-securitytrails` parameter after entering the API key in the cf-hero.yaml file. 235 | 236 | 237 | # Installation Instructions 238 | 239 | cf-hero requires **go1.18** to install successfully. Run the following command to install. 240 | 241 | ``` 242 | go install -v github.com/musana/cf-hero/cmd/cf-hero@latest 243 | ``` 244 | 245 | # Usage 246 | 247 | ``` 248 | 249 | 250 | ____ __ 251 | _____/ __/ / /_ ___ _________ 252 | / ___/ /__ ___ / __ \/ _ \/ ___/ __ \ 253 | / /__/ ___/ (___) / / / / __/ / / /_/ / 254 | \___/_/ /_/ /_/\___/_/ \____/ 255 | 256 | @musana 257 | _____________________________________________ 258 | 259 | Unmask the origin IPs of Cloudflare-protected domains 260 | 261 | Usage: 262 | cf-hero [flags] 263 | 264 | Flags: 265 | GENERAL OPTIONS: 266 | -w int Worker count (default 16) 267 | -f string Input file containing list of host/domain 268 | -v Enable verbose output 269 | -title string Specify HTML title to match (skip fetching from Cloudflare domain) 270 | 271 | PRINT OPTIONS: 272 | -cf Print domains behind Cloudflare 273 | -non-cf Print domains not behind Cloudflare 274 | 275 | SOURCES: 276 | -censys Include Censys in scanning 277 | -securitytrails Include SecurityTrails historical DNS records in scanning 278 | -shodan Include Shodan historical DNS records in scanning 279 | -zoomeye Include Zoomeye in scanning 280 | -dl string Domain list for sub/domain scanning 281 | -td string Target domain for sub/domain scanning 282 | 283 | CONFIGURATION: 284 | -hm string HTTP method. (default "GET") 285 | -ja3 string JA3 String (default "772,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,18-10-16-23-45-35-5-11-13-65281-0-51-43-17513-27,29-23-24,0") 286 | -ua string HTTP User-Agent (default "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/113.0") 287 | -px string HTTP proxy URL 288 | 289 | 290 | 291 | ``` 292 | 293 | 294 | # Running CF-Hero 295 | The most basic running command. It checks A and TXT records by default. 296 | 297 | ``` 298 | # cat domains.txt | cf-hero 299 | ``` 300 | 301 | or you can pass "f" parameter to it. 302 | ``` 303 | # cf-hero -f domains.txt 304 | ``` 305 | 306 | 307 | Use the **zoomeye** parameter to include ZoomEye in the scan 308 | ``` 309 | # cat domain.txt | cf-hero -zoomeye 310 | ``` 311 | 312 | Use the **censys** parameter to include Shodan in the scan 313 | ``` 314 | # cat domain.txt | cf-hero -censys 315 | ``` 316 | 317 | Use the **shodan** parameter to include Shodan in the scan 318 | ``` 319 | # cat domain.txt | cf-hero -shodan 320 | ``` 321 | 322 | Use the **securitytrails** parameter to include Shodan in the scan 323 | ``` 324 | # cat domain.txt | cf-hero -securitytrails 325 | ``` 326 | 327 | Use the -td and -dl parameters to attempt to find the target domain's IP address by utilizing a list of domains or subdomains that are not behind Cloudflare. By specifying the IP addresses in the blocks where you have identified live IP addresses used by the target's cloud or on-premises infrastructure with the -dl parameter, you can find the real IP address of the target domain 328 | ``` 329 | # cf-hero -td https://musana.net -dl sub_domainlist.txt 330 | ``` 331 | 332 | to get domains behind of CF 333 | 334 | ``` 335 | # cf-hero -f domains.txt -cf 336 | ``` 337 | 338 | to get domains not behind of CF 339 | 340 | ``` 341 | # cf-hero -f domains.txt -non-cf 342 | ``` 343 | 344 | other options (custom ja3, proxy, worker, user agent) 345 | 346 | ``` 347 | # cf-hero -d https://musana.net -ua "Mozilla" -w 32 -ja3 "771,22..." -px "http://127.0.0.1:8080" 348 | ``` 349 | 350 | create cf-hero.yaml file under $HOME/.config/ directory to set the APIs key 351 | ``` 352 | # touch ~/.config/cf-hero.yaml 353 | 354 | // content of YAML file should be like; 355 | 356 | zoomeye: 357 | - "api_key_here" 358 | securitytrails: 359 | - "api_key_here" 360 | shodan: 361 | - "api_key_here" 362 | censys: 363 | - "api_key_here" 364 | 365 | ``` 366 | 367 | ## SS 368 | 369 | - Smart coloring: Yellow highlights indicate non-Cloudflare IPs, which will only be subject to checks. 370 | - The title parameter can be set if the first request is blocked by Cloudflare.(The title would be 'Just a moment...') If the title parameter is not set, the HTML title is automatically retrieved. 371 | 372 |

373 | 374 |

375 | 376 |

377 | 378 |

379 | 380 | ## 🏆 Sponsorship 381 | This project is proudly supported by [ZoomEye](https://www.zoomeye.ai). 382 | 383 |

384 | 385 |

386 | 387 | ## To Do 388 | - favicon search 389 | - viewdns integration 390 | -------------------------------------------------------------------------------- /internal/scanner/scanner.go: -------------------------------------------------------------------------------- 1 | package scanner 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net" 9 | "net/http" 10 | neturl "net/url" 11 | "strconv" 12 | "strings" 13 | "sync" 14 | "time" 15 | 16 | "github.com/fatih/color" 17 | "github.com/gammazero/workerpool" 18 | "github.com/musana/cf-hero/internal/config" 19 | "github.com/musana/cf-hero/internal/dns" 20 | httpClient "github.com/musana/cf-hero/internal/http" 21 | "github.com/musana/cf-hero/pkg/models" 22 | "github.com/schollz/progressbar/v3" 23 | ) 24 | 25 | type Scanner struct { 26 | Options *models.Options 27 | URLs []string 28 | Domains []string 29 | Bar *progressbar.ProgressBar 30 | mu sync.Mutex 31 | Stats struct { 32 | Total int 33 | Behind int 34 | NotBehind int 35 | TotalIPsScanned int 36 | RealIPsFound int 37 | } 38 | } 39 | 40 | func New(options *models.Options, urls []string, domains []string) *Scanner { 41 | // Filter valid URLs 42 | var validURLs []string 43 | for _, url := range urls { 44 | if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") { 45 | validURLs = append(validURLs, url) 46 | } 47 | } 48 | 49 | return &Scanner{ 50 | Options: options, 51 | URLs: validURLs, 52 | Domains: domains, 53 | } 54 | } 55 | 56 | func (s *Scanner) PreScan() { 57 | color.White("\n[*] Pre-scanning domains to identify Cloudflare protected ones...") 58 | processed := 0 59 | 60 | wp := workerpool.New(s.Options.Worker) 61 | var wg sync.WaitGroup 62 | 63 | for _, url := range s.URLs { 64 | url := url // capture variable 65 | wg.Add(1) 66 | wp.Submit(func() { 67 | defer wg.Done() 68 | domain := strings.Split(url, "//")[1] 69 | cfIPs, _ := dns.GetARecords(domain) 70 | 71 | s.mu.Lock() 72 | processed++ 73 | s.Stats.Total++ 74 | if len(cfIPs) > 0 { 75 | s.Stats.Behind++ 76 | } else { 77 | s.Stats.NotBehind++ 78 | } 79 | s.mu.Unlock() 80 | }) 81 | } 82 | 83 | wg.Wait() 84 | wp.StopWait() 85 | 86 | color.White("[+] Found %d/%d domains behind Cloudflare", s.Stats.Behind, s.Stats.Total) 87 | 88 | // Show provided HTML title if exists 89 | if s.Options.Title != "" { 90 | color.Cyan("[*] Using provided HTML title: %s", s.Options.Title) 91 | } 92 | 93 | // Build technique string 94 | var techniques []string 95 | techniques = append(techniques, "DNS (a, txt records)") // Always included 96 | 97 | if s.Options.Censys { 98 | techniques = append(techniques, "Censys") 99 | } 100 | if s.Options.SecurityTrails { 101 | techniques = append(techniques, "SecurityTrails") 102 | } 103 | if s.Options.Shodan { 104 | techniques = append(techniques, "Shodan") 105 | } 106 | if s.Options.Zoomeye { 107 | techniques = append(techniques, "ZoomEye") 108 | } 109 | 110 | color.Cyan("[*] Techniques: %s", strings.Join(techniques, ", ")) 111 | 112 | // Check API keys status at the beginning of the scan 113 | color.Cyan("\n[*] Checking API keys...") 114 | 115 | // Censys API key check 116 | if keys := config.ReadAPIKeys("censys"); len(keys) > 0 && keys[0] != "" { 117 | color.Green("[*] Censys API found in the config file") 118 | } else { 119 | color.Yellow("[!] Censys API could not find in the config file") 120 | s.Options.Censys = false 121 | } 122 | 123 | // SecurityTrails API key check 124 | if keys := config.ReadAPIKeys("securitytrails"); len(keys) > 0 && keys[0] != "" { 125 | color.Green("[*] SecurityTrails API found in the config file") 126 | } else { 127 | color.Yellow("[!] SecurityTrails API could not find in the config file") 128 | s.Options.SecurityTrails = false 129 | } 130 | 131 | // Shodan API key check 132 | if keys := config.ReadAPIKeys("shodan"); len(keys) > 0 && keys[0] != "" { 133 | color.Green("[*] Shodan API found in the config file") 134 | } else { 135 | color.Yellow("[!] Shodan API could not find in the config file") 136 | s.Options.Shodan = false 137 | } 138 | 139 | // Zoomeye API key check 140 | if keys := config.ReadAPIKeys("zoomeye"); len(keys) > 0 && keys[0] != "" { 141 | color.Green("[*] ZoomEye API found in the config file") 142 | } else { 143 | color.Yellow("[!] ZoomEye API could not find in the config file") 144 | s.Options.Zoomeye = false 145 | } 146 | color.Cyan("\n[*] Scan has been started for targets...") 147 | 148 | if s.Stats.Behind > 0 { 149 | s.Bar = progressbar.NewOptions(s.Stats.Behind, 150 | progressbar.OptionEnableColorCodes(true), 151 | progressbar.OptionShowCount(), 152 | progressbar.OptionSetWidth(40), 153 | progressbar.OptionSetDescription("[cyan][*][reset] Scanning Cloudflare protected domains..."), 154 | progressbar.OptionSetTheme(progressbar.Theme{ 155 | Saucer: "[green]=[reset]", 156 | SaucerHead: "[green]>[reset]", 157 | SaucerPadding: " ", 158 | BarStart: "[", 159 | BarEnd: "]", 160 | })) 161 | } 162 | } 163 | 164 | func (s *Scanner) Start(url string) { 165 | if s.Options.CF || s.Options.NCF { 166 | s.printDomains(url) 167 | return 168 | } 169 | 170 | // Check if URL starts with http or https 171 | if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { 172 | color.Red("[!] %s does not start with http or https. Skipping...", url) 173 | return 174 | } 175 | 176 | domain := strings.Split(url, "//")[1] 177 | cfIPs, nonCFIPs := dns.GetARecords(domain) 178 | 179 | if len(cfIPs) > 0 { 180 | var actualHTMLTitle string 181 | if s.Options.Title != "" { 182 | actualHTMLTitle = s.Options.Title 183 | } else { 184 | actualHTMLTitle, _ = s.getHTMLTitle(url) 185 | } 186 | 187 | color.White("[*] Target Information: [ %s (%s) (Cloudflare) - Title: %s ]", domain, cfIPs[0], actualHTMLTitle) 188 | 189 | if len(nonCFIPs) > 0 { 190 | s.checkARecords(url, nonCFIPs, cfIPs[0], actualHTMLTitle) 191 | s.mu.Lock() 192 | s.Stats.TotalIPsScanned += len(nonCFIPs) 193 | s.mu.Unlock() 194 | } 195 | 196 | s.getTXTRecords(domain, url, cfIPs[0], actualHTMLTitle) 197 | 198 | if s.Options.Censys { 199 | s.censysSearch(domain, url, cfIPs[0], actualHTMLTitle) 200 | } 201 | 202 | if s.Options.SecurityTrails { 203 | s.securityTrailsSearch(domain, url, cfIPs[0], actualHTMLTitle) 204 | } 205 | 206 | if s.Options.Shodan { 207 | s.shodanSearch(domain, url, cfIPs[0], actualHTMLTitle) 208 | } 209 | 210 | if s.Options.Zoomeye { 211 | s.zoomeyeSearch(domain, url, cfIPs[0], actualHTMLTitle) 212 | } 213 | 214 | if s.Options.DomainList != "" && s.Options.TargetDomain != "" { 215 | s.checkDomainList(url, cfIPs[1], actualHTMLTitle) 216 | } 217 | 218 | // Print final results only for the last domain 219 | if url == s.URLs[len(s.URLs)-1] { 220 | color.White("\n[*] Scan finished. %d real IP(s) found out of %d IP(s) scanned.", s.Stats.RealIPsFound, s.Stats.TotalIPsScanned) 221 | } 222 | } else { 223 | color.Red("[!] %s is not behind Cloudflare. Skipping...", domain) 224 | } 225 | } 226 | 227 | func (s *Scanner) printDomains(url string) { 228 | domain := strings.Split(url, "//")[1] 229 | cfIPs, nonCFIPs := dns.GetARecords(domain) 230 | 231 | if s.Options.CF { 232 | if len(cfIPs) > 0 { 233 | fmt.Println(url) 234 | } 235 | } 236 | if s.Options.NCF { 237 | if len(nonCFIPs) > 0 { 238 | fmt.Println(url) 239 | } 240 | } 241 | } 242 | 243 | func (s *Scanner) checkDomainList(url string, cfIP net.IP, actualHTMLTitle string) { 244 | for _, d := range s.Domains { 245 | parseIt := strings.Split(d, "//") 246 | domain := parseIt[1] 247 | 248 | targetDomain := strings.Split(s.Options.TargetDomain, "//")[1] 249 | 250 | _, nonCFIPs := dns.GetARecords(domain) 251 | 252 | if len(nonCFIPs) > 0 { 253 | for _, ip := range nonCFIPs { 254 | htmlTitle := s.checkHTMLTitle("http://"+ip.String(), targetDomain) 255 | if htmlTitle == actualHTMLTitle { 256 | s.printResult(url, cfIP, ip, "DNS A Record", actualHTMLTitle) 257 | } 258 | } 259 | } 260 | } 261 | } 262 | 263 | func (s *Scanner) checkHTMLTitle(urlStr string, hostHeader string) string { 264 | // URL'den host kısmını çıkar 265 | parsedURL, err := neturl.Parse(urlStr) 266 | if err != nil { 267 | return "" 268 | } 269 | 270 | title, err := httpClient.GetHTMLTitleWithPortCheck(parsedURL.Host, s.Options.JA3, s.Options.UserAgent, s.Options.Proxy) 271 | if err != nil { 272 | return "" 273 | } 274 | return title 275 | } 276 | 277 | func (s *Scanner) getHTMLTitle(urlStr string) (string, error) { 278 | // URL'den host kısmını çıkar 279 | parsedURL, err := neturl.Parse(urlStr) 280 | if err != nil { 281 | return "", err 282 | } 283 | 284 | return httpClient.GetHTMLTitleWithPortCheck(parsedURL.Host, s.Options.JA3, s.Options.UserAgent, s.Options.Proxy) 285 | } 286 | 287 | func (s *Scanner) checkARecords(url string, ips []net.IP, cfIP net.IP, actualHTMLTitle string) { 288 | for _, ip := range ips { 289 | if s.Options.Verbose { 290 | color.Cyan("[*] Non-Cloudflare IP(%s) found in %s's A record. Checking it...", ip.String(), url) 291 | } 292 | s.mu.Lock() 293 | s.Stats.TotalIPsScanned++ 294 | s.mu.Unlock() 295 | s.compareTitle(url, ip, cfIP, "A - Record", actualHTMLTitle) 296 | } 297 | } 298 | 299 | func (s *Scanner) getTXTRecords(domain, url string, cfIP net.IP, actualHTMLTitle string) { 300 | txtRecords, err := dns.GetTXTRecords(domain) 301 | if err != nil { 302 | return 303 | } 304 | 305 | var extractedIPs []string 306 | for _, txt := range txtRecords { 307 | extractedIP := dns.ExtractIPAddresses(txt) 308 | if len(extractedIP) > 0 { 309 | for _, ipAddress := range extractedIP { 310 | if !strings.Contains(strings.Join(extractedIPs, ","), ipAddress) { 311 | extractedIPs = append(extractedIPs, ipAddress) 312 | } 313 | } 314 | 315 | for _, ipx := range extractedIPs { 316 | netIP := net.ParseIP(ipx) 317 | if netIP.To4() != nil { 318 | if s.Options.Verbose { 319 | color.Magenta("[*] Non-Cloudflare IP(%s) found in %s's TXT record. Checking it...", netIP.String(), domain) 320 | } 321 | s.mu.Lock() 322 | s.Stats.TotalIPsScanned++ 323 | s.mu.Unlock() 324 | s.compareTitle(url, netIP, cfIP, "TXT - DNS Record", actualHTMLTitle) 325 | } 326 | } 327 | } 328 | } 329 | } 330 | 331 | func (s *Scanner) censysSearch(domain, url string, cfIP net.IP, actualHTMLTitle string) { 332 | keys := config.ReadAPIKeys("censys") 333 | if len(keys) == 0 || keys[0] == "" { 334 | return 335 | } 336 | 337 | key := keys[0] 338 | keyToBytes := []byte(key) 339 | token := base64.StdEncoding.EncodeToString(keyToBytes) 340 | 341 | censysURL := "https://search.censys.io/api/v2/hosts/search?q=" + domain + "&per_page=50&virtual_hosts=EXCLUDE" 342 | client := httpClient.NewHTTPClient(s.Options.Proxy, url) 343 | resp, err := client.Do(httpClient.RequestBuilder(censysURL, token, s.Options.HTTPMethod, s.Options.UserAgent)) 344 | if err != nil { 345 | if strings.Contains(err.Error(), "giving up after") { 346 | color.Yellow("[!] Censys API rate limit exceeded. Please try again later. (%s)", domain) 347 | } else { 348 | color.Yellow("[!] Error making request to Censys API: %v", err) 349 | } 350 | return 351 | } 352 | defer resp.Body.Close() 353 | 354 | // Check response status 355 | if resp.StatusCode != 200 { 356 | bodyBytes, _ := io.ReadAll(resp.Body) 357 | var errorResponse struct { 358 | Message string `json:"message"` 359 | } 360 | if err := json.Unmarshal(bodyBytes, &errorResponse); err == nil { 361 | if strings.Contains(errorResponse.Message, "exceeded the usage limits") { 362 | color.Yellow("[!] Censys API rate limit exceeded: %s", errorResponse.Message) 363 | } else { 364 | color.Yellow("[!] Censys API error: %s", errorResponse.Message) 365 | } 366 | } else { 367 | color.Yellow("[!] Censys API returned non-200 status code %d: %s", resp.StatusCode, string(bodyBytes)) 368 | } 369 | color.Yellow("[!] HTTP Status: %s", resp.Status) 370 | color.Yellow("[!] HTTP Headers:") 371 | for key, values := range resp.Header { 372 | for _, value := range values { 373 | color.Yellow("[!] %s: %s", key, value) 374 | } 375 | } 376 | return 377 | } 378 | 379 | var data models.CensysJSON 380 | if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { 381 | color.Yellow("[!] Error decoding Censys response: %v", err) 382 | return 383 | } 384 | 385 | var stats struct { 386 | totalFound int 387 | cloudflareIPs int 388 | nonCloudflareIPs int 389 | } 390 | 391 | if !s.Options.Verbose { 392 | color.Cyan("\n[*] Censys search for %s started.", domain) 393 | } else { 394 | color.Cyan("\n[*] Censys search results for %s:", domain) 395 | } 396 | 397 | for _, cip := range data.Result.Hits { 398 | censysIP := net.ParseIP(cip.IP) 399 | if censysIP.To4() != nil { 400 | stats.totalFound++ 401 | result, _ := dns.IsInCloudflareIPRange(censysIP) 402 | if s.Options.Verbose { 403 | if result { 404 | color.White("[+] IP: %s (Cloudflare)", censysIP) 405 | } else { 406 | color.Yellow("[+] IP: %s", censysIP) 407 | } 408 | } 409 | if result { 410 | stats.cloudflareIPs++ 411 | } else { 412 | stats.nonCloudflareIPs++ 413 | s.compareTitle(url, censysIP, cfIP, "Censys", actualHTMLTitle) 414 | } 415 | } 416 | } 417 | 418 | if !s.Options.Verbose { 419 | color.Cyan("[*] Censys search for %s completed. (Total %d IPs Found, %d IPs don't belong to Cloudflare)", 420 | domain, stats.totalFound, stats.nonCloudflareIPs) 421 | } 422 | } 423 | 424 | func (s *Scanner) securityTrailsSearch(domain, url string, cfIP net.IP, actualHTMLTitle string) { 425 | keys := config.ReadAPIKeys("securitytrails") 426 | if len(keys) == 0 { 427 | color.Yellow("[!] SecurityTrails API key not found in ~/.config/cf-hero.yaml") 428 | return 429 | } 430 | key := keys[0] 431 | if key == "" { 432 | color.Yellow("[!] SecurityTrails API key is empty in ~/.config/cf-hero.yaml") 433 | return 434 | } 435 | 436 | apiURL := fmt.Sprintf("https://api.securitytrails.com/v1/history/%s/dns/a", domain) 437 | client := httpClient.NewHTTPClient(s.Options.Proxy, url) 438 | 439 | req := httpClient.RequestBuilder(apiURL, "", s.Options.HTTPMethod, s.Options.UserAgent) 440 | req.Header.Set("APIKEY", key) 441 | req.Header.Set("Accept", "application/json") 442 | 443 | resp, err := client.Do(req) 444 | if err != nil { 445 | if strings.Contains(err.Error(), "giving up after") { 446 | color.Yellow("[!] SecurityTrails API rate limit exceeded. Please try again later. (%s)", domain) 447 | } else { 448 | color.Yellow("[!] Error making request to SecurityTrails API: %v", err) 449 | } 450 | return 451 | } 452 | defer resp.Body.Close() 453 | 454 | // Check response status 455 | if resp.StatusCode != 200 { 456 | bodyBytes, _ := io.ReadAll(resp.Body) 457 | var errorResponse struct { 458 | Message string `json:"message"` 459 | } 460 | if err := json.Unmarshal(bodyBytes, &errorResponse); err == nil { 461 | if strings.Contains(errorResponse.Message, "exceeded the usage limits") { 462 | color.Yellow("[!] SecurityTrails API rate limit exceeded: %s", errorResponse.Message) 463 | } else { 464 | color.Yellow("[!] SecurityTrails API error: %s", errorResponse.Message) 465 | } 466 | } else { 467 | color.Yellow("[!] SecurityTrails API returned non-200 status code %d: %s", resp.StatusCode, string(bodyBytes)) 468 | } 469 | color.Yellow("[!] HTTP Status: %s", resp.Status) 470 | color.Yellow("[!] HTTP Headers:") 471 | for key, values := range resp.Header { 472 | for _, value := range values { 473 | color.Yellow("[!] %s: %s", key, value) 474 | } 475 | } 476 | return 477 | } 478 | 479 | var data models.SecurityTrailsResponse 480 | if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { 481 | color.Yellow("[!] Error decoding SecurityTrails response: %v", err) 482 | return 483 | } 484 | 485 | var stats struct { 486 | totalFound int 487 | cloudflareIPs int 488 | nonCloudflareIPs int 489 | } 490 | 491 | if !s.Options.Verbose { 492 | color.Cyan("\n[*] SecurityTrails DNS records for %s started.", domain) 493 | } else { 494 | color.Cyan("\n[*] SecurityTrails DNS records for %s:", domain) 495 | } 496 | 497 | for _, record := range data.Records { 498 | org := "Unknown" 499 | if len(record.Organizations) > 0 { 500 | org = strings.Join(record.Organizations, ", ") 501 | } 502 | period := fmt.Sprintf("%s to %s", record.FirstSeen, record.LastSeen) 503 | 504 | for _, value := range record.Values { 505 | ip := net.ParseIP(value.IP) 506 | if ip != nil && ip.To4() != nil { 507 | stats.totalFound++ 508 | result, _ := dns.IsInCloudflareIPRange(ip) 509 | if s.Options.Verbose { 510 | if result { 511 | color.White("[+] [IP: %s - Organization: %s - Period: %s] (Cloudflare)", value.IP, org, period) 512 | } else { 513 | color.Yellow("[+] [IP: %s - Organization: %s - Period: %s]", value.IP, org, period) 514 | } 515 | } 516 | if result { 517 | stats.cloudflareIPs++ 518 | } else { 519 | stats.nonCloudflareIPs++ 520 | s.compareTitle(url, ip, cfIP, "SecurityTrails", actualHTMLTitle) 521 | } 522 | } 523 | } 524 | } 525 | 526 | if !s.Options.Verbose { 527 | color.Cyan("[*] SecurityTrails DNS records for %s completed. (Total %d IPs Found, %d IPs don't belong to Cloudflare)", 528 | domain, stats.totalFound, stats.nonCloudflareIPs) 529 | } 530 | } 531 | 532 | func (s *Scanner) shodanSearch(domain, url string, cfIP net.IP, actualHTMLTitle string) { 533 | keys := config.ReadAPIKeys("shodan") 534 | if len(keys) == 0 { 535 | color.Yellow("[!] Shodan API key not found in ~/.config/cf-hero.yaml") 536 | return 537 | } 538 | key := keys[0] 539 | if key == "" { 540 | color.Yellow("[!] Shodan API key is empty in ~/.config/cf-hero.yaml") 541 | return 542 | } 543 | 544 | maxRetries := 3 545 | retryCount := 0 546 | var resp *http.Response 547 | var err error 548 | 549 | apiURL := fmt.Sprintf("https://api.shodan.io/dns/domain/%s?key=%s&history=true", domain, key) 550 | client := httpClient.NewHTTPClient(s.Options.Proxy, url) 551 | req := httpClient.RequestBuilder(apiURL, "", s.Options.HTTPMethod, s.Options.UserAgent) 552 | req.Header.Set("Accept", "application/json") 553 | 554 | // Retry loop 555 | for retryCount < maxRetries { 556 | resp, err = client.Do(req) 557 | if err == nil && resp.StatusCode == 200 { 558 | break 559 | } 560 | 561 | if resp != nil { 562 | resp.Body.Close() 563 | } 564 | 565 | retryCount++ 566 | if retryCount == maxRetries { 567 | color.Red("[-] Error making request to Shodan API after %d retries: %v\n", maxRetries, err) 568 | return 569 | } 570 | 571 | // Exponential backoff: 1s, 2s, 4s, 8s, 16s 572 | waitTime := time.Duration(1<= totalResults { 761 | break 762 | } 763 | page++ 764 | } 765 | 766 | if !s.Options.Verbose { 767 | color.Cyan("[*] ZoomEye search for %s completed. (Total %d IPs Found, %d IPs don't belong to Cloudflare)", 768 | domain, stats.totalFound, stats.nonCloudflareIPs) 769 | } 770 | } 771 | 772 | func (s *Scanner) compareTitle(url string, ip net.IP, cfIP net.IP, source string, actualHTMLTitle string) { 773 | foundIPTitle, _ := httpClient.GetHTMLTitleWithPortCheck(ip.String(), s.Options.JA3, s.Options.UserAgent, s.Options.Proxy) 774 | 775 | if actualHTMLTitle == foundIPTitle { 776 | s.mu.Lock() 777 | s.Stats.RealIPsFound++ 778 | s.mu.Unlock() 779 | s.printResult(url, cfIP, ip, source, actualHTMLTitle) 780 | } 781 | } 782 | 783 | func (s *Scanner) printResult(url string, cfIP net.IP, realIP interface{}, source, htmlTitle string) { 784 | color.Green("[+] Found real IP of %s : %v (Source: %s) - Title: %s", url, realIP, source, htmlTitle) 785 | } 786 | --------------------------------------------------------------------------------