package search import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" ) // TestSearch_MaliciousInputDoesNotAlterFieldNames ist die geforderte // Pflichtprüfung 2: eine Abfrage mit manipulierten Eingabewerten // (SQL-/Injection-artige Zeichen in tenantSlug und queryText) darf keine // Spalten-/Tabellennamen in der an Manticore gesendeten Anfrage verändern // — Werte landen ausschließlich als JSON-String-Werte, niemals als // Feld-/Tabellenname. func TestSearch_MaliciousInputDoesNotAlterFieldNames(t *testing.T) { var captured map[string]any srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&captured); err != nil { t.Fatal(err) } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"hits":{"hits":[]}}`)) })) defer srv.Close() client := NewClient(srv.URL) maliciousTenant := `acme"; DROP TABLE mail_documents; --` maliciousQuery := `x' OR '1'='1` if _, err := client.Search(context.Background(), maliciousTenant, maliciousQuery); err != nil { t.Fatalf("search: %v", err) } query, ok := captured["query"].(map[string]any) if !ok { t.Fatal("erwartetes 'query'-Objekt fehlt in gesendetem Payload") } boolQuery, ok := query["bool"].(map[string]any) if !ok { t.Fatal("erwartetes 'bool'-Objekt fehlt") } must, ok := boolQuery["must"].([]any) if !ok || len(must) != 2 { t.Fatal("erwartete 'must'-Liste mit 2 Klauseln fehlt") } equalsClause, ok := must[0].(map[string]any)["equals"].(map[string]any) if !ok { t.Fatal("erwartete 'equals'-Klausel fehlt") } // Feldname bleibt statisch "tenant_slug" — nur der Wert enthält die // böswillige Eingabe, unverändert als String. if _, hasStaticField := equalsClause[FieldTenantSlug]; !hasStaticField { t.Fatalf("erwartetes statisches Feld %q nicht gefunden, habe: %v", FieldTenantSlug, equalsClause) } if equalsClause[FieldTenantSlug] != maliciousTenant { t.Fatalf("wert wurde verändert: %v", equalsClause[FieldTenantSlug]) } // query_string hat keinerlei dynamischen Feldnamen — der Klausel-Wert // ist unmittelbar der übergebene String, keine map mit datenabhängigem // Schlüssel möglich. queryStringClause, ok := must[1].(map[string]any)["query_string"] if !ok { t.Fatal("erwartete 'query_string'-Klausel fehlt") } if queryStringClause != maliciousQuery { t.Fatalf("suchwert wurde verändert: %v", queryStringClause) } }