package netguard import ( "context" "net/netip" "strings" "testing" ) // TestBlocksInternalTargets ist der Kerntest gegen SSRF. // // Jede dieser Adressen war vor Phase 19 als Ziel eines Benachrichtigungswegs // zulaessig — real gegen die laufende Anlage nachgewiesen. Der gefaehrlichste // Fall steht ganz oben: 169.254.169.254 ist der Metadatendienst der Cloud und // antwortet mit Zugangsdaten. func TestBlocksInternalTargets(testInstance *testing.T) { addressGuard := NewGuard(false) blockedTargets := []string{ "https://169.254.169.254/latest/meta-data/", "https://127.0.0.1:5432/", "http://localhost:8080/api/v1/users", "https://[::1]:8080/", "https://10.0.0.1/internal", "https://192.168.1.1/router", "https://172.16.0.5/intern", "https://0.0.0.0/", "https://[fe80::1]/", } for _, target := range blockedTargets { if guardError := addressGuard.CheckURL(context.Background(), target); guardError == nil { testInstance.Errorf("die interne Zieladresse %q wurde zugelassen", target) } } } // TestBlocksIPv6MappedIPv4 prueft die naheliegendste Umgehung. // // ::ffff:127.0.0.1 ist dieselbe Adresse wie 127.0.0.1, nur anders geschrieben. // Ohne die Entpackung in checkAddress fuehrt sie an jeder IPv4-Sperre vorbei — // und wer eine Sperrliste umgehen will, probiert genau das als Erstes. func TestBlocksIPv6MappedIPv4(testInstance *testing.T) { addressGuard := NewGuard(false) mappedTargets := []string{ "https://[::ffff:127.0.0.1]/umgehung", "https://[::ffff:169.254.169.254]/meta-data", "https://[::ffff:10.0.0.1]/intern", } for _, target := range mappedTargets { if guardError := addressGuard.CheckURL(context.Background(), target); guardError == nil { testInstance.Errorf("die eingebettete IPv4-Adresse %q wurde zugelassen", target) } } } // TestAllowsPublicTargets prueft die Gegenprobe. // // Ein Schutz, der auch die legitimen Ziele sperrt, wird abgeschaltet — und dann // steht die Anlage wieder offen. Der Test benutzt feste Adressen statt einer // Namensaufloesung: Ein Test, der das Netz braucht, ist kein Test. func TestAllowsPublicTargets(testInstance *testing.T) { addressGuard := NewGuard(false) publicTargets := []string{ "https://93.184.216.34/hook", "https://1.1.1.1/", "https://[2606:4700:4700::1111]/", } for _, target := range publicTargets { if guardError := addressGuard.CheckURL(context.Background(), target); guardError != nil { testInstance.Errorf("die öffentliche Zieladresse %q wurde gesperrt: %v", target, guardError) } } } // TestResolvedNamesAreCheckedCompletely prueft die Aufloesung mehrerer Adressen. // // Ein Name kann auf mehrere Adressen zeigen. Wird nur die erste geprueft, // genuegt ein Eintrag mit einer oeffentlichen und einer internen Adresse, um // den Schutz zu umgehen — der Verbindungsaufbau nimmt nicht zwangslaeufig // dieselbe, die die Pruefung angesehen hat. func TestResolvedNamesAreCheckedCompletely(testInstance *testing.T) { addressGuard := NewGuard(false) // Die Aufloesung wird ersetzt: erst eine oeffentliche, dann eine interne. addressGuard.resolveHost = func(context.Context, string) ([]netip.Addr, error) { return []netip.Addr{ netip.MustParseAddr("93.184.216.34"), netip.MustParseAddr("127.0.0.1"), }, nil } guardError := addressGuard.CheckURL(context.Background(), "https://zweideutig.example/hook") if guardError == nil { testInstance.Fatal("ein Name mit einer internen Zweitadresse wurde zugelassen") } if !IsBlocked(guardError) { testInstance.Errorf("der Fehler ist keine Sperre: %v", guardError) } } // TestAllowInternalTargetsOpensTheGate prueft den ausdruecklichen Ausweg. // // Wer einen Meldedienst im eigenen Netz betreibt, braucht ihn. Er steht in der // Konfiguration und heisst, was er tut. func TestAllowInternalTargetsOpensTheGate(testInstance *testing.T) { permissiveGuard := NewGuard(true) if guardError := permissiveGuard.CheckURL(context.Background(), "https://127.0.0.1:9000/hook"); guardError != nil { testInstance.Errorf("der Ausweg wirkt nicht: %v", guardError) } } // TestBlockedErrorNamesTheReason prueft die Fehlermeldung. // // „Gesperrt" allein hilft niemandem: Der Betreiber muss erfahren, **warum** — // sonst haelt er den Schutz fuer eine Fehlfunktion und schaltet ihn ab. func TestBlockedErrorNamesTheReason(testInstance *testing.T) { addressGuard := NewGuard(false) guardError := addressGuard.CheckURL(context.Background(), "https://169.254.169.254/") if guardError == nil { testInstance.Fatal("die Adresse wurde zugelassen") } if !strings.Contains(guardError.Error(), "Metadatendienst") { testInstance.Errorf("die Meldung nennt den Grund nicht: %v", guardError) } }