mirror of
https://github.com/Vyntral/god-eye.git
synced 2026-02-12 16:52:45 +00:00
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>
165 lines
4.5 KiB
Go
165 lines
4.5 KiB
Go
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
|
|
}
|