mirror of
https://github.com/Vyntral/god-eye.git
synced 2026-10-10 22:38:49 +02:00
🚀 God's Eye v0.1 - Initial Release
God's Eye is an ultra-fast subdomain enumeration and reconnaissance tool with AI-powered security analysis. ## ✨ Key Features ### 🔍 Comprehensive Enumeration - 20+ passive sources (crt.sh, Censys, URLScan, etc.) - DNS brute-force with smart wordlists - Wildcard detection and filtering - 1000 concurrent workers for maximum speed ### 🌐 Deep Reconnaissance - HTTP probing with 13+ security checks - Port scanning (configurable) - TLS/SSL fingerprinting - Technology detection (Wappalyzer-style) - WAF detection (Cloudflare, Akamai, etc.) - Security header analysis - JavaScript secrets extraction - Admin panel & API discovery - Backup file detection - robots.txt & sitemap.xml checks ### 🎯 Subdomain Takeover Detection - 110+ fingerprints (AWS, Azure, GitHub Pages, Heroku, etc.) - CNAME validation - Dead DNS detection ### 🤖 AI-Powered Analysis (NEW!) - Local AI using Ollama - No API costs, complete privacy - Real-time CVE detection via function calling (queries NVD database) - Cascade architecture: phi3.5 (fast triage) + qwen2.5-coder (deep analysis) - JavaScript security analysis - HTTP response anomaly detection - Executive summary reports ### 📊 Output Formats - Pretty terminal output with colors - JSON export - CSV export - TXT (simple subdomain list) - Silent mode for piping ## 🚀 Installation bash go install github.com/Vyntral/god-eye@latest ## 📖 Quick Start bash # Basic scan god-eye -d example.com # With AI analysis god-eye -d example.com --enable-ai # Only active hosts god-eye -d example.com --active # Export to JSON god-eye -d example.com -o results.json -f json ## 🎯 Use Cases - Bug bounty reconnaissance - Penetration testing - Security audits - Attack surface mapping - Red team operations ## ⚠️ Legal Notice This tool is for authorized security testing only. Users must obtain explicit permission before scanning any targets. Unauthorized access is illegal. ## 📄 License MIT License with additional security tool terms - see LICENSE file ## 🙏 Credits Built with ❤️ by Vyntral for Orizon Powered by Go, Ollama, and the security community --- 🤖 Generated with Claude Code https://claude.com/claude-code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
6f3bc2f952
26 files changed
+8052
No files matched your search
@@ -0,0 +1,276 @@
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// DetectCloudProvider detects cloud provider based on IP/CNAME
|
||||
func DetectCloudProvider(ips []string, cname string, asn string) string {
|
||||
// Check CNAME patterns
|
||||
cnamePatterns := map[string]string{
|
||||
"amazonaws.com": "AWS",
|
||||
"aws.com": "AWS",
|
||||
"cloudfront.net": "AWS CloudFront",
|
||||
"elasticbeanstalk.com": "AWS Elastic Beanstalk",
|
||||
"elb.amazonaws.com": "AWS ELB",
|
||||
"s3.amazonaws.com": "AWS S3",
|
||||
"azure.com": "Azure",
|
||||
"azurewebsites.net": "Azure App Service",
|
||||
"cloudapp.net": "Azure",
|
||||
"azurefd.net": "Azure Front Door",
|
||||
"blob.core.windows.net": "Azure Blob",
|
||||
"googleapis.com": "Google Cloud",
|
||||
"appspot.com": "Google App Engine",
|
||||
"storage.googleapis.com": "Google Cloud Storage",
|
||||
"digitaloceanspaces.com": "DigitalOcean Spaces",
|
||||
"ondigitalocean.app": "DigitalOcean App Platform",
|
||||
"cloudflare.com": "Cloudflare",
|
||||
"fastly.net": "Fastly",
|
||||
"akamai.net": "Akamai",
|
||||
"netlify.app": "Netlify",
|
||||
"vercel.app": "Vercel",
|
||||
"herokuapp.com": "Heroku",
|
||||
}
|
||||
|
||||
for pattern, provider := range cnamePatterns {
|
||||
if strings.Contains(cname, pattern) {
|
||||
return provider
|
||||
}
|
||||
}
|
||||
|
||||
// Check ASN patterns
|
||||
asnPatterns := map[string]string{
|
||||
"AS14618": "AWS",
|
||||
"AS16509": "AWS",
|
||||
"AS8075": "Azure",
|
||||
"AS15169": "Google Cloud",
|
||||
"AS14061": "DigitalOcean",
|
||||
"AS13335": "Cloudflare",
|
||||
"AS54113": "Fastly",
|
||||
"AS20940": "Akamai",
|
||||
}
|
||||
|
||||
for pattern, provider := range asnPatterns {
|
||||
if strings.Contains(asn, pattern) {
|
||||
return provider
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// CheckS3Buckets checks for exposed S3 buckets
|
||||
func CheckS3Buckets(subdomain string, timeout int) []string {
|
||||
client := &http.Client{
|
||||
Timeout: time.Duration(timeout) * time.Second,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
// Common S3 bucket URL patterns
|
||||
parts := strings.Split(subdomain, ".")
|
||||
bucketName := parts[0]
|
||||
|
||||
patterns := []string{
|
||||
fmt.Sprintf("https://%s.s3.amazonaws.com", bucketName),
|
||||
fmt.Sprintf("https://s3.amazonaws.com/%s", bucketName),
|
||||
fmt.Sprintf("https://%s.s3.us-east-1.amazonaws.com", bucketName),
|
||||
fmt.Sprintf("https://%s.s3.us-west-2.amazonaws.com", bucketName),
|
||||
fmt.Sprintf("https://%s.s3.eu-west-1.amazonaws.com", bucketName),
|
||||
}
|
||||
|
||||
var found []string
|
||||
for _, url := range patterns {
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
// Public bucket if 200 or 403 (exists but forbidden)
|
||||
if resp.StatusCode == 200 {
|
||||
found = append(found, url+" (PUBLIC)")
|
||||
} else if resp.StatusCode == 403 {
|
||||
found = append(found, url+" (exists)")
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
// CheckEmailSecurity checks SPF/DKIM/DMARC records
|
||||
func CheckEmailSecurity(domain string, resolvers []string, timeout int) (spf string, dmarc string, security string) {
|
||||
c := dns.Client{
|
||||
Timeout: time.Duration(timeout) * time.Second,
|
||||
}
|
||||
|
||||
// Check SPF record
|
||||
m := dns.Msg{}
|
||||
m.SetQuestion(dns.Fqdn(domain), dns.TypeTXT)
|
||||
|
||||
for _, resolver := range resolvers {
|
||||
r, _, err := c.Exchange(&m, resolver)
|
||||
if err != nil || r == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ans := range r.Answer {
|
||||
if txt, ok := ans.(*dns.TXT); ok {
|
||||
for _, t := range txt.Txt {
|
||||
if strings.HasPrefix(t, "v=spf1") {
|
||||
spf = t
|
||||
if len(spf) > 80 {
|
||||
spf = spf[:77] + "..."
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if spf != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Check DMARC record
|
||||
m2 := dns.Msg{}
|
||||
m2.SetQuestion(dns.Fqdn("_dmarc."+domain), dns.TypeTXT)
|
||||
|
||||
for _, resolver := range resolvers {
|
||||
r, _, err := c.Exchange(&m2, resolver)
|
||||
if err != nil || r == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ans := range r.Answer {
|
||||
if txt, ok := ans.(*dns.TXT); ok {
|
||||
for _, t := range txt.Txt {
|
||||
if strings.HasPrefix(t, "v=DMARC1") {
|
||||
dmarc = t
|
||||
if len(dmarc) > 80 {
|
||||
dmarc = dmarc[:77] + "..."
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if dmarc != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Determine email security level
|
||||
if spf != "" && dmarc != "" {
|
||||
if strings.Contains(dmarc, "p=reject") || strings.Contains(dmarc, "p=quarantine") {
|
||||
security = "Strong"
|
||||
} else {
|
||||
security = "Moderate"
|
||||
}
|
||||
} else if spf != "" || dmarc != "" {
|
||||
security = "Weak"
|
||||
} else {
|
||||
security = "None"
|
||||
}
|
||||
|
||||
return spf, dmarc, security
|
||||
}
|
||||
|
||||
// GetTLSAltNames extracts Subject Alternative Names from TLS certificate
|
||||
func GetTLSAltNames(subdomain string, timeout int) []string {
|
||||
conn, err := tls.DialWithDialer(
|
||||
&net.Dialer{Timeout: time.Duration(timeout) * time.Second},
|
||||
"tcp",
|
||||
subdomain+":443",
|
||||
&tls.Config{InsecureSkipVerify: true},
|
||||
)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
certs := conn.ConnectionState().PeerCertificates
|
||||
if len(certs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var altNames []string
|
||||
seen := make(map[string]bool)
|
||||
|
||||
for _, cert := range certs {
|
||||
for _, name := range cert.DNSNames {
|
||||
if !seen[name] && name != subdomain {
|
||||
seen[name] = true
|
||||
altNames = append(altNames, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Limit to first 10
|
||||
if len(altNames) > 10 {
|
||||
altNames = altNames[:10]
|
||||
}
|
||||
|
||||
return altNames
|
||||
}
|
||||
|
||||
// CheckS3BucketsWithClient checks for exposed S3 buckets with shared client
|
||||
func CheckS3BucketsWithClient(subdomain string, client *http.Client) []string {
|
||||
parts := strings.Split(subdomain, ".")
|
||||
if len(parts) < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
subPrefix := parts[0]
|
||||
// Get domain name (e.g., "finnat" from "ftp.finnat.it")
|
||||
var domainName string
|
||||
if len(parts) >= 2 {
|
||||
domainName = parts[len(parts)-2]
|
||||
}
|
||||
|
||||
// Skip generic subdomain names that cause false positives
|
||||
genericNames := map[string]bool{
|
||||
"www": true, "ftp": true, "mail": true, "smtp": true, "imap": true,
|
||||
"pop": true, "webmail": true, "autodiscover": true, "test": true,
|
||||
"dev": true, "staging": true, "api": true, "admin": true, "pop3": true,
|
||||
}
|
||||
|
||||
var patterns []string
|
||||
if genericNames[subPrefix] {
|
||||
// For generic subdomains, use domain-specific bucket names
|
||||
patterns = []string{
|
||||
fmt.Sprintf("https://%s-%s.s3.amazonaws.com", domainName, subPrefix),
|
||||
fmt.Sprintf("https://%s.s3.amazonaws.com", domainName),
|
||||
}
|
||||
} else {
|
||||
// For specific subdomains, use combination
|
||||
patterns = []string{
|
||||
fmt.Sprintf("https://%s-%s.s3.amazonaws.com", domainName, subPrefix),
|
||||
fmt.Sprintf("https://%s.s3.amazonaws.com", domainName),
|
||||
}
|
||||
}
|
||||
|
||||
var found []string
|
||||
for _, url := range patterns {
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
// Only report PUBLIC buckets (200), not just existing (403)
|
||||
if resp.StatusCode == 200 {
|
||||
found = append(found, url+" (PUBLIC)")
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AnalyzeJSFiles finds JavaScript files and extracts potential secrets
|
||||
func AnalyzeJSFiles(subdomain string, client *http.Client) ([]string, []string) {
|
||||
var jsFiles []string
|
||||
var secrets []string
|
||||
|
||||
urls := []string{
|
||||
fmt.Sprintf("https://%s", subdomain),
|
||||
fmt.Sprintf("http://%s", subdomain),
|
||||
}
|
||||
|
||||
// Common JS file paths
|
||||
jsPaths := []string{
|
||||
"/main.js", "/app.js", "/bundle.js", "/vendor.js",
|
||||
"/static/js/main.js", "/static/js/app.js",
|
||||
"/assets/js/app.js", "/js/main.js", "/js/app.js",
|
||||
"/dist/main.js", "/dist/bundle.js",
|
||||
"/_next/static/chunks/main.js",
|
||||
"/build/static/js/main.js",
|
||||
}
|
||||
|
||||
// Secret patterns to search for
|
||||
secretPatterns := []*regexp.Regexp{
|
||||
regexp.MustCompile(`(?i)['"]?api[_-]?key['"]?\s*[:=]\s*['"]([a-zA-Z0-9_\-]{20,})['"]`),
|
||||
regexp.MustCompile(`(?i)['"]?aws[_-]?access[_-]?key[_-]?id['"]?\s*[:=]\s*['"]([A-Z0-9]{20})['"]`),
|
||||
regexp.MustCompile(`(?i)['"]?aws[_-]?secret[_-]?access[_-]?key['"]?\s*[:=]\s*['"]([a-zA-Z0-9/+=]{40})['"]`),
|
||||
regexp.MustCompile(`(?i)['"]?google[_-]?api[_-]?key['"]?\s*[:=]\s*['"]([a-zA-Z0-9_\-]{39})['"]`),
|
||||
regexp.MustCompile(`(?i)['"]?firebase[_-]?api[_-]?key['"]?\s*[:=]\s*['"]([a-zA-Z0-9_\-]{39})['"]`),
|
||||
regexp.MustCompile(`(?i)['"]?stripe[_-]?(publishable|secret)[_-]?key['"]?\s*[:=]\s*['"]([a-zA-Z0-9_\-]{20,})['"]`),
|
||||
regexp.MustCompile(`(?i)['"]?github[_-]?token['"]?\s*[:=]\s*['"]([a-zA-Z0-9_]{36,})['"]`),
|
||||
regexp.MustCompile(`(?i)['"]?slack[_-]?token['"]?\s*[:=]\s*['"]([a-zA-Z0-9\-]{30,})['"]`),
|
||||
regexp.MustCompile(`(?i)['"]?private[_-]?key['"]?\s*[:=]\s*['"]([a-zA-Z0-9/+=]{50,})['"]`),
|
||||
regexp.MustCompile(`(?i)['"]?secret['"]?\s*[:=]\s*['"]([a-zA-Z0-9_\-]{20,})['"]`),
|
||||
regexp.MustCompile(`(?i)['"]?password['"]?\s*[:=]\s*['"]([^'"]{8,})['"]`),
|
||||
regexp.MustCompile(`(?i)['"]?authorization['"]?\s*[:=]\s*['"]Bearer\s+([a-zA-Z0-9_\-\.]+)['"]`),
|
||||
}
|
||||
|
||||
// Also search for API endpoints in JS
|
||||
endpointPatterns := []*regexp.Regexp{
|
||||
regexp.MustCompile(`(?i)['"]https?://[a-zA-Z0-9\-\.]+/api/[a-zA-Z0-9/\-_]+['"]`),
|
||||
regexp.MustCompile(`(?i)['"]https?://api\.[a-zA-Z0-9\-\.]+[a-zA-Z0-9/\-_]*['"]`),
|
||||
}
|
||||
|
||||
for _, baseURL := range urls {
|
||||
// First, get the main page and extract JS file references
|
||||
resp, err := client.Get(baseURL)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 500000))
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find JS files referenced in HTML
|
||||
jsRe := regexp.MustCompile(`src=["']([^"']*\.js[^"']*)["']`)
|
||||
matches := jsRe.FindAllStringSubmatch(string(body), -1)
|
||||
for _, match := range matches {
|
||||
if len(match) > 1 {
|
||||
jsURL := match[1]
|
||||
if !strings.HasPrefix(jsURL, "http") {
|
||||
if strings.HasPrefix(jsURL, "/") {
|
||||
jsURL = baseURL + jsURL
|
||||
} else {
|
||||
jsURL = baseURL + "/" + jsURL
|
||||
}
|
||||
}
|
||||
jsFiles = append(jsFiles, jsURL)
|
||||
}
|
||||
}
|
||||
|
||||
// Also check common JS paths
|
||||
for _, path := range jsPaths {
|
||||
testURL := baseURL + path
|
||||
resp, err := client.Get(testURL)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
jsFiles = append(jsFiles, path)
|
||||
|
||||
// Read JS content and search for secrets
|
||||
jsBody, err := io.ReadAll(io.LimitReader(resp.Body, 500000))
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
jsContent := string(jsBody)
|
||||
|
||||
// Search for secrets
|
||||
for _, pattern := range secretPatterns {
|
||||
if matches := pattern.FindAllStringSubmatch(jsContent, 3); len(matches) > 0 {
|
||||
for _, m := range matches {
|
||||
if len(m) > 1 {
|
||||
secret := m[0]
|
||||
if len(secret) > 60 {
|
||||
secret = secret[:57] + "..."
|
||||
}
|
||||
secrets = append(secrets, secret)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search for API endpoints
|
||||
for _, pattern := range endpointPatterns {
|
||||
if matches := pattern.FindAllString(jsContent, 5); len(matches) > 0 {
|
||||
for _, m := range matches {
|
||||
if len(m) > 60 {
|
||||
m = m[:57] + "..."
|
||||
}
|
||||
secrets = append(secrets, "endpoint: "+m)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
if len(jsFiles) > 0 || len(secrets) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Deduplicate and limit
|
||||
jsFiles = UniqueStrings(jsFiles)
|
||||
secrets = UniqueStrings(secrets)
|
||||
|
||||
if len(jsFiles) > 10 {
|
||||
jsFiles = jsFiles[:10]
|
||||
}
|
||||
if len(secrets) > 10 {
|
||||
secrets = secrets[:10]
|
||||
}
|
||||
|
||||
return jsFiles, secrets
|
||||
}
|
||||
|
||||
// UniqueStrings returns unique strings from a slice
|
||||
func UniqueStrings(input []string) []string {
|
||||
seen := make(map[string]bool)
|
||||
var result []string
|
||||
for _, s := range input {
|
||||
if !seen[s] {
|
||||
seen[s] = true
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,254 @@
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
var TakeoverFingerprints = map[string]string{
|
||||
// GitHub
|
||||
"github.io": "There isn't a GitHub Pages site here",
|
||||
"githubusercontent.com": "There isn't a GitHub Pages site here",
|
||||
// Heroku
|
||||
"herokuapp.com": "no-such-app.herokuapp.com",
|
||||
"herokussl.com": "no-such-app.herokuapp.com",
|
||||
// AWS
|
||||
"s3.amazonaws.com": "NoSuchBucket",
|
||||
"s3-website": "NoSuchBucket",
|
||||
"elasticbeanstalk.com": "NoSuchBucket",
|
||||
"cloudfront.net": "Bad Request",
|
||||
"elb.amazonaws.com": "NXDOMAIN",
|
||||
// Azure
|
||||
"azurewebsites.net": "404 Web Site not found",
|
||||
"cloudapp.net": "404 Web Site not found",
|
||||
"cloudapp.azure.com": "404 Web Site not found",
|
||||
"azurefd.net": "404 Web Site not found",
|
||||
"blob.core.windows.net": "BlobNotFound",
|
||||
"azure-api.net": "404 Resource not found",
|
||||
"azurehdinsight.net": "404",
|
||||
"azureedge.net": "404 Web Site not found",
|
||||
"trafficmanager.net": "404 Web Site not found",
|
||||
// Google Cloud
|
||||
"appspot.com": "Error: Not Found",
|
||||
"storage.googleapis.com": "NoSuchBucket",
|
||||
"googleplex.com": "404. That's an error",
|
||||
// Shopify
|
||||
"myshopify.com": "Sorry, this shop is currently unavailable",
|
||||
// Pantheon
|
||||
"pantheonsite.io": "404 error unknown site",
|
||||
// Zendesk
|
||||
"zendesk.com": "Help Center Closed",
|
||||
// Various services
|
||||
"teamwork.com": "Oops - We didn't find your site",
|
||||
"helpjuice.com": "We could not find what you're looking for",
|
||||
"helpscoutdocs.com": "No settings were found for this company",
|
||||
"ghost.io": "The thing you were looking for is no longer here",
|
||||
"surge.sh": "project not found",
|
||||
"bitbucket.io": "Repository not found",
|
||||
"wordpress.com": "Do you want to register",
|
||||
"smartling.com": "Domain is not configured",
|
||||
"acquia.com": "Web Site Not Found",
|
||||
"fastly.net": "Fastly error: unknown domain",
|
||||
"uservoice.com": "This UserVoice subdomain is currently available",
|
||||
"unbounce.com": "The requested URL was not found on this server",
|
||||
"thinkific.com": "You may have mistyped the address",
|
||||
"tilda.cc": "Please renew your subscription",
|
||||
"mashery.com": "Unrecognized domain",
|
||||
"intercom.help": "This page is reserved for",
|
||||
"webflow.io": "The page you are looking for doesn't exist",
|
||||
"wishpond.com": "https://www.wishpond.com/404",
|
||||
"aftership.com": "Oops.</h2><p>The page you're looking for doesn't exist",
|
||||
"aha.io": "There is no portal here",
|
||||
"tictail.com": "to target URL: <a href=\"https://tictail.com",
|
||||
"campaignmonitor.com": "Trying to access your account?",
|
||||
"cargocollective.com": "404 Not Found",
|
||||
"statuspage.io": "You are being <a href=\"https://www.statuspage.io\">",
|
||||
"tumblr.com": "There's nothing here.",
|
||||
"worksites.net": "Hello! Sorry, but the website you’re looking for doesn’t exist.",
|
||||
"smugmug.com": "class=\"message-text\">Page Not Found<",
|
||||
// Additional services
|
||||
"netlify.app": "Not Found",
|
||||
"netlify.com": "Not Found",
|
||||
"vercel.app": "NOT_FOUND",
|
||||
"now.sh": "NOT_FOUND",
|
||||
"fly.dev": "404 Not Found",
|
||||
"render.com": "NOT_FOUND",
|
||||
"gitbook.io": "Domain not found",
|
||||
"readme.io": "Project doesnt exist",
|
||||
"desk.com": "Sorry, We Couldn't Find That Page",
|
||||
"freshdesk.com": "There is no helpdesk here",
|
||||
"tave.com": "Sorry, this profile doesn't exist",
|
||||
"feedpress.me": "The feed has not been found",
|
||||
"launchrock.com": "It looks like you may have taken a wrong turn",
|
||||
"pingdom.com": "This public status page",
|
||||
"surveygizmo.com": "data-html-name",
|
||||
"tribepad.com": "Sorry, we could not find that page",
|
||||
"uptimerobot.com": "This public status page",
|
||||
"wufoo.com": "Profile not found",
|
||||
"brightcove.com": "Error - Loss of soul",
|
||||
"bigcartel.com": "Oops! We couldn't find that page",
|
||||
"activehosted.com": "alt=\"LIGHTTPD - fly light.\"",
|
||||
"createsend.com": "Double check the URL",
|
||||
"flexbe.com": "Domain doesn't exist",
|
||||
"agilecrm.com": "Sorry, this page is no longer available",
|
||||
"anima.io": "not found",
|
||||
"proposify.com": "If you need immediate assistance",
|
||||
"simplebooklet.com": "We can't find this FlipBook",
|
||||
"getresponse.com": "With GetResponse Landing Pages",
|
||||
"vend.com": "Looks like you've traveled too far",
|
||||
"strikingly.com": "But if you're looking to build your own website",
|
||||
"airee.ru": "Ошибка 402. Сервис",
|
||||
"anweb.ru": "Эта страница не существует",
|
||||
"domain.ru": "К сожалению, не удалось",
|
||||
"instapage.com": "Looks Like You're Lost",
|
||||
"landingi.com": "Nie znaleziono strony",
|
||||
"leadpages.net": "Oops - We Couldn't Find Your Page",
|
||||
"pagewiz.com": "PAGE NOT FOUND",
|
||||
"short.io": "Link does not exist",
|
||||
"smartjobboard.com": "Company Not Found",
|
||||
"uberflip.com": "Non-hub polygon detected",
|
||||
"vingle.net": "해당 페이지가 존재하지 않습니다",
|
||||
"ngrok.io": "Tunnel",
|
||||
"kinsta.cloud": "No Site For Domain",
|
||||
"canny.io": "There is no such company",
|
||||
"hatena.ne.jp": "404 Blog is not found",
|
||||
"medium.com": "This page doesn't exist",
|
||||
"hatenablog.com": "404 Blog is not found",
|
||||
"jetbrains.com": "is not a registered InCloud YouTrack",
|
||||
}
|
||||
|
||||
func CheckTakeover(subdomain string, timeout int) string {
|
||||
client := &http.Client{
|
||||
Timeout: time.Duration(timeout) * time.Second,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
// Check CNAME
|
||||
c := dns.Client{Timeout: 3 * time.Second}
|
||||
m := dns.Msg{}
|
||||
m.SetQuestion(dns.Fqdn(subdomain), dns.TypeCNAME)
|
||||
|
||||
r, _, err := c.Exchange(&m, "8.8.8.8:53")
|
||||
if err != nil || r == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var cname string
|
||||
for _, ans := range r.Answer {
|
||||
if cn, ok := ans.(*dns.CNAME); ok {
|
||||
cname = cn.Target
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if cname == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Check if CNAME matches any vulnerable service
|
||||
for service, fingerprint := range TakeoverFingerprints {
|
||||
if strings.Contains(cname, service) {
|
||||
// Verify by checking response
|
||||
resp, err := client.Get(fmt.Sprintf("http://%s", subdomain))
|
||||
if err != nil {
|
||||
resp, err = client.Get(fmt.Sprintf("https://%s", subdomain))
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 100000))
|
||||
if strings.Contains(string(body), fingerprint) {
|
||||
return service
|
||||
}
|
||||
}
|
||||
|
||||
// If can't reach, might still be vulnerable
|
||||
if err != nil {
|
||||
return service + " (unverified)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Helper functions for connection pooling
|
||||
|
||||
func CheckRobotsTxtWithClient(subdomain string, client *http.Client) bool {
|
||||
urls := []string{
|
||||
fmt.Sprintf("https://%s/robots.txt", subdomain),
|
||||
fmt.Sprintf("http://%s/robots.txt", subdomain),
|
||||
}
|
||||
|
||||
for _, url := range urls {
|
||||
resp, err := client.Head(url)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode == 200 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func CheckSitemapXmlWithClient(subdomain string, client *http.Client) bool {
|
||||
urls := []string{
|
||||
fmt.Sprintf("https://%s/sitemap.xml", subdomain),
|
||||
fmt.Sprintf("http://%s/sitemap.xml", subdomain),
|
||||
}
|
||||
|
||||
for _, url := range urls {
|
||||
resp, err := client.Head(url)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode == 200 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func GetFaviconHashWithClient(subdomain string, client *http.Client) string {
|
||||
urls := []string{
|
||||
fmt.Sprintf("https://%s/favicon.ico", subdomain),
|
||||
fmt.Sprintf("http://%s/favicon.ico", subdomain),
|
||||
}
|
||||
|
||||
for _, url := range urls {
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
continue
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 100000))
|
||||
if err != nil || len(body) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
hash := md5.Sum(body)
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
Reference in new issue
Block a user