mirror of
https://github.com/lunchcat/sif.git
synced 2026-06-12 19:11:25 -07:00
9401aa669e
three active web-vuln probes wired into the per-target loop: - cors: crafts attacker origins (evil sentinel, null, prefix/suffix bypass, http downgrade) and flags responses that reflect them in access-control-allow-origin, ranking reflection+credentials high. - redirect: injects a controlled sentinel host plus bypass variants (//, https:/, backslash, null-byte, userinfo @) into redirect-prone params and catches 30x location, meta-refresh and js redirects that resolve off-site. - xss: injects a unique canary wrapped in breaking chars, classifies the reflection context (html/attribute/script) and reports only the chars that survive unescaped where they matter, so escaped reflections don't false-positive. all route through httpx.Client so proxy/-H/-cookie/-rate-limit apply. hermetic httptest coverage plus integration testbed entries.
141 lines
4.5 KiB
Go
141 lines
4.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"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// reflectingCORS echoes the Origin into Access-Control-Allow-Origin and sets
|
|
// credentials, the exploitable misconfiguration.
|
|
func reflectingCORS() *httptest.Server {
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if origin := r.Header.Get("Origin"); origin != "" {
|
|
w.Header().Set("Access-Control-Allow-Origin", origin)
|
|
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
}
|
|
|
|
func TestCORS_ReflectsArbitraryOrigin(t *testing.T) {
|
|
srv := reflectingCORS()
|
|
defer srv.Close()
|
|
|
|
result, err := CORS(srv.URL, 5*time.Second, 3, "")
|
|
if err != nil {
|
|
t.Fatalf("CORS: %v", err)
|
|
}
|
|
if result == nil || len(result.Findings) == 0 {
|
|
t.Fatalf("expected cors findings on reflecting server, got %+v", result)
|
|
}
|
|
|
|
// the reflecting server echoes every crafted origin with credentials,
|
|
// so each finding should be high severity.
|
|
var sawEvil bool
|
|
for _, f := range result.Findings {
|
|
if f.OriginTested == corsEvilOrigin {
|
|
sawEvil = true
|
|
if !f.AllowCredentials {
|
|
t.Errorf("expected credentials flagged for evil origin, got %+v", f)
|
|
}
|
|
if f.Severity != "high" {
|
|
t.Errorf("expected high severity for reflection+creds, got %s", f.Severity)
|
|
}
|
|
}
|
|
}
|
|
if !sawEvil {
|
|
t.Errorf("expected the sentinel evil origin to be reflected, got %+v", result.Findings)
|
|
}
|
|
}
|
|
|
|
func TestCORS_SeverityWithoutCredentials(t *testing.T) {
|
|
// reflects the origin but never grants credentials - medium, not high.
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if origin := r.Header.Get("Origin"); origin != "" {
|
|
w.Header().Set("Access-Control-Allow-Origin", origin)
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
result, err := CORS(srv.URL, 5*time.Second, 3, "")
|
|
if err != nil {
|
|
t.Fatalf("CORS: %v", err)
|
|
}
|
|
if result == nil || len(result.Findings) == 0 {
|
|
t.Fatalf("expected reflection findings, got %+v", result)
|
|
}
|
|
for _, f := range result.Findings {
|
|
if f.AllowCredentials {
|
|
t.Errorf("did not expect credentials, got %+v", f)
|
|
}
|
|
if f.Severity != "medium" {
|
|
t.Errorf("expected medium severity without creds, got %s", f.Severity)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCORS_NoFalsePositiveOnSafeServer(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
handler http.HandlerFunc
|
|
}{
|
|
{
|
|
name: "ignores origin entirely",
|
|
handler: func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
},
|
|
},
|
|
{
|
|
name: "returns its own fixed origin",
|
|
handler: func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "https://trusted.example.com")
|
|
w.WriteHeader(http.StatusOK)
|
|
},
|
|
},
|
|
{
|
|
name: "plain wildcard, no credentials",
|
|
handler: func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.WriteHeader(http.StatusOK)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
srv := httptest.NewServer(tt.handler)
|
|
defer srv.Close()
|
|
|
|
result, err := CORS(srv.URL, 5*time.Second, 3, "")
|
|
if err != nil {
|
|
t.Fatalf("CORS: %v", err)
|
|
}
|
|
if result != nil && len(result.Findings) > 0 {
|
|
t.Errorf("expected no findings on safe server, got %+v", result.Findings)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCORSResult_ResultType(t *testing.T) {
|
|
r := &CORSResult{}
|
|
if r.ResultType() != "cors" {
|
|
t.Errorf("expected result type 'cors', got %q", r.ResultType())
|
|
}
|
|
}
|