mirror of
https://github.com/Vyntral/god-eye.git
synced 2026-03-21 02:23:32 +00:00
## AI & CVE Improvements - Fix AI report to display actual subdomain names instead of generic placeholders - Add 10-year CVE filter to reduce false positives from outdated vulnerabilities - Integrate CISA KEV (Known Exploited Vulnerabilities) database support - Improve AI analysis prompt for more accurate security findings ## New Security Modules - Add wildcard DNS detection with multi-phase validation (DNS + HTTP) - Add TLS certificate analyzer for certificate chain inspection - Add comprehensive rate limiting module for API requests - Add retry mechanism with exponential backoff - Add stealth mode for reduced detection during scans - Add progress tracking module for better UX ## Code Refactoring - Extract scanner output logic to dedicated module - Add base source interface for consistent passive source implementation - Reduce admin panel paths to common generic patterns only - Improve HTTP client with connection pooling - Add JSON output formatter ## Documentation Updates - Correct passive source count to 20 (was incorrectly stated as 34) - Fix AI model names: deepseek-r1:1.5b (fast) + qwen2.5-coder:7b (deep) - Update all markdown files for consistency - Relocate demo GIFs to assets/ directory - Add benchmark disclaimer for test variability ## Files Changed - 4 documentation files updated (README, AI_SETUP, BENCHMARK, EXAMPLES) - 11 new source files added - 12 existing files modified
246 lines
7.0 KiB
Go
246 lines
7.0 KiB
Go
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{}
|
|
|
|
// Use shared transport for connection pooling
|
|
client := GetSharedClient(timeout)
|
|
|
|
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"
|
|
}
|
|
|
|
// Check for self-signed certificate
|
|
result.TLSSelfSigned = IsSelfSigned(cert)
|
|
|
|
// Analyze certificate for appliance fingerprinting
|
|
// This is especially useful for self-signed certs (firewalls, VPNs, etc.)
|
|
if fp := AnalyzeTLSCertificate(cert); fp != nil {
|
|
result.TLSFingerprint = fp
|
|
// Add vendor/product to tech stack if detected
|
|
if fp.Vendor != "" && fp.Product != "" {
|
|
result.Tech = append(result.Tech, fp.Vendor+" "+fp.Product)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
bodyStrLower := strings.ToLower(bodyStr)
|
|
|
|
// WordPress - specific patterns
|
|
if strings.Contains(bodyStr, "wp-content") || strings.Contains(bodyStr, "wp-includes") {
|
|
result.Tech = append(result.Tech, "WordPress")
|
|
}
|
|
// Next.js - specific patterns (check before React since Next uses React)
|
|
if strings.Contains(bodyStr, "/_next/") || strings.Contains(bodyStr, "__NEXT_DATA__") {
|
|
result.Tech = append(result.Tech, "Next.js")
|
|
} else if strings.Contains(bodyStr, "react-root") || strings.Contains(bodyStr, "data-reactroot") ||
|
|
strings.Contains(bodyStr, "__REACT_DEVTOOLS_GLOBAL_HOOK__") {
|
|
// React - only if not Next.js
|
|
result.Tech = append(result.Tech, "React")
|
|
}
|
|
// Laravel - specific patterns
|
|
if strings.Contains(bodyStr, "laravel_session") || strings.Contains(bodyStr, "XSRF-TOKEN") {
|
|
result.Tech = append(result.Tech, "Laravel")
|
|
}
|
|
// Django - specific patterns
|
|
if strings.Contains(bodyStr, "csrfmiddlewaretoken") || strings.Contains(bodyStrLower, "django") {
|
|
result.Tech = append(result.Tech, "Django")
|
|
}
|
|
// Angular - more specific patterns (ng-app, ng-controller are Angular 1.x specific)
|
|
if strings.Contains(bodyStr, "ng-app") || strings.Contains(bodyStr, "ng-controller") ||
|
|
strings.Contains(bodyStr, "ng-version") || strings.Contains(bodyStrLower, "angular.js") ||
|
|
strings.Contains(bodyStrLower, "angular.min.js") || strings.Contains(bodyStr, "@angular/core") {
|
|
result.Tech = append(result.Tech, "Angular")
|
|
}
|
|
// Vue.js - specific patterns
|
|
if strings.Contains(bodyStr, "data-v-") || strings.Contains(bodyStr, "__VUE__") ||
|
|
strings.Contains(bodyStr, "vue.js") || strings.Contains(bodyStr, "vue.min.js") {
|
|
result.Tech = append(result.Tech, "Vue.js")
|
|
}
|
|
// Svelte
|
|
if strings.Contains(bodyStr, "svelte") && strings.Contains(bodyStr, "__svelte") {
|
|
result.Tech = append(result.Tech, "Svelte")
|
|
}
|
|
// Nuxt.js (Vue-based)
|
|
if strings.Contains(bodyStr, "__NUXT__") || strings.Contains(bodyStr, "_nuxt/") {
|
|
result.Tech = append(result.Tech, "Nuxt.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
|
|
}
|