mirror of
https://github.com/Vyntral/god-eye.git
synced 2026-07-04 11:17:51 +02:00
6f3bc2f952
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>
233 lines
4.6 KiB
Go
233 lines
4.6 KiB
Go
package dns
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
|
|
"god-eye/internal/config"
|
|
)
|
|
|
|
func ResolveSubdomain(subdomain string, resolvers []string, timeout int) []string {
|
|
c := dns.Client{
|
|
Timeout: time.Duration(timeout) * time.Second,
|
|
}
|
|
|
|
m := dns.Msg{}
|
|
m.SetQuestion(dns.Fqdn(subdomain), dns.TypeA)
|
|
|
|
for _, resolver := range resolvers {
|
|
r, _, err := c.Exchange(&m, resolver)
|
|
if err != nil || r == nil {
|
|
continue
|
|
}
|
|
|
|
var ips []string
|
|
for _, ans := range r.Answer {
|
|
if a, ok := ans.(*dns.A); ok {
|
|
ips = append(ips, a.A.String())
|
|
}
|
|
}
|
|
|
|
if len(ips) > 0 {
|
|
return ips
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func CheckWildcard(domain string, resolvers []string) []string {
|
|
// Test multiple random patterns for better wildcard detection
|
|
patterns := []string{
|
|
fmt.Sprintf("random%d.%s", time.Now().UnixNano(), domain),
|
|
fmt.Sprintf("xyz%d.%s", time.Now().UnixNano()%1000000, domain),
|
|
fmt.Sprintf("nonexistent-%s.%s", "abc123xyz", domain),
|
|
}
|
|
|
|
allIPs := make(map[string]int)
|
|
for _, pattern := range patterns {
|
|
ips := ResolveSubdomain(pattern, resolvers, 3)
|
|
for _, ip := range ips {
|
|
allIPs[ip]++
|
|
}
|
|
}
|
|
|
|
// If same IP(s) appear in multiple patterns, it's a wildcard
|
|
var wildcardIPs []string
|
|
for ip, count := range allIPs {
|
|
if count >= 2 {
|
|
wildcardIPs = append(wildcardIPs, ip)
|
|
}
|
|
}
|
|
|
|
return wildcardIPs
|
|
}
|
|
|
|
func ResolveCNAME(subdomain string, resolvers []string, timeout int) string {
|
|
c := dns.Client{
|
|
Timeout: time.Duration(timeout) * time.Second,
|
|
}
|
|
|
|
m := dns.Msg{}
|
|
m.SetQuestion(dns.Fqdn(subdomain), dns.TypeCNAME)
|
|
|
|
for _, resolver := range resolvers {
|
|
r, _, err := c.Exchange(&m, resolver)
|
|
if err != nil || r == nil {
|
|
continue
|
|
}
|
|
|
|
for _, ans := range r.Answer {
|
|
if cname, ok := ans.(*dns.CNAME); ok {
|
|
return strings.TrimSuffix(cname.Target, ".")
|
|
}
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func ResolvePTR(ip string, resolvers []string, timeout int) string {
|
|
c := dns.Client{
|
|
Timeout: time.Duration(timeout) * time.Second,
|
|
}
|
|
|
|
// Convert IP to reverse DNS format
|
|
parts := strings.Split(ip, ".")
|
|
if len(parts) != 4 {
|
|
return ""
|
|
}
|
|
reverseIP := fmt.Sprintf("%s.%s.%s.%s.in-addr.arpa.", parts[3], parts[2], parts[1], parts[0])
|
|
|
|
m := dns.Msg{}
|
|
m.SetQuestion(reverseIP, dns.TypePTR)
|
|
|
|
for _, resolver := range resolvers {
|
|
r, _, err := c.Exchange(&m, resolver)
|
|
if err != nil || r == nil {
|
|
continue
|
|
}
|
|
|
|
for _, ans := range r.Answer {
|
|
if ptr, ok := ans.(*dns.PTR); ok {
|
|
return strings.TrimSuffix(ptr.Ptr, ".")
|
|
}
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func ResolveMX(domain string, resolvers []string, timeout int) []string {
|
|
c := dns.Client{
|
|
Timeout: time.Duration(timeout) * time.Second,
|
|
}
|
|
|
|
m := dns.Msg{}
|
|
m.SetQuestion(dns.Fqdn(domain), dns.TypeMX)
|
|
|
|
for _, resolver := range resolvers {
|
|
r, _, err := c.Exchange(&m, resolver)
|
|
if err != nil || r == nil {
|
|
continue
|
|
}
|
|
|
|
var records []string
|
|
for _, ans := range r.Answer {
|
|
if mx, ok := ans.(*dns.MX); ok {
|
|
records = append(records, strings.TrimSuffix(mx.Mx, "."))
|
|
}
|
|
}
|
|
if len(records) > 0 {
|
|
return records
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ResolveTXT(domain string, resolvers []string, timeout int) []string {
|
|
c := dns.Client{
|
|
Timeout: time.Duration(timeout) * time.Second,
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
var records []string
|
|
for _, ans := range r.Answer {
|
|
if txt, ok := ans.(*dns.TXT); ok {
|
|
for _, t := range txt.Txt {
|
|
// Limit length for display
|
|
if len(t) > 100 {
|
|
t = t[:97] + "..."
|
|
}
|
|
records = append(records, t)
|
|
}
|
|
}
|
|
}
|
|
if len(records) > 0 {
|
|
return records
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ResolveNS(domain string, resolvers []string, timeout int) []string {
|
|
c := dns.Client{
|
|
Timeout: time.Duration(timeout) * time.Second,
|
|
}
|
|
|
|
m := dns.Msg{}
|
|
m.SetQuestion(dns.Fqdn(domain), dns.TypeNS)
|
|
|
|
for _, resolver := range resolvers {
|
|
r, _, err := c.Exchange(&m, resolver)
|
|
if err != nil || r == nil {
|
|
continue
|
|
}
|
|
|
|
var records []string
|
|
for _, ans := range r.Answer {
|
|
if ns, ok := ans.(*dns.NS); ok {
|
|
records = append(records, strings.TrimSuffix(ns.Ns, "."))
|
|
}
|
|
}
|
|
if len(records) > 0 {
|
|
return records
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func GetIPInfo(ip string) (*config.IPInfo, error) {
|
|
client := &http.Client{Timeout: 5 * time.Second}
|
|
url := fmt.Sprintf("http://ip-api.com/json/%s?fields=as,org,country,city", ip)
|
|
|
|
resp, err := client.Get(url)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var info config.IPInfo
|
|
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &info, nil
|
|
}
|