mirror of
https://github.com/lunchcat/sif.git
synced 2026-06-12 11:01:24 -07:00
839c0a779c
four recon-flagged bugs, each with a focused test:
- dnslist fired both http and https per candidate and counted a "found"
on any non-error response (incl 404 and wildcard catch-all redirects),
so every host double-counted and a wildcard-dns host flooded results.
probe http then https with per-subdomain dedupe, gate on a meaningful
(2xx, non-redirect) status, and stop chasing redirects so a catch-all
301 reads as a redirect instead of a 200.
- fetchRobotsTXT recursed on every 301 Location with no depth limit and
no visited set, so an A->B->A loop blew the stack. bound it to a named
hop cap and a visited set, iteratively.
- framework cve lookup used best.version ("unknown" when the detector
only fingerprints the framework) and threw away the version
ExtractVersionOptimized dug out of the body, missing CVEs. reconcile
via resolveVersion, preferring the extracted concrete version.
- subdomain takeover flagged a dangling cname whenever a no-such-host
coincided with ANY cname (LookupCNAME echoes the host back for plain A
records). only flag when the cname points off-host at a known
takeoverable provider.
99 lines
3.5 KiB
Go
99 lines
3.5 KiB
Go
/*
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
: :
|
|
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
|
: ▄█ █ █▀ · BSD 3-Clause License :
|
|
: :
|
|
: (c) 2022-2026 vmfunc, xyzeva, :
|
|
: lunchcat alumni & contributors :
|
|
: :
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
*/
|
|
|
|
package scan
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestMeaningfulStatus(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
code int
|
|
want bool
|
|
}{
|
|
{"ok counts", http.StatusOK, true},
|
|
{"204 counts", http.StatusNoContent, true},
|
|
{"301 catch-all redirect dropped", http.StatusMovedPermanently, false},
|
|
{"302 catch-all redirect dropped", http.StatusFound, false},
|
|
{"404 dropped", http.StatusNotFound, false},
|
|
{"500 dropped", http.StatusInternalServerError, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := meaningfulStatus(tt.code); got != tt.want {
|
|
t.Errorf("meaningfulStatus(%d) = %v, want %v", tt.code, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// a host that answers 200 over http should count exactly once, not once per
|
|
// scheme - the old path appended on both http and https.
|
|
func TestProbeSubdomain_DedupesAcrossSchemes(t *testing.T) {
|
|
var hits int32
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
atomic.AddInt32(&hits, 1)
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
host := strings.TrimPrefix(srv.URL, "http://")
|
|
client := &http.Client{Timeout: 5 * time.Second}
|
|
|
|
url, scheme := probeSubdomain(client, host)
|
|
if url == "" {
|
|
t.Fatal("expected http probe to count the host")
|
|
}
|
|
if scheme != dnsSchemeHTTP {
|
|
t.Errorf("expected http scheme to win, got %q", scheme)
|
|
}
|
|
// http already counted, so https must not be tried - one request total.
|
|
if got := atomic.LoadInt32(&hits); got != 1 {
|
|
t.Errorf("expected exactly 1 probe request, got %d", got)
|
|
}
|
|
}
|
|
|
|
// a wildcard catch-all that 404s (or 301s) every candidate must not be reported
|
|
// as found - that's the flood the gating closes.
|
|
func TestProbeSubdomain_WildcardCatchAllNotFound(t *testing.T) {
|
|
for _, code := range []int{http.StatusNotFound, http.StatusMovedPermanently} {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if code == http.StatusMovedPermanently {
|
|
w.Header().Set("Location", "https://catch-all.example/")
|
|
}
|
|
w.WriteHeader(code)
|
|
}))
|
|
|
|
host := strings.TrimPrefix(srv.URL, "http://")
|
|
client := &http.Client{
|
|
Timeout: 5 * time.Second,
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
},
|
|
}
|
|
|
|
url, _ := probeSubdomain(client, host)
|
|
if url != "" {
|
|
t.Errorf("status %d should not count as found, got %q", code, url)
|
|
}
|
|
srv.Close()
|
|
}
|
|
}
|