// Package security runs the v1 security checks (open redirect, CORS, // HTTP methods, git/svn, backups, admin, API) on every probed host. // // Reads hosts from the store (not events) so late-start phases don't miss // the upstream HTTPProbed events. package security import ( "context" "sync" "time" "god-eye/internal/eventbus" gohttp "god-eye/internal/http" "god-eye/internal/module" "god-eye/internal/security" "god-eye/internal/store" ) const ModuleName = "security.checks" type secModule struct{} func Register() { module.Register(&secModule{}) } func (*secModule) Name() string { return ModuleName } func (*secModule) Phase() module.Phase { return module.PhaseAnalysis } func (*secModule) Consumes() []eventbus.EventType { return []eventbus.EventType{eventbus.EventHTTPProbed} } func (*secModule) Produces() []eventbus.EventType { return []eventbus.EventType{eventbus.EventVulnerability} } func (*secModule) DefaultEnabled() bool { return true } func (*secModule) Run(mctx module.Context) error { conc := mctx.Config.Int("concurrency", 200) if conc <= 0 { conc = 200 } timeout := mctx.Config.Int("timeout", 5) processed := make(map[string]struct{}) var processedMu sync.Mutex shouldProcess := func(host string) bool { processedMu.Lock() defer processedMu.Unlock() if _, dup := processed[host]; dup { return false } processed[host] = struct{}{} return true } work := make(chan string, conc*2) var wg sync.WaitGroup for i := 0; i < conc; i++ { wg.Add(1) go func() { defer wg.Done() for host := range work { runChecks(mctx, host, timeout) } }() } // Drain: every host that got a successful HTTP probe. for _, h := range mctx.Store.All(mctx.Ctx) { if h == nil || h.StatusCode == 0 { continue } if !shouldProcess(h.Subdomain) { continue } select { case work <- h.Subdomain: case <-mctx.Ctx.Done(): close(work) wg.Wait() return nil } } // Listen for late HTTPProbed events. sub := mctx.Bus.Subscribe(eventbus.EventHTTPProbed, func(_ context.Context, e eventbus.Event) { ev, ok := e.(eventbus.HTTPProbed) if !ok || ev.StatusCode == 0 { return } host := ev.Meta().Target if !shouldProcess(host) { return } select { case work <- host: case <-mctx.Ctx.Done(): } }) defer sub.Unsubscribe() select { case <-time.After(500 * time.Millisecond): case <-mctx.Ctx.Done(): } close(work) wg.Wait() return nil } func runChecks(mctx module.Context, host string, timeout int) { if mctx.Ctx.Err() != nil { return } client := gohttp.GetSharedClient(timeout) var openRedirect bool var cors string var allowed, dangerous []string var admin, backups, apis []string var gitExposed, svnExposed bool var wg sync.WaitGroup wg.Add(7) go func() { defer wg.Done(); openRedirect = security.CheckOpenRedirectWithClient(host, client) }() go func() { defer wg.Done(); cors = security.CheckCORSWithClient(host, client) }() go func() { defer wg.Done(); allowed, dangerous = security.CheckHTTPMethodsWithClient(host, client) }() go func() { defer wg.Done(); admin = security.CheckAdminPanelsWithClient(host, client) }() go func() { defer wg.Done(); gitExposed, svnExposed = security.CheckGitSvnExposureWithClient(host, client) }() go func() { defer wg.Done(); backups = security.CheckBackupFilesWithClient(host, client) }() go func() { defer wg.Done(); apis = security.CheckAPIEndpointsWithClient(host, client) }() wg.Wait() _ = mctx.Store.Upsert(mctx.Ctx, host, func(h *store.Host) { now := time.Now() if openRedirect { h.Vulnerabilities = append(h.Vulnerabilities, store.Vulnerability{ ID: "open-redirect", Title: "Open Redirect", Description: "Server redirects to attacker-controlled URL via redirect parameter", Severity: string(eventbus.SeverityMedium), URL: "https://" + host, OWASP: "A01:2021-Broken Access Control", FoundAt: now, }) } if cors != "" { h.Vulnerabilities = append(h.Vulnerabilities, store.Vulnerability{ ID: "cors-misconfig", Title: "CORS Misconfiguration", Description: cors, Severity: string(eventbus.SeverityHigh), URL: "https://" + host, OWASP: "A05:2021-Security Misconfiguration", FoundAt: now, }) } if len(dangerous) > 0 { h.Vulnerabilities = append(h.Vulnerabilities, store.Vulnerability{ ID: "dangerous-http-methods", Title: "Dangerous HTTP Methods Enabled", Description: "Server allows potentially dangerous methods", Severity: string(eventbus.SeverityMedium), Evidence: joinStrings(dangerous, ", "), URL: "https://" + host, OWASP: "A05:2021-Security Misconfiguration", FoundAt: now, }) } if gitExposed { h.Vulnerabilities = append(h.Vulnerabilities, store.Vulnerability{ ID: "git-exposed", Title: "Git Repository Exposed", Description: ".git directory is publicly accessible", Severity: string(eventbus.SeverityCritical), URL: "https://" + host + "/.git/config", OWASP: "A05:2021-Security Misconfiguration", FoundAt: now, }) } if svnExposed { h.Vulnerabilities = append(h.Vulnerabilities, store.Vulnerability{ ID: "svn-exposed", Title: "SVN Repository Exposed", Description: ".svn directory is publicly accessible", Severity: string(eventbus.SeverityHigh), URL: "https://" + host + "/.svn/entries", OWASP: "A05:2021-Security Misconfiguration", FoundAt: now, }) } for _, b := range backups { h.Vulnerabilities = append(h.Vulnerabilities, store.Vulnerability{ ID: "backup-file", Title: "Backup File Exposed", Description: "Backup file accessible: " + b, Severity: string(eventbus.SeverityHigh), URL: b, OWASP: "A05:2021-Security Misconfiguration", FoundAt: now, }) } _ = allowed _ = admin _ = apis }) now := time.Now() base := eventbus.EventMeta{At: now, Source: ModuleName, Target: host} emit := func(ev eventbus.VulnerabilityFound) { mctx.Bus.Publish(mctx.Ctx, ev) } if openRedirect { emit(eventbus.VulnerabilityFound{EventMeta: base, ID: "open-redirect", Title: "Open Redirect", Severity: eventbus.SeverityMedium, URL: "https://" + host, OWASP: "A01:2021-Broken Access Control"}) } if cors != "" { emit(eventbus.VulnerabilityFound{EventMeta: base, ID: "cors-misconfig", Title: "CORS Misconfiguration", Description: cors, Severity: eventbus.SeverityHigh, URL: "https://" + host, OWASP: "A05:2021-Security Misconfiguration"}) } if len(dangerous) > 0 { emit(eventbus.VulnerabilityFound{EventMeta: base, ID: "dangerous-http-methods", Title: "Dangerous HTTP Methods", Evidence: joinStrings(dangerous, ", "), Severity: eventbus.SeverityMedium, URL: "https://" + host, OWASP: "A05:2021-Security Misconfiguration"}) } if gitExposed { emit(eventbus.VulnerabilityFound{EventMeta: base, ID: "git-exposed", Title: "Git Repository Exposed", Severity: eventbus.SeverityCritical, URL: "https://" + host + "/.git/config", OWASP: "A05:2021-Security Misconfiguration"}) } if svnExposed { emit(eventbus.VulnerabilityFound{EventMeta: base, ID: "svn-exposed", Title: "SVN Repository Exposed", Severity: eventbus.SeverityHigh, URL: "https://" + host + "/.svn/entries", OWASP: "A05:2021-Security Misconfiguration"}) } for _, b := range backups { emit(eventbus.VulnerabilityFound{EventMeta: base, ID: "backup-file", Title: "Backup File Exposed", Severity: eventbus.SeverityHigh, URL: b, OWASP: "A05:2021-Security Misconfiguration"}) } } func joinStrings(ss []string, sep string) string { if len(ss) == 0 { return "" } out := ss[0] for _, s := range ss[1:] { out += sep + s } return out }