Add recon form fingerprint library and SSRF canary mode.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Score crawl inputs by SSRF-prone field names and register ping-back canaries for operator paste confirmation.
This commit is contained in:
38
server/internal/api/recon_canary_hub.go
Normal file
38
server/internal/api/recon_canary_hub.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package api
|
||||
|
||||
import ("sync"; "time"; "crypto-miner-server/internal/recon")
|
||||
|
||||
type ReconCanaryHub struct { mu sync.RWMutex; byID map[string]*recon.SSRFCanaryInfo }
|
||||
|
||||
func NewReconCanaryHub() *ReconCanaryHub { return &ReconCanaryHub{byID: map[string]*recon.SSRFCanaryInfo{}} }
|
||||
|
||||
func (h *ReconCanaryHub) Register(id string, info *recon.SSRFCanaryInfo) {
|
||||
if h == nil || info == nil { return }
|
||||
h.mu.Lock(); defer h.mu.Unlock(); cp := *info; h.byID[id] = &cp
|
||||
}
|
||||
|
||||
func (h *ReconCanaryHub) Get(id string) *recon.SSRFCanaryInfo {
|
||||
if h == nil { return nil }
|
||||
h.mu.RLock(); defer h.mu.RUnlock()
|
||||
if x := h.byID[id]; x != nil { cp := *x; return &cp }
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *ReconCanaryHub) MarkHit(id string) bool {
|
||||
if h == nil { return false }
|
||||
h.mu.Lock(); defer h.mu.Unlock()
|
||||
x, ok := h.byID[id]; if !ok { return false }
|
||||
if x.Status == "confirmed" { return true }
|
||||
now := time.Now().UTC(); x.Status = "confirmed"; x.HitAt = &now; return true
|
||||
}
|
||||
|
||||
func (h *ReconCanaryHub) UpdatePasteField(id, name, id2 string) {
|
||||
if h == nil { return }
|
||||
h.mu.Lock(); defer h.mu.Unlock()
|
||||
if x := h.byID[id]; x != nil { x.PasteFieldName, x.PasteFieldID = name, id2 }
|
||||
}
|
||||
|
||||
func BroadcastReconCanaryHit(hub *WSHub, scanID string) {
|
||||
if hub == nil { return }
|
||||
hub.broadcastDashboard(Message{Type: "recon_canary_hit", Payload: mustMarshal(map[string]interface{}{"scan_id": scanID, "status": "confirmed"})})
|
||||
}
|
||||
@@ -2,24 +2,32 @@ 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"
|
||||
)
|
||||
|
||||
// ReconHandler serves owned-target browser deploy recon.
|
||||
type ReconHandler struct {
|
||||
db *dbpkg.Database
|
||||
wsHub *WSHub
|
||||
db *dbpkg.Database
|
||||
wsHub *WSHub
|
||||
publicURL func() string
|
||||
canaryHub *ReconCanaryHub
|
||||
}
|
||||
|
||||
func NewReconHandler(database *dbpkg.Database, hub *WSHub) *ReconHandler {
|
||||
return &ReconHandler{db: database, wsHub: hub}
|
||||
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()}
|
||||
}
|
||||
|
||||
// POST /api/v1/recon/scan
|
||||
func (h *ReconHandler) Scan(w http.ResponseWriter, r *http.Request) {
|
||||
var req recon.ScanRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -31,33 +39,224 @@ func (h *ReconHandler) Scan(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "host required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
report, err := recon.Scan(req)
|
||||
host, err := recon.NormalizeHost(req.Host)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if h != nil && h.db != nil {
|
||||
payload := map[string]interface{}{
|
||||
"host": report.Host,
|
||||
"open_ports": openPortList(report.Ports),
|
||||
"ssrf_score": crawlSSRFScore(report.Crawl),
|
||||
"recommendations": len(report.Recommendations),
|
||||
"cms_fingerprints": crawlCMS(report.Crawl),
|
||||
}
|
||||
_ = (&OathLedgerBridge{DB: h.db, Hub: oathHub(h)}).Record(
|
||||
AuthUsername(r),
|
||||
dbpkg.OathReconScan,
|
||||
"",
|
||||
"",
|
||||
dbpkg.OathOutcomeSuccess,
|
||||
map[string]string{"host": report.Host},
|
||||
payload,
|
||||
)
|
||||
req.Host = host
|
||||
scanID := uuid.New().String()
|
||||
if req.SSRFCanary {
|
||||
h.registerSSRfCanary(scanID, host, r)
|
||||
}
|
||||
|
||||
writeJSON(w, report)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"scan_id": scanID,
|
||||
"status": "running",
|
||||
})
|
||||
|
||||
go h.runScan(req, scanID, AuthUsername(r))
|
||||
}
|
||||
|
||||
func (h *ReconHandler) runScan(req recon.ScanRequest, scanID, operator string) {
|
||||
emit := func(eventType string, payload map[string]interface{}) {
|
||||
if h != nil && h.wsHub != nil {
|
||||
h.wsHub.broadcastDashboard(Message{Type: eventType, Payload: mustMarshal(payload)})
|
||||
}
|
||||
}
|
||||
|
||||
report, err := recon.ScanStream(req, scanID, emit)
|
||||
if err != nil {
|
||||
if h != nil && h.wsHub != nil {
|
||||
h.wsHub.broadcastDashboard(Message{
|
||||
Type: "recon_complete",
|
||||
Payload: mustMarshal(map[string]interface{}{"scan_id": scanID, "host": req.Host, "status": "failed", "error": err.Error()}),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
report.ScanID = scanID
|
||||
if report.Status == "" {
|
||||
report.Status = "complete"
|
||||
}
|
||||
if req.SSRFCanary {
|
||||
h.finalizeSSRfCanary(scanID, report)
|
||||
}
|
||||
if h != nil && h.db != nil {
|
||||
_ = h.db.InsertReconScan(report)
|
||||
}
|
||||
|
||||
if h != nil && h.wsHub != nil {
|
||||
h.wsHub.broadcastDashboard(Message{
|
||||
Type: "recon_complete",
|
||||
Payload: mustMarshal(map[string]interface{}{
|
||||
"scan_id": scanID, "host": report.Host, "status": report.Status, "profile": report.Profile,
|
||||
}),
|
||||
})
|
||||
}
|
||||
emitter := &HubSeerEmitter{Hub: oathHub(h), DB: h.db}
|
||||
_ = emitter.EmitSeerEvent("recon_complete", "", map[string]interface{}{
|
||||
"scan_id": scanID, "host": report.Host, "profile": report.Profile, "open_ports": openPortList(report.Ports),
|
||||
})
|
||||
|
||||
if h != nil && h.db != nil {
|
||||
_ = (&OathLedgerBridge{DB: h.db, Hub: oathHub(h)}).Record(
|
||||
operator, 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),
|
||||
"recommendations": len(report.Recommendations), "cms_fingerprints": crawlCMS(report.Crawl),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/v1/recon/history?host=
|
||||
func (h *ReconHandler) History(w http.ResponseWriter, r *http.Request) {
|
||||
host := strings.TrimSpace(r.URL.Query().Get("host"))
|
||||
if host == "" {
|
||||
http.Error(w, "host required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
nhost, err := recon.NormalizeHost(host)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if h == nil || h.db == nil {
|
||||
writeJSON(w, map[string]interface{}{"host": nhost, "history": []recon.ReconHistoryEntry{}})
|
||||
return
|
||||
}
|
||||
rows, err := h.db.ListReconScansByHost(nhost, 10)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]interface{}{"host": nhost, "history": recon.BuildHistory(rows)})
|
||||
}
|
||||
|
||||
// GET /api/v1/recon/export/{scan_id}?format=json|pdf
|
||||
func (h *ReconHandler) Export(w http.ResponseWriter, r *http.Request) {
|
||||
scanID := strings.TrimSpace(chi.URLParam(r, "scan_id"))
|
||||
if scanID == "" {
|
||||
http.Error(w, "scan_id required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
format := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("format")))
|
||||
if format == "" {
|
||||
format = "json"
|
||||
}
|
||||
if h == nil || h.db == nil {
|
||||
http.Error(w, "database unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
report, err := h.db.GetReconScan(scanID)
|
||||
if err != nil {
|
||||
http.Error(w, "scan not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
switch format {
|
||||
case "json":
|
||||
writeJSON(w, report)
|
||||
case "pdf":
|
||||
w.Header().Set("Content-Type", "application/pdf")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="recon-`+scanID+`.pdf"`)
|
||||
_, _ = w.Write(recon.ReportToPDF(report))
|
||||
default:
|
||||
http.Error(w, "format must be json or pdf", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@@ -2,66 +2,124 @@ package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"crypto-miner-server/internal/db"
|
||||
"crypto-miner-server/internal/recon"
|
||||
)
|
||||
|
||||
func waitForReconScan(t *testing.T, database *db.Database, scanID string) *recon.ScanReport {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(3 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
report, err := database.GetReconScan(scanID)
|
||||
if err == nil && report != nil {
|
||||
return report
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("scan %s not persisted", scanID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func withScanIDParam(r *http.Request, scanID string) *http.Request {
|
||||
ctx := chi.NewRouteContext()
|
||||
ctx.URLParams.Add("scan_id", scanID)
|
||||
return r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, ctx))
|
||||
}
|
||||
|
||||
func TestReconScanEndpoint(t *testing.T) {
|
||||
recon.SetPortDialHook(func(host string, port int, _ time.Duration) bool {
|
||||
return port == 80
|
||||
})
|
||||
recon.SetPortDialHook(func(host string, port int, _ time.Duration) bool { return port == 80 })
|
||||
t.Cleanup(func() { recon.SetPortDialHook(nil) })
|
||||
recon.SetFetchPageHook(func(rawURL string) (int, string, error) {
|
||||
return 200, `<html><form enctype="multipart/form-data"><input type="text" name="preview_url"><input type="file" name="payload"></form></html>`, nil
|
||||
return 200, `<html><form enctype="multipart/form-data"><input type="file" name="payload"></form></html>`, nil
|
||||
})
|
||||
t.Cleanup(func() { recon.SetFetchPageHook(nil) })
|
||||
|
||||
database, err := db.New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
database, _ := db.New(t.TempDir())
|
||||
t.Cleanup(func() { _ = database.Close() })
|
||||
h := NewReconHandler(database, NewWSHub(database))
|
||||
body, _ := json.Marshal(map[string]interface{}{"host": "recon.lab", "port": 80, "scheme": "http"})
|
||||
w := httptest.NewRecorder()
|
||||
h.Scan(w, httptest.NewRequest(http.MethodPost, "/api/v1/recon/scan", bytes.NewReader(body)))
|
||||
if w.Code != http.StatusAccepted {
|
||||
t.Fatalf("%d %s", w.Code, w.Body.String())
|
||||
}
|
||||
var started map[string]string
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &started)
|
||||
report := waitForReconScan(t, database, started["scan_id"])
|
||||
if report.Host != "recon.lab" || report.Crawl == nil {
|
||||
t.Fatalf("%+v", report)
|
||||
}
|
||||
}
|
||||
|
||||
h := NewReconHandler(database, nil)
|
||||
body, _ := json.Marshal(map[string]interface{}{
|
||||
"host": "recon.lab",
|
||||
"port": 80,
|
||||
"scheme": "http",
|
||||
func TestReconSSRfCanaryFlow(t *testing.T) {
|
||||
recon.SetPortDialHook(func(host string, port int, _ time.Duration) bool { return port == 80 })
|
||||
t.Cleanup(func() { recon.SetPortDialHook(nil) })
|
||||
recon.SetFetchPageHook(func(rawURL string) (int, string, error) {
|
||||
return 200, `<html><input name="callback_url" id="cb">`, nil
|
||||
})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/recon/scan", bytes.NewReader(body))
|
||||
t.Cleanup(func() { recon.SetFetchPageHook(nil) })
|
||||
database, _ := db.New(t.TempDir())
|
||||
t.Cleanup(func() { _ = database.Close() })
|
||||
h := NewReconHandler(database, nil, func() string { return "https://canary.test" })
|
||||
body, _ := json.Marshal(map[string]interface{}{"host": "recon.lab", "port": 80, "scheme": "http", "ssrf_canary": true})
|
||||
w := httptest.NewRecorder()
|
||||
h.Scan(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
|
||||
h.Scan(w, httptest.NewRequest(http.MethodPost, "/api/v1/recon/scan", bytes.NewReader(body)))
|
||||
if w.Code != http.StatusAccepted {
|
||||
t.Fatalf("%d", w.Code)
|
||||
}
|
||||
var report recon.ScanReport
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &report); err != nil {
|
||||
t.Fatal(err)
|
||||
var started map[string]string
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &started)
|
||||
report := waitForReconScan(t, database, started["scan_id"])
|
||||
if report.Canary == nil || report.Canary.PasteFieldName != "callback_url" {
|
||||
t.Fatalf("%+v", report.Canary)
|
||||
}
|
||||
if report.Host != "recon.lab" || report.Crawl == nil || len(report.Recommendations) == 0 {
|
||||
t.Fatalf("report=%+v", report)
|
||||
pw := httptest.NewRecorder()
|
||||
h.CanaryPing(pw, withScanIDParam(httptest.NewRequest(http.MethodGet, "/recon/ping/"+report.ScanID, nil), report.ScanID))
|
||||
sw := httptest.NewRecorder()
|
||||
h.CanaryStatus(sw, withScanIDParam(httptest.NewRequest(http.MethodGet, "/api/v1/recon/canary/"+report.ScanID, nil), report.ScanID))
|
||||
var st recon.SSRFCanaryInfo
|
||||
_ = json.Unmarshal(sw.Body.Bytes(), &st)
|
||||
if st.Status != "confirmed" {
|
||||
t.Fatalf("%+v", st)
|
||||
}
|
||||
rows, err := database.ListOathLedger(5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(rows) == 0 || rows[0].ActionType != db.OathReconScan {
|
||||
t.Fatalf("oath rows=%+v", rows)
|
||||
if !strings.Contains(report.Canary.PasteTarget, report.ScanID) {
|
||||
t.Fatal(report.Canary.PasteTarget)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconScanRequiresHost(t *testing.T) {
|
||||
h := NewReconHandler(nil, nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/recon/scan", bytes.NewReader([]byte(`{}`)))
|
||||
func TestReconHistoryAndExport(t *testing.T) {
|
||||
recon.SetPortDialHook(func(host string, port int, _ time.Duration) bool { return port == 80 })
|
||||
t.Cleanup(func() { recon.SetPortDialHook(nil) })
|
||||
recon.SetFetchPageHook(func(rawURL string) (int, string, error) {
|
||||
return 200, `<html><form action="/up" enctype="multipart/form-data"><input type="file" name="f"></form></html>`, nil
|
||||
})
|
||||
t.Cleanup(func() { recon.SetFetchPageHook(nil) })
|
||||
database, _ := db.New(t.TempDir())
|
||||
t.Cleanup(func() { _ = database.Close() })
|
||||
h := NewReconHandler(database, nil)
|
||||
body, _ := json.Marshal(map[string]interface{}{"host": "hist.lab", "port": 80, "scheme": "http", "profile": "quick"})
|
||||
w := httptest.NewRecorder()
|
||||
h.Scan(w, req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status=%d", w.Code)
|
||||
h.Scan(w, httptest.NewRequest(http.MethodPost, "/api/v1/recon/scan", bytes.NewReader(body)))
|
||||
var started map[string]string
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &started)
|
||||
waitForReconScan(t, database, started["scan_id"])
|
||||
histW := httptest.NewRecorder()
|
||||
h.History(histW, httptest.NewRequest(http.MethodGet, "/api/v1/recon/history?host=hist.lab", nil))
|
||||
if histW.Code != http.StatusOK {
|
||||
t.Fatalf("history %d", histW.Code)
|
||||
}
|
||||
exportW := httptest.NewRecorder()
|
||||
h.Export(exportW, withScanIDParam(httptest.NewRequest(http.MethodGet, "/api/v1/recon/export/"+started["scan_id"]+"?format=pdf", nil), started["scan_id"]))
|
||||
if exportW.Code != http.StatusOK {
|
||||
t.Fatalf("export %d %s", exportW.Code, exportW.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,6 +501,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
|
||||
}
|
||||
|
||||
reconHandler := NewReconHandler(database, wsHub)
|
||||
subnetDiscoveryHandler := NewSubnetDiscoveryHandler(database, wsHub)
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
@@ -556,6 +557,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
|
||||
|
||||
r.Post("/recon/scan", reconHandler.Scan)
|
||||
r.Post("/recon/relay-scan", reconHandler.RelayScan)
|
||||
r.Get("/recon/discovered-hosts", subnetDiscoveryHandler.GetDiscoveredHosts)
|
||||
|
||||
// Agents
|
||||
r.Get("/agents", h.ListAgents)
|
||||
|
||||
Reference in New Issue
Block a user