Files
god-eye/internal/output/json.go
Vyntral b1bf119c82 v0.1.1: Major AI improvements, new security modules, and documentation fixes
## 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
2025-11-21 12:00:58 +01:00

339 lines
9.1 KiB
Go

package output
import (
"encoding/json"
"io"
"sort"
"time"
"god-eye/internal/config"
)
// ScanReport represents the complete JSON output structure
type ScanReport struct {
// Metadata
Meta ScanMeta `json:"meta"`
// Statistics
Stats ScanStats `json:"stats"`
// Results
Subdomains []*config.SubdomainResult `json:"subdomains"`
// Wildcard info (if detected)
Wildcard *WildcardReport `json:"wildcard,omitempty"`
// Findings summary
Findings FindingsSummary `json:"findings"`
}
// ScanMeta contains metadata about the scan
type ScanMeta struct {
Version string `json:"version"`
ToolName string `json:"tool_name"`
Target string `json:"target"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
Duration string `json:"duration"`
DurationMs int64 `json:"duration_ms"`
Concurrency int `json:"concurrency"`
Timeout int `json:"timeout"`
Options ScanOptions `json:"options"`
}
// ScanOptions contains the scan configuration
type ScanOptions struct {
BruteForce bool `json:"brute_force"`
HTTPProbe bool `json:"http_probe"`
PortScan bool `json:"port_scan"`
TakeoverCheck bool `json:"takeover_check"`
AIAnalysis bool `json:"ai_analysis"`
OnlyActive bool `json:"only_active"`
CustomWordlist bool `json:"custom_wordlist"`
CustomResolvers bool `json:"custom_resolvers"`
CustomPorts string `json:"custom_ports,omitempty"`
}
// ScanStats contains scan statistics
type ScanStats struct {
TotalSubdomains int `json:"total_subdomains"`
ActiveSubdomains int `json:"active_subdomains"`
InactiveSubdomains int `json:"inactive_subdomains"`
WithIPs int `json:"with_ips"`
WithHTTP int `json:"with_http"`
WithHTTPS int `json:"with_https"`
WithPorts int `json:"with_ports"`
TakeoverVulnerable int `json:"takeover_vulnerable"`
Vulnerabilities int `json:"vulnerabilities"`
CloudHosted int `json:"cloud_hosted"`
AIFindings int `json:"ai_findings"`
CVEFindings int `json:"cve_findings"`
PassiveSources int `json:"passive_sources"`
BruteForceFound int `json:"brute_force_found"`
}
// WildcardReport contains wildcard detection info
type WildcardReport struct {
Detected bool `json:"detected"`
IPs []string `json:"ips,omitempty"`
CNAME string `json:"cname,omitempty"`
StatusCode int `json:"status_code,omitempty"`
Confidence float64 `json:"confidence,omitempty"`
}
// FindingsSummary categorizes findings by severity
type FindingsSummary struct {
Critical []Finding `json:"critical,omitempty"`
High []Finding `json:"high,omitempty"`
Medium []Finding `json:"medium,omitempty"`
Low []Finding `json:"low,omitempty"`
Info []Finding `json:"info,omitempty"`
}
// Finding represents a single finding
type Finding struct {
Subdomain string `json:"subdomain"`
Type string `json:"type"`
Description string `json:"description"`
Evidence string `json:"evidence,omitempty"`
}
// ReportBuilder helps construct the JSON report
type ReportBuilder struct {
report *ScanReport
startTime time.Time
}
// NewReportBuilder creates a new report builder
func NewReportBuilder(domain string, cfg config.Config) *ReportBuilder {
now := time.Now()
return &ReportBuilder{
startTime: now,
report: &ScanReport{
Meta: ScanMeta{
Version: "0.1",
ToolName: "God's Eye",
Target: domain,
StartTime: now,
Concurrency: cfg.Concurrency,
Timeout: cfg.Timeout,
Options: ScanOptions{
BruteForce: !cfg.NoBrute,
HTTPProbe: !cfg.NoProbe,
PortScan: !cfg.NoPorts,
TakeoverCheck: !cfg.NoTakeover,
AIAnalysis: cfg.EnableAI,
OnlyActive: cfg.OnlyActive,
CustomWordlist: cfg.Wordlist != "",
CustomResolvers: cfg.Resolvers != "",
CustomPorts: cfg.Ports,
},
},
Stats: ScanStats{},
Findings: FindingsSummary{
Critical: []Finding{},
High: []Finding{},
Medium: []Finding{},
Low: []Finding{},
Info: []Finding{},
},
},
}
}
// SetWildcard sets wildcard detection info
func (rb *ReportBuilder) SetWildcard(detected bool, ips []string, cname string, statusCode int, confidence float64) {
rb.report.Wildcard = &WildcardReport{
Detected: detected,
IPs: ips,
CNAME: cname,
StatusCode: statusCode,
Confidence: confidence,
}
}
// SetPassiveSources sets the number of passive sources used
func (rb *ReportBuilder) SetPassiveSources(count int) {
rb.report.Stats.PassiveSources = count
}
// SetBruteForceFound sets the number of subdomains found via brute force
func (rb *ReportBuilder) SetBruteForceFound(count int) {
rb.report.Stats.BruteForceFound = count
}
// Finalize completes the report with results and calculates stats
func (rb *ReportBuilder) Finalize(results map[string]*config.SubdomainResult) *ScanReport {
endTime := time.Now()
duration := endTime.Sub(rb.startTime)
rb.report.Meta.EndTime = endTime
rb.report.Meta.Duration = duration.String()
rb.report.Meta.DurationMs = duration.Milliseconds()
// Sort subdomains
var sortedSubs []string
for sub := range results {
sortedSubs = append(sortedSubs, sub)
}
sort.Strings(sortedSubs)
// Build results list and calculate stats
rb.report.Subdomains = make([]*config.SubdomainResult, 0, len(results))
for _, sub := range sortedSubs {
r := results[sub]
rb.report.Subdomains = append(rb.report.Subdomains, r)
// Calculate stats
rb.report.Stats.TotalSubdomains++
if len(r.IPs) > 0 {
rb.report.Stats.WithIPs++
}
if r.StatusCode >= 200 && r.StatusCode < 400 {
rb.report.Stats.ActiveSubdomains++
} else if r.StatusCode >= 400 {
rb.report.Stats.InactiveSubdomains++
}
if r.TLSVersion != "" {
rb.report.Stats.WithHTTPS++
}
if r.StatusCode > 0 {
rb.report.Stats.WithHTTP++
}
if len(r.Ports) > 0 {
rb.report.Stats.WithPorts++
}
if r.CloudProvider != "" {
rb.report.Stats.CloudHosted++
}
if r.Takeover != "" {
rb.report.Stats.TakeoverVulnerable++
rb.addFinding("critical", sub, "Subdomain Takeover", r.Takeover)
}
// Count vulnerabilities
vulnCount := 0
if r.OpenRedirect {
vulnCount++
rb.addFinding("high", sub, "Open Redirect", "Vulnerable to open redirect attacks")
}
if r.CORSMisconfig != "" {
vulnCount++
rb.addFinding("medium", sub, "CORS Misconfiguration", r.CORSMisconfig)
}
if len(r.DangerousMethods) > 0 {
vulnCount++
rb.addFinding("medium", sub, "Dangerous HTTP Methods", join(r.DangerousMethods, ", "))
}
if r.GitExposed {
vulnCount++
rb.addFinding("high", sub, "Git Repository Exposed", ".git directory accessible")
}
if r.SvnExposed {
vulnCount++
rb.addFinding("high", sub, "SVN Repository Exposed", ".svn directory accessible")
}
if len(r.BackupFiles) > 0 {
vulnCount++
rb.addFinding("high", sub, "Backup Files Exposed", join(r.BackupFiles, ", "))
}
if len(r.JSSecrets) > 0 {
vulnCount++
for _, secret := range r.JSSecrets {
rb.addFinding("high", sub, "Secret in JavaScript", secret)
}
}
if vulnCount > 0 {
rb.report.Stats.Vulnerabilities += vulnCount
}
// AI findings
if len(r.AIFindings) > 0 {
rb.report.Stats.AIFindings += len(r.AIFindings)
severity := "info"
if r.AISeverity != "" {
severity = r.AISeverity
}
for _, finding := range r.AIFindings {
rb.addFinding(severity, sub, "AI Analysis", finding)
}
}
// CVE findings
if len(r.CVEFindings) > 0 {
rb.report.Stats.CVEFindings += len(r.CVEFindings)
for _, cve := range r.CVEFindings {
rb.addFinding("high", sub, "CVE Vulnerability", cve)
}
}
// Info findings
if len(r.AdminPanels) > 0 {
for _, panel := range r.AdminPanels {
rb.addFinding("info", sub, "Admin Panel Found", panel)
}
}
if len(r.APIEndpoints) > 0 {
for _, endpoint := range r.APIEndpoints {
rb.addFinding("info", sub, "API Endpoint Found", endpoint)
}
}
}
return rb.report
}
// addFinding adds a finding to the appropriate severity category
func (rb *ReportBuilder) addFinding(severity, subdomain, findingType, description string) {
finding := Finding{
Subdomain: subdomain,
Type: findingType,
Description: description,
}
switch severity {
case "critical":
rb.report.Findings.Critical = append(rb.report.Findings.Critical, finding)
case "high":
rb.report.Findings.High = append(rb.report.Findings.High, finding)
case "medium":
rb.report.Findings.Medium = append(rb.report.Findings.Medium, finding)
case "low":
rb.report.Findings.Low = append(rb.report.Findings.Low, finding)
default:
rb.report.Findings.Info = append(rb.report.Findings.Info, finding)
}
}
// WriteJSON writes the report as JSON to a writer
func (rb *ReportBuilder) WriteJSON(w io.Writer, indent bool) error {
encoder := json.NewEncoder(w)
if indent {
encoder.SetIndent("", " ")
}
return encoder.Encode(rb.report)
}
// GetReport returns the built report
func (rb *ReportBuilder) GetReport() *ScanReport {
return rb.report
}
// Helper function
func join(strs []string, sep string) string {
if len(strs) == 0 {
return ""
}
result := strs[0]
for _, s := range strs[1:] {
result += sep + s
}
return result
}