mirror of
https://github.com/lunchcat/sif.git
synced 2026-07-28 14:37:01 -07:00
feat(modules): add disable-redirects http option (#273)
A module that fingerprints a redirect itself (open-redirect proofs, a Location header, a 3xx status) could not see the redirect: the executor followed 3xx to the final response before matchers ran, with no way to stop. Add an opt-in disable-redirects field that scopes an ErrUseLastResponse policy to the module's own client copy, so the shared httpx transport keeps following redirects for every other module.
This commit is contained in:
@@ -72,6 +72,18 @@ func ExecuteHTTPModule(ctx context.Context, target string, def *YAMLModule, opts
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// disable-redirects only applies to this module's requests; opts.Client may
|
||||||
|
// be the shared httpx client reused by every other module in the run, so a
|
||||||
|
// module-scoped policy shallow-copies it (keeping the pooled Transport)
|
||||||
|
// rather than mutating CheckRedirect on the shared instance.
|
||||||
|
if cfg.DisableRedirects {
|
||||||
|
scoped := *client
|
||||||
|
scoped.CheckRedirect = func(_ *http.Request, _ []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}
|
||||||
|
client = &scoped
|
||||||
|
}
|
||||||
|
|
||||||
// Generate requests based on paths and payloads
|
// Generate requests based on paths and payloads
|
||||||
requests, err := generateHTTPRequests(target, cfg)
|
requests, err := generateHTTPRequests(target, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||||
|
: :
|
||||||
|
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||||
|
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||||
|
: :
|
||||||
|
: (c) 2022-2026 vmfunc, xyzeva, :
|
||||||
|
: lunchcat alumni & contributors :
|
||||||
|
: :
|
||||||
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||||
|
*/
|
||||||
|
|
||||||
|
package modules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmfunc/sif/internal/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// a target whose root 302-redirects to a landing page: the redirect response
|
||||||
|
// carries the signal (a Location header, the 302 status), the landing page does
|
||||||
|
// not. matching that signal requires stopping at the 3xx.
|
||||||
|
func redirectServer() *httptest.Server {
|
||||||
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/landing" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte("landed"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Location", "/landing")
|
||||||
|
w.WriteHeader(http.StatusFound)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecuteHTTPModuleDisableRedirects(t *testing.T) {
|
||||||
|
srv := redirectServer()
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
mod := func(disable bool) *YAMLModule {
|
||||||
|
return &YAMLModule{
|
||||||
|
ID: "redirect",
|
||||||
|
Type: TypeHTTP,
|
||||||
|
HTTP: &HTTPConfig{
|
||||||
|
Paths: []string{"{{BaseURL}}/"},
|
||||||
|
DisableRedirects: disable,
|
||||||
|
Matchers: []Matcher{
|
||||||
|
{Type: "status", Status: []int{http.StatusFound}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts := Options{Timeout: testTimeout, Client: httpx.Client(testTimeout)}
|
||||||
|
|
||||||
|
followed, err := ExecuteHTTPModule(context.Background(), srv.URL, mod(false), opts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExecuteHTTPModule(follow): %v", err)
|
||||||
|
}
|
||||||
|
if len(followed.Findings) != 0 {
|
||||||
|
t.Fatalf("with redirects followed, got %d findings matching 302, want 0", len(followed.Findings))
|
||||||
|
}
|
||||||
|
|
||||||
|
stopped, err := ExecuteHTTPModule(context.Background(), srv.URL, mod(true), opts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExecuteHTTPModule(no-follow): %v", err)
|
||||||
|
}
|
||||||
|
if len(stopped.Findings) != 1 {
|
||||||
|
t.Fatalf("with redirects disabled, got %d findings matching 302, want 1", len(stopped.Findings))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,6 +61,7 @@ type HTTPConfig struct {
|
|||||||
Body string `yaml:"body,omitempty"`
|
Body string `yaml:"body,omitempty"`
|
||||||
Attack string `yaml:"attack,omitempty"` // clusterbomb (default), pitchfork
|
Attack string `yaml:"attack,omitempty"` // clusterbomb (default), pitchfork
|
||||||
Threads int `yaml:"threads,omitempty"`
|
Threads int `yaml:"threads,omitempty"`
|
||||||
|
DisableRedirects bool `yaml:"disable-redirects,omitempty"` // stop at the first response; don't follow 3xx
|
||||||
Matchers []Matcher `yaml:"matchers"`
|
Matchers []Matcher `yaml:"matchers"`
|
||||||
MatchersCondition string `yaml:"matchers-condition,omitempty"` // and (default), or
|
MatchersCondition string `yaml:"matchers-condition,omitempty"` // and (default), or
|
||||||
Extractors []Extractor `yaml:"extractors,omitempty"`
|
Extractors []Extractor `yaml:"extractors,omitempty"`
|
||||||
|
|||||||
Reference in New Issue
Block a user