package api import ( "encoding/json" "log" "net/http" "strings" "github.com/go-chi/chi/v5" "github.com/google/uuid" dbpkg "crypto-miner-server/internal/db" "crypto-miner-server/internal/recon" ) type ReconHandler struct { db *dbpkg.Database wsHub *WSHub publicURL func() string canaryHub *ReconCanaryHub } func NewReconHandler(database *dbpkg.Database, hub *WSHub, publicURL ...func() string) *ReconHandler { var fn func() string if len(publicURL) > 0 { fn = publicURL[0] } return &ReconHandler{db: database, wsHub: hub, publicURL: fn, canaryHub: NewReconCanaryHub()} } func (h *ReconHandler) Scan(w http.ResponseWriter, r *http.Request) { var req recon.ScanRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON", http.StatusBadRequest) return } req.Host = strings.TrimSpace(req.Host) if req.Host == "" { http.Error(w, "host required", http.StatusBadRequest) return } host, err := recon.NormalizeHost(req.Host) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } req.Host = host scanID := uuid.New().String() if req.SSRFCanary { h.registerSSRfCanary(scanID, host, r) } report, err := recon.ScanStream(req, scanID, nil) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } report.ScanID = scanID if req.SSRFCanary { h.finalizeSSRfCanary(scanID, report) } if h != nil && h.db != nil { _ = (&OathLedgerBridge{DB: h.db, Hub: oathHub(h)}).Record(AuthUsername(r), dbpkg.OathReconScan, "", "", dbpkg.OathOutcomeSuccess, map[string]string{"host": report.Host, "scan_id": scanID}, map[string]interface{}{"host": report.Host, "scan_id": scanID, "open_ports": openPortList(report.Ports), "ssrf_score": crawlSSRFScore(report.Crawl)}) } writeJSON(w, report) } func (h *ReconHandler) registerSSRfCanary(scanID, host string, r *http.Request) { base := "" if h.publicURL != nil { base = strings.TrimSpace(h.publicURL()) } if base == "" && r != nil { hh := strings.TrimSpace(r.Header.Get("X-Forwarded-Host")) if hh == "" { hh = r.Host } proto := strings.TrimSpace(r.Header.Get("X-Forwarded-Proto")) if proto == "" { proto = "https" } if hh != "" { base = proto + "://" + hh } } url := recon.BuildSSRfCanaryURL(base, scanID) info := &recon.SSRFCanaryInfo{ScanID: scanID, URL: url, Status: "pending", PasteTarget: url} if h.db != nil { _ = h.db.InsertReconSSRfCanary(scanID, host, url, "", "") } if h.canaryHub != nil { h.canaryHub.Register(scanID, info) } } func (h *ReconHandler) finalizeSSRfCanary(scanID string, report *recon.ScanReport) { info, _ := h.getCanaryInfo(scanID) if info == nil || report == nil || report.Crawl == nil { return } recon.ApplyCanaryPasteTarget(report.Crawl.FingerprintFields, info.URL) if top := recon.TopFormFieldFingerprint(report.Crawl.FingerprintFields); top != nil { info.PasteFieldName, info.PasteFieldID = top.Name, top.ID if h.canaryHub != nil { h.canaryHub.UpdatePasteField(scanID, top.Name, top.ID) } if h.db != nil { _ = h.db.UpdateReconSSRfCanaryPasteField(scanID, top.Name, top.ID) } } report.Canary = info } func (h *ReconHandler) getCanaryInfo(scanID string) (*recon.SSRFCanaryInfo, error) { if h.canaryHub != nil { if info := h.canaryHub.Get(scanID); info != nil { return info, nil } } if h.db != nil { return h.db.GetReconSSRfCanary(scanID) } return nil, nil } func (h *ReconHandler) CanaryStatus(w http.ResponseWriter, r *http.Request) { scanID := strings.TrimSpace(chi.URLParam(r, "scan_id")) info, err := h.getCanaryInfo(scanID) if scanID == "" || err != nil || info == nil { http.Error(w, "canary not found", http.StatusNotFound) return } writeJSON(w, info) } func (h *ReconHandler) CanaryPing(w http.ResponseWriter, r *http.Request) { scanID := strings.TrimSpace(chi.URLParam(r, "scan_id")) if scanID == "" { writeJSON(w, map[string]interface{}{"ok": false}) return } hit := h.canaryHub != nil && h.canaryHub.MarkHit(scanID) if !hit && h.db != nil { hit, _ = h.db.MarkReconSSRfCanaryHit(scanID) } if !hit { writeJSON(w, map[string]interface{}{"ok": false, "error": "canary not found"}) return } log.Printf("[recon] SSRF canary hit scan_id=%s", scanID) if h.wsHub != nil { BroadcastReconCanaryHit(h.wsHub, scanID) } writeJSON(w, map[string]interface{}{"ok": true, "scan_id": scanID, "status": "confirmed"}) } func oathHub(h *ReconHandler) *WSHub { if h == nil { return nil } return h.wsHub } func openPortList(ports []recon.PortResult) []int { var out []int for _, p := range ports { if p.Open { out = append(out, p.Port) } } return out } func crawlSSRFScore(c *recon.CrawlReport) int { if c == nil { return 0 } return c.SSRFScore } func crawlCMS(c *recon.CrawlReport) []string { if c == nil { return nil } return c.CMSFingerprints }