diff --git a/internal/modules/directory_listing_exposure_test.go b/internal/modules/directory_listing_exposure_test.go new file mode 100644 index 0000000..ab896a1 --- /dev/null +++ b/internal/modules/directory_listing_exposure_test.go @@ -0,0 +1,240 @@ +package modules_test + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/vmfunc/sif/internal/modules" +) + +func runDirectoryListingModule(t *testing.T, file string, status int, body string) *modules.Result { + t.Helper() + def, err := modules.ParseYAMLModule(file) + if err != nil { + t.Fatalf("parse %s: %v", file, err) + } + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(status) + _, _ = w.Write([]byte(body)) + })) + defer srv.Close() + + res, err := modules.ExecuteHTTPModule(context.Background(), srv.URL, def, modules.Options{ + Timeout: 5 * time.Second, + Threads: 2, + }) + if err != nil { + t.Fatalf("execute %s: %v", file, err) + } + return res +} + +func TestDirectoryListingExposureModule(t *testing.T) { + const dl = "../../modules/recon/directory-listing-exposure.yaml" + + t.Run("a real nginx autoindex page is flagged", func(t *testing.T) { + body := ` +Index of /uploads/ + +

Index of /uploads/


../
+invoice-2024.pdf            03-Jul-2026 09:12  842311
+dump.sql                     03-Jul-2026 09:14   19204
+

+` + res := runDirectoryListingModule(t, dl, 200, body) + if len(res.Findings) == 0 { + t.Fatal("expected a finding for a real nginx autoindex page") + } + }) + + t.Run("a real apache mod_autoindex page is flagged", func(t *testing.T) { + body := ` + + Index of /backup + + +

Index of /backup

+ + + + +
[ICO]NameLast modifiedSize
[PARENTDIR]Parent Directory  -
[   ]site-backup.tar.gz03-Jul-2026 09:10 41M
+
Apache/2.4.52 (Ubuntu) Server at example.com Port 80
+` + res := runDirectoryListingModule(t, dl, 200, body) + if len(res.Findings) == 0 { + t.Fatal("expected a finding for a real apache mod_autoindex page") + } + }) + + t.Run("a real python http.server listing is flagged", func(t *testing.T) { + body := ` + + + +Directory listing for /files/ + + +

Directory listing for /files/

+
+ +
+ +` + res := runDirectoryListingModule(t, dl, 200, body) + if len(res.Findings) == 0 { + t.Fatal("expected a finding for a real python http.server listing") + } + }) + + t.Run("a real IIS directory browsing page is flagged", func(t *testing.T) { + body := ` +files.example.com - /data/ + +

files.example.com - /data/


+
[To Parent Directory]

+7/3/2026 9:12 AM <dir> archive
+7/3/2026 9:14 AM 19204 dump.sql
+
+
+ +` + res := runDirectoryListingModule(t, dl, 200, body) + if len(res.Findings) == 0 { + t.Fatal("expected a finding for a real IIS directory browsing page") + } + }) + + t.Run("a real caddy file_server browse page is flagged", func(t *testing.T) { + body := ` + +/media/ + +
+
+ +

/media/

+
+
+
+
+ +
+
+ +` + res := runDirectoryListingModule(t, dl, 200, body) + if len(res.Findings) == 0 { + t.Fatal("expected a finding for a real caddy file_server browse page") + } + }) + + t.Run("a real apache non-fancy (plain) mod_autoindex page is flagged", func(t *testing.T) { + // IndexOptions without FancyIndexing: a