diff --git a/internal/modules/zabbix_api_exposure_test.go b/internal/modules/zabbix_api_exposure_test.go new file mode 100644 index 00000000..95db3082 --- /dev/null +++ b/internal/modules/zabbix_api_exposure_test.go @@ -0,0 +1,84 @@ +/* +·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━· +: : +: █▀ █ █▀▀ · Blazing-fast pentesting suite : +: ▄█ █ █▀ · BSD 3-Clause License : +: : +: (c) 2022-2026 vmfunc, xyzeva, : +: lunchcat alumni & contributors : +: : +·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━· +*/ + +package modules_test + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/vmfunc/sif/internal/modules" +) + +// zabbix-api-exposure fires on a target that answers apiinfo.version with a +// dotted-version json-rpc result. the discriminator is the result shape, not the +// content-type; the module explains why. a service that speaks json-rpc but +// returns something other than a version must stay silent. +func TestZabbixAPIExposureModule(t *testing.T) { + const mod = "../../modules/recon/zabbix-api-exposure.yaml" + def, err := modules.ParseYAMLModule(mod) + if err != nil { + t.Fatalf("parse: %v", err) + } + + run := func(contentType, body string) *modules.Result { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", contentType) + w.WriteHeader(http.StatusOK) + _, _ = 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: %v", err) + } + return res + } + + // what a live zabbix actually puts on the wire. + fire := run("application/json", `{"jsonrpc":"2.0","result":"7.0.5","id":1}`) + if len(fire.Findings) == 0 { + t.Error("fire-on-real failed: exposed apiinfo.version not detected") + } + var version string + for _, f := range fire.Findings { + if v := f.Extracted["zabbix_api_version"]; v != "" { + version = v + } + } + if version != "7.0.5" { + t.Errorf("version extraction: got %q, want 7.0.5", version) + } + + // key order is not guaranteed; a reordered body must still match. + if res := run("application/json", `{"id":1,"jsonrpc":"2.0","result":"7.0.5"}`); len(res.Findings) == 0 { + t.Error("fire-on-reordered-keys failed: exposed apiinfo.version not detected") + } + + // older deployments and proxies that echo the request type back still fire. + if res := run("application/json-rpc", `{"jsonrpc":"2.0","result":"7.0.5","id":1}`); len(res.Findings) == 0 { + t.Error("fire-on-json-rpc-content-type failed: exposed apiinfo.version not detected") + } + + if res := run("application/json", `{"jsonrpc":"2.0","result":"pong","id":1}`); len(res.Findings) != 0 { + t.Errorf("silent-on-foreign-jsonrpc failed: %d findings for a non-version result", len(res.Findings)) + } + + // a json-rpc error (auth required, method not found) is not a version leak. + if res := run("application/json", `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params."},"id":1}`); len(res.Findings) != 0 { + t.Errorf("silent-on-jsonrpc-error failed: %d findings", len(res.Findings)) + } +} diff --git a/modules/info/icinga-panel.yaml b/modules/info/icinga-panel.yaml new file mode 100644 index 00000000..7409d6a9 --- /dev/null +++ b/modules/info/icinga-panel.yaml @@ -0,0 +1,28 @@ +id: icinga-panel +info: + name: Icinga Web 2 Login Panel + author: sif + severity: info + description: Detects exposed Icinga Web 2 monitoring frontend login panels + tags: [icinga, monitoring, panel, login, detection, info] + +type: http + +http: + method: GET + paths: + - "{{BaseURL}}/icingaweb2/authentication/login" + - "{{BaseURL}}/authentication/login" + - "{{BaseURL}}" + + matchers: + - type: status + status: + - 200 + + - type: word + part: body + condition: and + words: + - 'id="icinga-logo"' + - 'class="login-form"' diff --git a/modules/info/zabbix-panel.yaml b/modules/info/zabbix-panel.yaml new file mode 100644 index 00000000..b069e9f1 --- /dev/null +++ b/modules/info/zabbix-panel.yaml @@ -0,0 +1,36 @@ +id: zabbix-panel +info: + name: Zabbix Login Panel + author: sif + severity: info + description: Detects exposed Zabbix monitoring frontend login panels + tags: [zabbix, monitoring, panel, login, detection, info] + +type: http + +http: + method: GET + paths: + - "{{BaseURL}}/index.php" + - "{{BaseURL}}/zabbix/index.php" + - "{{BaseURL}}" + + matchers: + - type: status + status: + - 200 + + - type: word + part: body + condition: and + words: + - 'class="signin-container"' + - 'class="signin-logo"' + + extractors: + - type: regex + name: zabbix_server_name + part: body + regex: + - '