// Package vhost is a Fase 0.6 adapter around v1 network.VHostScanner which // performs virtual host discovery on resolved IPs. Reveals additional // hostnames sharing infrastructure with in-scope targets. package vhost import ( "strings" "sync" "time" "god-eye/internal/eventbus" "god-eye/internal/module" "god-eye/internal/network" "god-eye/internal/store" ) const ModuleName = "discovery.vhost" type vhostModule struct{} func Register() { module.Register(&vhostModule{}) } func (*vhostModule) Name() string { return ModuleName } func (*vhostModule) Phase() module.Phase { return module.PhaseResolution } func (*vhostModule) Consumes() []eventbus.EventType { return []eventbus.EventType{eventbus.EventDNSResolved} } func (*vhostModule) Produces() []eventbus.EventType { return []eventbus.EventType{eventbus.EventSubdomainDiscovered} } func (*vhostModule) DefaultEnabled() bool { return false } // opt-in func (*vhostModule) Run(mctx module.Context) error { if !mctx.Config.Bool("vhost_scan", false) { return nil } timeout := mctx.Config.Int("timeout", 10) target := mctx.Target hosts := mctx.Store.All(mctx.Ctx) seenIP := make(map[string]struct{}) for _, h := range hosts { for _, ip := range h.IPs { seenIP[ip] = struct{}{} } } scanner := network.NewVHostScanner(timeout) var wg sync.WaitGroup for ip := range seenIP { ip := ip if mctx.Ctx.Err() != nil { break } wg.Add(1) go func() { defer wg.Done() res := scanner.DiscoverVHosts(mctx.Ctx, ip) if res == nil { return } for _, h := range res.Domains { h = strings.ToLower(strings.TrimSpace(h)) if h == "" || !strings.HasSuffix(h, target) { continue } _ = mctx.Store.Upsert(mctx.Ctx, h, func(sh *store.Host) { store.AddIPs(sh, []string{ip}) store.AddDiscoveryMethod(sh, "vhost") }) mctx.Bus.Publish(mctx.Ctx, eventbus.SubdomainDiscovered{ EventMeta: eventbus.EventMeta{At: time.Now(), Source: ModuleName, Target: h}, Subdomain: h, Method: "vhost", }) } }() } wg.Wait() return nil }