mirror of
https://github.com/Vyntral/god-eye.git
synced 2026-08-17 23:27:18 +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:
@@ -0,0 +1,27 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SharedTransport is a global shared HTTP transport for connection pooling
|
||||
var SharedTransport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
MaxIdleConns: 100,
|
||||
MaxIdleConnsPerHost: 10,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
DisableCompression: true, // Keep Content-Length header for SPA detection
|
||||
}
|
||||
|
||||
// GetSharedClient returns an HTTP client with connection pooling
|
||||
func GetSharedClient(timeout int) *http.Client {
|
||||
return &http.Client{
|
||||
Timeout: time.Duration(timeout) * time.Second,
|
||||
Transport: SharedTransport,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"god-eye/internal/config"
|
||||
)
|
||||
|
||||
func ProbeHTTP(subdomain string, timeout int) *config.SubdomainResult {
|
||||
result := &config.SubdomainResult{}
|
||||
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: time.Duration(timeout) * time.Second,
|
||||
Transport: transport,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
|
||||
urls := []string{
|
||||
fmt.Sprintf("https://%s", subdomain),
|
||||
fmt.Sprintf("http://%s", subdomain),
|
||||
}
|
||||
|
||||
for _, url := range urls {
|
||||
start := time.Now()
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
result.StatusCode = resp.StatusCode
|
||||
result.ResponseMs = time.Since(start).Milliseconds()
|
||||
|
||||
// Content-Length
|
||||
if cl := resp.ContentLength; cl > 0 {
|
||||
result.ContentLength = cl
|
||||
}
|
||||
|
||||
// Redirect location
|
||||
if resp.StatusCode >= 300 && resp.StatusCode < 400 {
|
||||
if loc := resp.Header.Get("Location"); loc != "" {
|
||||
result.RedirectURL = loc
|
||||
}
|
||||
}
|
||||
|
||||
// Server header
|
||||
if server := resp.Header.Get("Server"); server != "" {
|
||||
result.Server = server
|
||||
result.Tech = append(result.Tech, server)
|
||||
}
|
||||
|
||||
// TLS/SSL info
|
||||
if resp.TLS != nil && len(resp.TLS.PeerCertificates) > 0 {
|
||||
cert := resp.TLS.PeerCertificates[0]
|
||||
result.TLSIssuer = cert.Issuer.CommonName
|
||||
result.TLSExpiry = cert.NotAfter.Format("2006-01-02")
|
||||
|
||||
// TLS version
|
||||
switch resp.TLS.Version {
|
||||
case tls.VersionTLS13:
|
||||
result.TLSVersion = "TLS 1.3"
|
||||
case tls.VersionTLS12:
|
||||
result.TLSVersion = "TLS 1.2"
|
||||
case tls.VersionTLS11:
|
||||
result.TLSVersion = "TLS 1.1"
|
||||
case tls.VersionTLS10:
|
||||
result.TLSVersion = "TLS 1.0"
|
||||
}
|
||||
}
|
||||
|
||||
// Interesting headers
|
||||
interestingHeaders := []string{
|
||||
"X-Powered-By", "X-AspNet-Version", "X-AspNetMvc-Version",
|
||||
"X-Generator", "X-Drupal-Cache", "X-Varnish",
|
||||
"X-Cache", "X-Backend-Server", "X-Server",
|
||||
}
|
||||
for _, h := range interestingHeaders {
|
||||
if val := resp.Header.Get(h); val != "" {
|
||||
result.Headers = append(result.Headers, fmt.Sprintf("%s: %s", h, val))
|
||||
if h == "X-Powered-By" {
|
||||
result.Tech = append(result.Tech, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WAF detection
|
||||
result.WAF = DetectWAF(resp)
|
||||
|
||||
// Security headers check
|
||||
result.SecurityHeaders, result.MissingHeaders = CheckSecurityHeaders(resp)
|
||||
|
||||
// Read body for title and tech detection
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 100000))
|
||||
if err == nil {
|
||||
// Content-Length from body if not set
|
||||
if result.ContentLength == 0 {
|
||||
result.ContentLength = int64(len(body))
|
||||
}
|
||||
|
||||
// Extract title
|
||||
titleRe := regexp.MustCompile(`(?i)<title[^>]*>([^<]+)</title>`)
|
||||
if matches := titleRe.FindSubmatch(body); len(matches) > 1 {
|
||||
result.Title = strings.TrimSpace(string(matches[1]))
|
||||
}
|
||||
|
||||
// Detect technologies
|
||||
bodyStr := string(body)
|
||||
if strings.Contains(bodyStr, "wp-content") || strings.Contains(bodyStr, "wordpress") {
|
||||
result.Tech = append(result.Tech, "WordPress")
|
||||
}
|
||||
if strings.Contains(bodyStr, "_next") || strings.Contains(bodyStr, "Next.js") {
|
||||
result.Tech = append(result.Tech, "Next.js")
|
||||
}
|
||||
if strings.Contains(bodyStr, "react") || strings.Contains(bodyStr, "React") {
|
||||
result.Tech = append(result.Tech, "React")
|
||||
}
|
||||
if strings.Contains(bodyStr, "laravel") || strings.Contains(bodyStr, "Laravel") {
|
||||
result.Tech = append(result.Tech, "Laravel")
|
||||
}
|
||||
if strings.Contains(bodyStr, "django") || strings.Contains(bodyStr, "Django") {
|
||||
result.Tech = append(result.Tech, "Django")
|
||||
}
|
||||
if strings.Contains(bodyStr, "angular") || strings.Contains(bodyStr, "ng-") {
|
||||
result.Tech = append(result.Tech, "Angular")
|
||||
}
|
||||
if strings.Contains(bodyStr, "vue") || strings.Contains(bodyStr, "Vue.js") {
|
||||
result.Tech = append(result.Tech, "Vue.js")
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func DetectWAF(resp *http.Response) string {
|
||||
// Check headers for WAF signatures
|
||||
serverHeader := strings.ToLower(resp.Header.Get("Server"))
|
||||
|
||||
// Cloudflare
|
||||
if resp.Header.Get("CF-RAY") != "" || strings.Contains(serverHeader, "cloudflare") {
|
||||
return "Cloudflare"
|
||||
}
|
||||
|
||||
// AWS WAF/CloudFront
|
||||
if resp.Header.Get("X-Amz-Cf-Id") != "" || resp.Header.Get("X-Amz-Cf-Pop") != "" {
|
||||
return "AWS CloudFront"
|
||||
}
|
||||
|
||||
// Akamai
|
||||
if resp.Header.Get("X-Akamai-Transformed") != "" || strings.Contains(serverHeader, "akamai") {
|
||||
return "Akamai"
|
||||
}
|
||||
|
||||
// Sucuri
|
||||
if resp.Header.Get("X-Sucuri-ID") != "" || strings.Contains(serverHeader, "sucuri") {
|
||||
return "Sucuri"
|
||||
}
|
||||
|
||||
// Imperva/Incapsula
|
||||
if resp.Header.Get("X-Iinfo") != "" || resp.Header.Get("X-CDN") == "Incapsula" {
|
||||
return "Imperva"
|
||||
}
|
||||
|
||||
// F5 BIG-IP
|
||||
if strings.Contains(serverHeader, "big-ip") || resp.Header.Get("X-WA-Info") != "" {
|
||||
return "F5 BIG-IP"
|
||||
}
|
||||
|
||||
// Barracuda
|
||||
if strings.Contains(serverHeader, "barracuda") {
|
||||
return "Barracuda"
|
||||
}
|
||||
|
||||
// Fastly
|
||||
if resp.Header.Get("X-Fastly-Request-ID") != "" || resp.Header.Get("Fastly-Debug-Digest") != "" {
|
||||
return "Fastly"
|
||||
}
|
||||
|
||||
// Varnish
|
||||
if resp.Header.Get("X-Varnish") != "" {
|
||||
return "Varnish"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func CheckSecurityHeaders(resp *http.Response) (present []string, missing []string) {
|
||||
securityHeaders := map[string]string{
|
||||
"Content-Security-Policy": "CSP",
|
||||
"X-Frame-Options": "X-Frame",
|
||||
"X-Content-Type-Options": "X-Content-Type",
|
||||
"Strict-Transport-Security": "HSTS",
|
||||
"X-XSS-Protection": "X-XSS",
|
||||
"Referrer-Policy": "Referrer",
|
||||
"Permissions-Policy": "Permissions",
|
||||
}
|
||||
|
||||
for header, shortName := range securityHeaders {
|
||||
if val := resp.Header.Get(header); val != "" {
|
||||
present = append(present, shortName)
|
||||
} else {
|
||||
missing = append(missing, shortName)
|
||||
}
|
||||
}
|
||||
|
||||
return present, missing
|
||||
}
|
||||
Reference in New Issue
Block a user