8 Min. Lesezeit

SSRF-Tests für Agenten-Tools, die Anmeldedaten einfügen

SSRF-Tests für Agenten-Tools brauchen eine Zielmatrix, die Loopback, private Bereiche, Rebinding, Weiterleitungen und IPv6 vor der Authentifizierung erfasst.

SSRF-Tests für Agenten-Tools, die Anmeldedaten einfügen

Ein Agenten-Tool, das Anmeldedaten einfügt, macht aus gewöhnlichem ausgehendem HTTP eine Grenzentscheidung. Wenn das Tool ein Ziel vom Agenten übernimmt, einen harmlos wirkenden Hostnamen prüft und seinen HTTP-Client anschließend überallhin gehen lässt, wohin DNS und Weiterleitungen führen, hat es seine Anmeldedaten an einen vom Angreifer ausgewählten Dienst übergeben.

Die Lösung ist keine längere Denylist, die in einen URL-Validator kopiert wird. Du brauchst eine Zielmatrix, die den gesamten Anfragelebenszyklus abdeckt: Parsen, DNS-Auflösung, Adressauswahl, Verbindungsaufbau, Weiterleitungsverarbeitung und erst danach das Einfügen der Anmeldedaten. Der Test muss den Peer beobachten, der die Anfrage empfangen hat, denn dorthin ist das Geheimnis gelangt.

Anmeldedaten erst nach der Zielprüfung einfügen

Ein Tool sollte entscheiden, ob es ein Ziel kontaktieren darf, bevor es Authentifizierungsdaten anhängt. Dazu gehören Bearer-Tokens, Zugangsdaten für Basic Authentication, eigene Header, Signaturen von Anfragen, Client-Zertifikate und SSH-Identitäten. Sie alle gehören auf die andere Seite der Zielprüfung.

Teams platzieren diese Prüfung oft an der falschen Stelle. Sie validieren den ursprünglichen URL-String, erstellen eine Anfrage mit Authorization-Header und rufen einen bequemen HTTP-Helfer mit aktivierter Weiterleitungsfunktion auf. Der Helfer löst dann Namen auf, wählt IPv6 oder IPv4, folgt einem Location-Header und verwendet möglicherweise Header oder Verbindungszustände auf eine Weise wieder, die der Validierungscode nie gesehen hat.

Dieses Design hat eine praktische Eigenschaft: Es lässt sich leicht schreiben. Die gefährliche Entscheidung wird dadurch aber einer Komponente überlassen, die nicht weiß, ob die Anfrage ein Geheimnis trägt.

Halte zwei Entscheidungen getrennt:

  • Ist diese URL syntaktisch für das Tool zulässig?
  • Ist dieses konkrete Netzwerkziel für eine authentifizierte Verbindung freigegeben?

Bei der ersten Entscheidung werden Schema, Host, Port, Pfad, Userinfo und Prozentkodierung geparst. Bei der zweiten werden aufgelöste Adressen klassifiziert und die tatsächliche Verbindung kontrolliert. Eine Host-Allowlist kann beide Entscheidungen unterstützen, die zweite aber nicht ersetzen.

RFC 3986 macht das Parsing-Problem deutlich. Die Authority-Komponente hat die Form [userinfo@]host[:port]. Ein String, der vor dem Zeichen @ scheinbar einen vertrauenswürdigen Hostnamen enthält, kann trotzdem die IP-Adresse dahinter benennen. RFC 3986 verwendet diese Konstruktion sogar bei der Beschreibung irreführender URI-Authority-Strings. Zerlege URLs nicht selbst und suche im Rohstring nicht nach einem vertrauenswürdigen Namen. Verwende einen Parser, der den Standard kennt, lehne Userinfo bei Zielen ab, wenn das Tool sie nicht wirklich braucht, und lies das geparste Host-Feld aus.

Die Sicherheitsinvariante lässt sich einfach testen:

Das Tool darf kein authentifiziertes Byte senden, bevor es den verbundenen Peer und die Request-Authority für diese Anfrage freigegeben hat.

«Authentifiziertes Byte» umfasst eine Request-Zeile oder einen Host-Header nur dann, wenn diese Werte selbst ein Geheimnis offenlegen. In den meisten HTTP-Fällen liegt die entscheidende Grenze beim Header mit den Anmeldedaten oder bei einer signierten Nutzlast. Sei strenger, wenn Pfad, Query-String oder Body eine Capability-URL, eine Mandantenkennung oder einen anderen sensiblen Wert enthalten.

Lege den Entscheidungspunkt fest, bevor du Testfälle schreibst

Du kannst eine SSRF-Kontrolle erst testen, wenn du den genauen Punkt benennen kannst, an dem sie eine Anfrage erlaubt oder ablehnt. «Vor der Anfrage» ist zu ungenau. Eine Anfrage hat mehrere Momente, in denen sich das Ziel ändern kann.

Verwende für ein HTTP-Tool, das Anmeldedaten einfügt, diese Abfolge als Modell:

  1. Parse die übergebene absolute URL und lehne nicht unterstützte Schemas, fehlerhafte Authorities, Userinfo und Ports außerhalb des Vertrags des Tools ab.
  2. Löse den Hostnamen über den Resolver auf, den der Prozess tatsächlich verwendet. Sammle A- und AAAA-Antworten, nicht nur die Antwort, die deine Entwicklungsmaschine zufällig bevorzugt.
  3. Klassifiziere jede mögliche Adresse. Lehne die Anfrage ab, wenn die Menge eine Adresse außerhalb der erlaubten Zielklassen enthält, außer der Connector kann sich an eine erlaubte Adresse binden.
  4. Öffne eine Verbindung zu einer erlaubten Adresse und prüfe die Adresse des entfernten Peers, bevor du die Anmeldedaten schreibst.
  5. Erstelle die authentifizierte Anfrage erst nach dieser Prüfung. Setze den vorgesehenen Host-Header und den TLS-Servernamen bewusst.
  6. Behandle eine Weiterleitung als neue Anfrage, die wieder beim Parsen beginnt, nicht als Fortsetzung mit geerbtem Vertrauen.

Das ist anspruchsvoller als hostname != "localhost" zu prüfen. Das muss es sein. RFC 8305 beschreibt, dass Clients AAAA- und A-Abfragen zeitnah ausführen und Adressen mit Blick auf IPv6-Präferenzen ausprobieren. Eine Testsuite, die nur A-Einträge auflöst, kann Code absegnen, der beim ersten erreichbaren AAAA-Eintrag versagt.

Der Entscheidungspunkt bestimmt auch, was dein Test-Harness protokollieren muss. Erfasse für jeden Versuch:

  • die vom Tool empfangene URL
  • die an das Tool zurückgegebenen DNS-Antworten
  • die für die Verbindung ausgewählte Adresse
  • die vom empfangenden Server beobachtete Adresse
  • jeden Anfrage-Header, wobei Test-Anmeldedaten in Berichten redigiert werden

Verwende keinen Test, der nur einen Statuscode oder eine ausgelöste Ausnahme prüft. Ein 403 von deinem Erfassungsserver beweist, dass er die Anfrage erhalten hat. Ein Timeout beweist möglicherweise gar nichts. Die gewünschte Aussage lautet: «Der blockierte Listener hat keine Anfrage mit Anmeldedaten beobachtet.»

Baue die Matrix um Ziele, nicht um Hostnamen herum

Eine Zielmatrix ist das dauerhafte Artefakt für diese Arbeit. Jede Zeile nennt eine Eingabeform, die Resolver-Antwort, ein optionales Weiterleitungsverhalten und das erwartete Ergebnis. So verhinderst du die übliche Lücke, bei der eine Person 127.0.0.1 testet, eine andere 10.0.0.1 und niemand die ungewöhnlichen Formen prüft, die die Laufzeit akzeptiert.

Beginne mit einer kleinen Matrix, die ein echtes Prüfkriterium besitzt. Das folgende Beispiel geht davon aus, dass public.test auf einen als erlaubt klassifizierten Listener im Test-Harness zeigt, während jede andere Adressklasse vor dem Einfügen von Anmeldedaten abgelehnt werden muss.

FallÜbergebene URLDNS- oder WeiterleitungsverhaltenErwartetes Ergebnis
Öffentliches IPv4https://public.test/echoA-Eintrag zum öffentlichen Harness-TestzielErlauben und Test-Anmeldedaten senden
IPv4-Loopbackhttp://127.0.0.1:18080/Literale AdresseVor dem Verbindungsaufbau ablehnen
IPv4-Loopback-Variantehttp://127.1:18080/Von Parser abhängige KurzformAblehnen oder als Loopback klassifizieren, niemals erlauben
Privates RFC 1918http://10.20.30.40/Literale AdresseVor dem Verbindungsaufbau ablehnen
Privates RFC 1918http://172.20.30.40/Literale AdresseVor dem Verbindungsaufbau ablehnen
Privates RFC 1918http://192.168.20.40/Literale AdresseVor dem Verbindungsaufbau ablehnen
IPv6-Loopbackhttp://[::1]:18080/Literale AdresseVor dem Verbindungsaufbau ablehnen
IPv6-Link-Localhttp://[fe80::1]/Literale AdresseVor dem Verbindungsaufbau ablehnen
IPv4-abgebildetes IPv6http://[::ffff:127.0.0.1]/Literale AdresseVor dem Verbindungsaufbau ablehnen
Gemischte DNS-Antworthttps://mixed.test/Erlaubtes A, blockiertes AAAAAblehnen oder Verbindung an erlaubte Adresse binden
Rebinding-Namehttps://rebind.test/Zuerst erlaubt, danach blockiertAuthentifizierte Anfrage an blockierten Peer ablehnen
Weiterleitung zu Loopbackhttps://public.test/to-local302 zu http://127.0.0.1:18080/Zweite Anfrage ablehnen
Weiterleitung zu privatem DNShttps://public.test/to-private302 zu https://internal.test/Zweite Anfrage auflösen und ablehnen
Täuschung durch Userinfohttp://[email protected]/Literaler Host hinter @Vor dem Verbindungsaufbau ablehnen

RFC 1918 nennt nur drei private IPv4-Blöcke: 10/8, 172.16/12 und 192.168/16. Teste die Grenzen, besonders 172.15.255.255, 172.16.0.0, 172.31.255.255 und 172.32.0.0. Eine ungenaue Präfixprüfung blockiert häufig den gesamten Bereich 172/8 und damit legitime öffentliche Endpunkte. Oder sie blockiert nur 172.16/16 und übersieht den größten Teil des zugewiesenen privaten Bereichs.

Verwechsle diese Tabelle nicht mit einer universellen Produktionsrichtlinie. Manche internen Tools müssen einen bestimmten privaten Dienst erreichen dürfen. Wenn das deine Produktanforderung ist, gib diesem Dienst eine ausdrückliche Regel mit engem Hostnamen, Port und Identitätsprüfung. Mache «private Adressen sind in unserem Büro in Ordnung» nicht zum Standard für jedes vom Agenten ausgewählte Ziel.

URL-Parsing-Tests erkennen Fehler vor DNS

Der Parser muss den Host identifizieren, den der Connector verwenden wird. Das klingt selbstverständlich, bis ein Test zeigt, dass eine Komponente die Eingabe anders normalisiert als eine andere.

Nimm diese Fälle in eine reine Parser-Suite auf. Sie müssen ohne DNS und ohne Netzwerk-Socket ausgeführt werden:

http://[email protected]/admin
http://127.0.0.1.nip.example/
http://[::1]/
http://[::ffff:7f00:1]/
http://0177.0.0.1/
http://2130706433/
http://127.0.0.1%2f.example/
http://public.test:[email protected]/
http://public.test./

Das erwartete Ergebnis lautet nicht immer «Diese exakte Zeichenfolge muss als Loopback geparst werden». URI- und URL-Bibliotheken unterscheiden sich bei älteren numerischen IPv4-Formen, Zonenkennungen und ungültigen Prozentkodierungen. Dein Test sollte stattdessen die Sicherheitseigenschaft festhalten: Wenn die Laufzeit die Eingabe akzeptiert und eine Verbindung zu einer blockierten Adresse herstellen würde, muss dein Tool sie ablehnen. Wenn die Laufzeit sie ablehnt, ist das in Ordnung.

Diese Unterscheidung ist wichtig, weil viele Teams zuerst einen eigenen Host-Filter schreiben und die unveränderte URL anschließend an eine andere Bibliothek übergeben. Der Filter kann 2130706433 als unbekannten registrierten Namen ansehen, während der Connector den Wert als 127.0.0.1 interpretiert. Damit hast du zwei Parser getestet und dem sichereren Ergebnis vertraut.

Verwende dieselbe geparste Darstellung für Validierung und Verbindung. Wenn das nicht möglich ist, muss der endgültige Connector den von ihm ausgewählten numerischen Peer melden. Führe anschließend eine letzte Prüfung der Adressklasse durch, bevor Anmeldedaten den Prozess verlassen.

Teste außerdem die Normalisierung von Hostnamen. Ein abschließender Punkt in public.test. verweist bei gewöhnlicher DNS-Nutzung auf denselben DNS-Namen wie public.test, einfache Alllists vergleichen jedoch häufig die Rohstrings. Internationalisierte Hostnamen bieten eine weitere Stelle für Abweichungen. Wandle den Namen einmal in die kanonische Darstellung der Laufzeit um und vergleiche DNS-Namen dann an Label-Grenzen. Eine Suffixprüfung wie endsWith("trusted.example") akzeptiert untrusted.example und trusted.example.attacker.test. Keiner der beiden Namen ist eine vertrauenswürdige Subdomain.

DNS-Rebinding-Tests müssen die zweite Auflösung erzwingen

Sensible Anmeldedaten absichern
Markiere einen Schlüssel für die Freigabe pro Aufruf, wenn jede Verwendung eine menschliche Entscheidung erfordert.

DNS-Rebinding ist ein Timing-Fehler. Die erste Auflösung liefert eine zulässige Adresse. Eine spätere Auflösung für denselben Namen liefert Loopback, eine private Adresse oder ein anderes blockiertes Ziel. Wenn Validierung und Verbindung nicht dasselbe Auflösungsergebnis verwenden, kann ein Angreifer die erste Prüfung bestehen und die zweite Verbindung umleiten.

Eine brauchbare Fixture braucht einen kleinen autoritativen DNS-Server unter deiner Kontrolle und zwei HTTP-Listener. Einer ist dein erlaubtes Testziel. Der andere ist ein blockiertes Testziel, das festhält, ob es den Authorization-Header des Tests erhalten hat. Gib dem DNS-Server für rebind.test folgenden Antwortplan:

query 1: rebind.test. A     198.51.100.20
query 2: rebind.test. A     127.0.0.1
query 1: rebind.test. AAAA  2001:db8::20
query 2: rebind.test. AAAA  ::1

Verwende ausschließlich Adressen für das Harness und ordne den erlaubten Listener im Testnetz nach Bedarf zu. Die öffentlich wirkenden Dokumentationsadressen in diesem Beispiel sind Bezeichnungen für die Fixture, keine Endpunkte, die der Test im Internet kontaktieren soll.

Führe nun zwei Varianten aus. In der ersten lässt du das Tool eine eigene Validierungsauflösung durchführen und rufst danach eine HTTP-Bibliothek auf, die Namen separat auflöst. Wenn die gesuchte Lücke vorhanden ist, sollte dein blockierter Listener die Anmeldedaten erhalten. In der zweiten konfigurierst du den Verbindungsweg so, dass er die genehmigte aufgelöste Adresse verwendet. Der ursprüngliche Hostname bleibt nur für Host- und TLS-Namensprüfung erhalten. Prüfe, dass der blockierte Listener nichts beobachtet.

Caching kann das Problem verdecken. Ein Resolver-Cache, Verbindungspool oder Betriebssystem-Cache kann die zweite Antwort in einen Nicht-Ereignis verwandeln. Das Test-Harness sollte Cache-Steuerung bieten, ungenutzte Verbindungen zwischen den Läufen schließen, bei Bedarf für jeden Lauf einen neuen Hostnamen verwenden und jede DNS-Abfrage protokollieren. Wenn ein Rebinding-Test besteht, ohne zwei unterschiedliche Auflösungen zu bestätigen, hat er kein Rebinding getestet.

Löse das Problem nicht, indem du einem DNS-TTL vertraust. Ein TTL ist ein Hinweis zum Caching, keine Aussage darüber, dass ein Name zwischen Validierung und Verbindung sicher bleibt. Der Code muss die genehmigte Adresse in die Verbindung übernehmen oder den Peer nach dem Öffnen des Sockets prüfen.

Weiterleitungen sind eigene Zielentscheidungen

Eine Weiterleitung ändert das Ziel. Sie kann Schema, Host, Port, Pfad oder alle vier Werte ändern. Wer sie als vertrauenswürdige Fortsetzung behandelt, macht aus einer scheinbar sicheren öffentlichen URL eine Anfrage an 127.0.0.1, einen Instanz-Metadatendienst, einen Router oder eine interne Steuerungsebene.

Deaktiviere die automatische Weiterleitungsverfolgung im Transport, den die Anfrage mit Anmeldedaten verwendet. Lies die Weiterleitungsantwort, setze ein angemessenes Weiterleitungslimit, parse den Location-Wert gemäß den URL-Auflösungsregeln der Bibliothek relativ zur aktuellen URL und führe die vollständige Zielentscheidung erneut aus.

Deine Weiterleitungs-Fixture sollte mindestens diese Fälle enthalten:

  • Eine öffentliche URL antwortet mit 302 auf ein IPv4-Loopback-Literal.
  • Eine öffentliche URL antwortet mit 307 auf einen Hostnamen, der zu einer privaten IPv4-Adresse aufgelöst wird.
  • Eine öffentliche URL liefert eine relative Location wie /next, die nach der normalen Auflösung bei der bereits genehmigten Authority bleiben sollte.
  • Eine öffentliche URL liefert eine schemarelative Location wie //other.test/path, die die Authority ändert und eine vollständige Prüfung braucht.
  • Eine öffentliche URL liefert eine Weiterleitungskette, in der der erste Sprung erlaubt und ein späterer Sprung blockiert ist.

Statuscodes spielen eine Rolle. 301, 302 oder 303 können Clients dazu bringen, eine Methode außer GET in GET zu ändern. 307 oder 308 sollen Methode und Body erhalten. Verlasse dich bei signierten POST-Anfragen oder Schreibvorgängen mit Bearer-Authentifizierung nicht auf das Standardverhalten einer Client-Bibliothek. Protokolliere bei jeder Weiterleitungs-Fixture Methode, Body-Hash, Host-Header und Authorization-Header.

Das Entfernen von Authorization bei einem Wechsel der Authority ist eine gute Schutzmaßnahme, macht die Zielvalidierung aber nicht überflüssig. Eine Anfrage ohne Authorization-Header kann weiterhin eine signierte URL im Pfad, ein Client-Zertifikat, Cookies oder einen sensiblen Body enthalten. Auch eine Weiterleitung zum selben Host kann von einem gewöhnlichen Pfad zu einem lokalen Dienst führen, wenn sich DNS zwischen den Sprüngen ändert.

Hier sollte ein Test bewusst langweilig sein. Lass den blockierten Weiterleitungs-Listener 200 zurückgeben. Wenn deine Prüfung nur einen Clientfehler erwartet, kann ein Client, der Weiterleitungen folgt, die SSRF-Anfrage erfolgreich ausführen. Dein Test würde sie dann aus dem falschen Grund als bestanden bewerten. Prüfe stattdessen die Anzahl der vom Listener erfassten Anfragen und den Zustand der erfassten Anmeldedaten.

IPv6 ist eine eigenständige SSRF-Angriffsfläche

Anmeldedaten vor Aktionen sperren
Sperre den Tresor mit Touch ID, dann wird jede Agentenaktion verweigert.

IPv6 wird meistens versehentlich ausgelassen. Ein Entwickler testet 127.0.0.1, blockiert RFC-1918-Bereiche und veröffentlicht Code, der [::1] als exotischen Hostnamen betrachtet. Moderne Clients können AAAA-Einträge zusammen mit A-Einträgen abfragen, und eine IPv6-Route kann gewinnen, obwohl die IPv4-Tests sauber aussehen.

RFC 4291 definiert ::1/128 als IPv6-Loopback-Adresse und ::/128 als nicht spezifizierte Adresse. Außerdem definiert es Link-Local-Unicast-Adressen unter fe80::/10. Diese Adressklassen dürfen nicht versehentlich zu einem vom Agenten ausgewählten authentifizierten Ziel werden.

Teste diese Kategorien ausdrücklich:

AdressklasseBeispielErwartung im Standardtest
Nicht spezifiziert[::]Ablehnen
Loopback[::1]Ablehnen
Link-Local[fe80::1]Ablehnen
Unique Local[fc00::1], [fd12:3456::1]Ablehnen, sofern nicht ausdrücklich erlaubt
Multicast[ff02::1]Ablehnen
IPv4-abgebildetes Loopback[::ffff:127.0.0.1]Ablehnen
IPv4-abgebildetes privates IPv4[::ffff:192.168.1.10]Ablehnen

Die Adressklassifizierung muss auf der binären Adresse arbeiten, nicht auf ihrer Schreibweise. Dieselbe Adresse kann in komprimierter oder ausgeschriebener IPv6-Form erscheinen. Ein Stringvergleich mit ::1 übersieht 0:0:0:0:0:0:0:1. Zuerst in einen echten IP-Typ zu parsen ist unspektakulär, verhindert aber diese Fehlerklasse.

Auch für Bereichskennungen brauchst du eine klare Regel. Eine Eingabe wie [fe80::1%25en0] enthält nach der Prozentkodierung eine Schnittstellenzone. Die meisten Tools sollten bereichsgebundene Literale aus Agenteneingaben ablehnen, statt entscheiden zu wollen, welche lokale Schnittstelle sicher ist. Wenn das Produkt einen gültigen Anwendungsfall im lokalen Netz hat, muss die Auswahl der Schnittstelle eine ausdrückliche Konfiguration des Benutzers sein, niemals ein URL-Detail des Agenten.

Gemischte A- und AAAA-Antworten brauchen eine harte Entscheidung. Wenn der Resolver eine erlaubte IPv4-Adresse und eine blockierte IPv6-Adresse zurückgibt, ist Ablehnen der sicherste Standard. Wenn die Verfügbarkeit die Verwendung der erlaubten Antwort verlangt, muss der Connector den Socket an diese Adresse binden und darf den Hostnamen später nicht wieder an einen Resolver übergeben. «Wir bevorzugen IPv4» ist keine Kontrolle. Netzwerka bibliotheken, Betriebssysteme und Verbindungsrennen können eine andere Wahl treffen.

Führe die Matrix in einem abgeschlossenen Harness aus

Richte SSRF-Tests nicht auf die echten Localhost-Dienste deines Laptops oder einen Cloud-Metadaten-Endpunkt. Du willst Beweise, keinen stressigen Nachmittag. Platziere Resolver und alle Listener in einem isolierten Testnetz, verwende wegwerfbare Anmeldedaten und lasse die Listener ihre eigene Adresse sowie empfangene Header protokollieren.

Ein einfaches Harness hat vier Beteiligte:

  1. Einen Test-Runner, der das Tool mit einer URL und einer Referenz auf wegwerfbare Anmeldedaten aufruft.
  2. Einen kontrollierbaren DNS-Server, der A- und AAAA-Antworten in einer programmierten Reihenfolge zurückgeben kann.
  3. Eine erlaubte HTTP-Fixture, die erfolgreiche authentifizierte Anfragen protokolliert.
  4. Eine blockierte HTTP-Fixture, die jede Verbindung oder Anfrage als Testfehler protokolliert.

Gib der blockierten Fixture einen Response-Body, der Leaks in lokalen Logs sichtbar macht, aber niemals ein vollständiges Token ausgibt. Sie kann zum Beispiel Peer-Adresse, Anfragemethode, Host-Header und einen Boolean zurückgeben, der angibt, ob Authorization vorhanden war. Der Runner vergleicht diese Informationen mit der erwarteten Zeile der Matrix.

Das Ergebnisformat sollte so einfach sein, dass es sich in CI prüfen lässt:

case=redirect_to_loopback
submitted=https://public.test/to-local
resolved=198.51.100.20
redirect=http://127.0.0.1:18080/
decision=deny
allowed_requests=0
blocked_connections=0
blocked_credentials=0

Ein Fehler sollte dieselben Felder sowie den tatsächlichen Peer enthalten. So wird aus einer vagen Regression ein verwertbarer Bericht:

case=rebind_ipv6
submitted=https://rebind.test/export
validation_answer=2001:db8::20
connected_peer=::1
blocked_credentials=1
result=FAIL

Der Bericht zeigt den Fehler sofort: Der Code hat eine DNS-Antwort genehmigt und sich mit einer anderen verbunden. Außerdem zeigt er, ob die Anmeldedaten die Grenze überschritten haben. Genau dieses Risiko ist entscheidend.

Wenn dein Tool eine Proxy-Konfiguration unterstützt, nimm Proxy-Zeilen in die Matrix auf. Ein HTTP-Proxy verändert das Ziel der TCP-Verbindung, während CONNECT den Proxy trotzdem dazu bringen kann, das vom Angreifer ausgewählte Ziel zu erreichen. Entscheide, ob der Proxy selbst ein vertrauenswürdiger Transport ist und ob das Tool die endgültige Authority prüft. Ein Proxy-Test, der nur die Adresse des Proxys kontrolliert, kann ein offenes Relay zu internen Zielen absegnen.

Halte Authority und Ziel in der Implementierung getrennt

API-Schlüssel nicht mehr offenlegen
Verwende den enthaltenen MCP-Shim, statt API-Schlüssel in den Agentenkontext zu übergeben.

TLS macht es leicht, diese Aufgabe zu vermischen. Um sicher eine Verbindung zu einem Hostnamen aufzubauen, muss das Tool möglicherweise einen Socket zu einer genehmigten numerischen Adresse öffnen und gleichzeitig den ursprünglichen genehmigten Hostnamen als TLS-Servernamen und HTTP-Host-Header verwenden. Das sind unterschiedliche Felder mit unterschiedlichen Aufgaben.

Der numerische Peer beantwortet die Frage: «Wohin geht dieser Socket?» Der TLS-Servername beantwortet: «Welche Zertifikatsidentität muss dieser Server beweisen?» Der Host-Header beantwortet: «Welche HTTP-Authority adressiert diese Anfrage?» Deine Implementierung sollte alle drei Werte ausdrücklich verwalten, statt sie von einer veränderlichen URL-Zeichenfolge in einer bequemen HTTP-API ableiten zu lassen.

Diese Trennung zeigt auch eine weiterhin verbreitete schlechte Empfehlung: einmal auflösen und anschließend den Hostnamen in der URL durch die numerische Adresse ersetzen. Das kann eine zweite DNS-Auflösung vermeiden, aber TLS-Zertifikatsprüfung, virtuelles Hosting und signierte Anfrageverfahren beschädigen. Entwickler reagieren dann oft, indem sie Zertifikatsprüfung deaktivieren oder Host-Prüfungen abschwächen. Das ist schlimmer als der ursprüngliche Fehler.

Verwende stattdessen einen Transport, der das Wählen der genehmigten Adresse unterstützt und gleichzeitig eine strenge TLS-Prüfung für den ursprünglichen Hostnamen beibehält. Prüfe nach dem Handshake, dass der Socket-Peer die genehmigte Adresse hat. Wenn deine Laufzeit diese Kontrollen nicht bietet, behaupte nicht, dass sie DNS-Rebinding bei authentifizierten Agenten-Anfragen verhindert. Lege die Einschränkung in der Produktgrenze offen und füge bei vom Agenten gewählten Zielen keine Anmeldedaten ein.

Bei SSH ist es genauso, auch wenn SSH keine HTTP-Weiterleitungen verwendet. Löse den angeforderten Host auf und klassifiziere ihn, binde die Verbindung an die genehmigte Adresse und prüfe den Host-Key gegen die vorgesehene Hostidentität. Eine Host-Key-Abfrage oder eine großzügige Known-Hosts-Regel ist keine Zielrichtlinie.

Sallyport bewahrt Anmeldedaten in seinem verschlüsselten Tresor auf und führt HTTP- und SSH-Aktionen selbst aus, statt geheimes Material an den Agenten zu übergeben. Diese Grenze funktioniert nur, wenn die Aktionsschicht Zielkontrollen anwendet, bevor sie gespeicherte Anmeldedaten verwendet.

Mache die Matrix zu einer Release-Schranke, nicht zu einem Sicherheitsdokument

Eine Zielmatrix ist nur dann nützlich, wenn sie gegen denselben Anfragepfad läuft, der ausgeliefert wird. Ein Unit-Test für isPrivateIp() prüft weder den Resolver des HTTP-Clients noch Weiterleitungscode, Proxy-Verhalten oder Verbindungspool. Behalte die Unit-Tests, aber mache die Harness-Suite verpflichtend, sobald du Transportbibliothek, URL-Parser, Resolver-Konfiguration oder Code zum Einfügen von Anmeldedaten aktualisierst.

Füge Zeilen hinzu, wenn du einen Fehler behebst. Verwandle einen schmerzhaften Vorfall nicht in eine allgemeine Aussage wie «SSRF-Validierung verbessern». Bewahre die exakte Eingabe, DNS-Sequenz, Weiterleitungsantwort und erwartete Abwesenheit von Anmeldedaten auf. Künftige Maintainer brauchen diese Erfahrung in ausführbarer Form.

Untersuche Fehler nach Kategorien. Abweichungen beim Parsen weisen auf doppelte URL-Verarbeitung hin. Wenn ein blockierter Listener eine TCP-Verbindung, aber keine Anfrage sieht, kann das bedeuten, dass du zu spät geprüft hast, auch wenn kein Token durchgesickert ist. Wenn ein blockierter Listener einen Authorization-Header sieht, ist die Invariante verletzt. Dann sollte der Release gestoppt werden.

Die Arbeit lohnt sich, weil sie die Frage deines Teams verändert. Fragt nicht mehr, ob ein Hostname äußerlich aussah. Fragt, welcher Peer eine authentifizierte Anfrage erhalten hat, wie er ausgewählt wurde und ob euer Test die Antwort beweisen kann. Wenn sich das nicht aus der Testausgabe ablesen lässt, hat das Tool weiterhin einen blinden Fleck bei SSRF.

FAQ

Reicht eine Hostname-Allowlist aus, um SSRF in einem Agenten-Tool zu verhindern?

Nein. Ein Hostname ist nur ein Eingangswert für die Auflösung. Ein zunächst harmloser Hostname kann beim Verbindungsaufbau zu einer lokalen oder privaten Adresse aufgelöst werden. Teste die Menge der aufgelösten Adressen und den tatsächlich verbundenen Peer, nicht nur den ursprünglichen Text des Agenten.

Soll ein HTTP-Client Anmeldedaten bei Weiterleitungen weitergeben?

Behandle jede Weiterleitung als neue Anfrage an ein Ziel. Validiere die neue URL, löse sie erneut auf, wende dieselbe Zielentscheidung an und füge Anmeldedaten erst nach erfolgreicher Prüfung hinzu. Wenn sich die Authority ändert, ist das Entfernen des Authorization-Headers sinnvoll, reicht allein aber nicht aus.

Welche IPv6-Adressen sollte ein SSRF-Test blockieren?

Ja. Blockiere oder klassifiziere IPv6-Loopback (::1), die nicht spezifizierte Adresse (::), Link-Local-Adressen (fe80::/10), Unique-Local-Adressen (fc00::/7), Multicast-Adressen und IPv4-abgebildete IPv6-Formen. Eine Testsuite, die nur Punktnotation bei IPv4 prüft, lässt eine große Lücke offen.

Wie teste ich DNS-Rebinding sicher?

DNS-Rebinding tritt auf, wenn ein Name eine frühere Prüfung besteht, später aber eine andere Adresse zurückgibt, oft kurz vor dem Verbindungsaufbau. Verwende eine kontrollierbare DNS-Fixture, die zuerst eine erlaubte und später eine blockierte Adresse liefert. Prüfe anschließend, dass das Tool keine authentifizierte Anfrage an den blockierten Peer sendet.

Kann ich DNS einmal prüfen und den HTTP-Client danach normal verbinden lassen?

Nein. Wenn du ein Auflösungsergebnis prüfst und danach eine Bibliothek den Hostnamen erneut auflösen lässt, entsteht eine Lücke zwischen Prüfung und Verwendung. Löse den Namen auf, klassifiziere jeden möglichen Kandidaten und verbinde dich mit der genehmigten Adresse. Alternativ prüfst du nach dem Verbindungsaufbau die Peer-Adresse, bevor du Anmeldedaten sendest.

Was ist eine Zielmatrix für SSRF-Tests?

Eine Zielmatrix ist eine Liste aus URL-Eingaben, DNS-Antworten, Weiterleitungszielen und erwarteten Sicherheitsentscheidungen. Sie verwandelt vage Aussagen wie «Wir blockieren private IPs» in wiederholbare Tests, die Parserfehler, alternative Adressformen und Änderungen im Bibliotheksverhalten erkennen.

Brauchen schreibgeschützte Agenten-Tools SSRF-Schutz?

Ja, wenn das Tool ein Geheimnis, ein Client-Zertifikat, eine signierte Anfrage oder andere Anmeldedaten mit mehr Rechten als der Agent anhängt. Eine Anfrage, die lediglich öffentliche Dokumentation liest, hat einen kleineren Schadensradius, sollte aber trotzdem ungeplante Zugriffe auf lokale Dienste vermeiden.

Was soll passieren, wenn DNS sowohl öffentliche als auch private Adressen zurückgibt?

Verweigere die Anfrage standardmäßig. Wenn der Resolver eine Mischung aus erlaubten und blockierten Adressen zurückgibt, kann ein gewöhnlicher HTTP-Client wegen der Adressreihenfolge oder einer IPv6-Präferenz die blockierte Adresse auswählen. Ein Tool, das Anmeldedaten einfügt, sollte gemischte Antworten ablehnen, sofern es nicht kontrolliert, mit welcher erlaubten Adresse es sich verbindet.

Reicht das Blockieren von RFC-1918-Bereichen als SSRF-Schutz?

Nein. Private Adressbereiche sind nur eine Kategorie. Loopback, Link-Local-Adressen, Carrier-Grade-NAT-Bereiche, Dokumentationsbereiche, nicht spezifizierte Adressen, Multicast, IPv4-abgebildete IPv6-Werte, Weiterleitungen und DNS-Änderungen brauchen ebenfalls eine bewusste Behandlung.

Wie kann ich beweisen, dass ein Agenten-Tool niemals Anmeldedaten an ein blockiertes Ziel gesendet hat?

Verwende einen ungefährlichen Erfassungsserver, der den Remote-Peer, den Anfragepfad, den Host-Header und das Vorhandensein eines Authorization-Headers protokolliert. Hinterlege ein Wegwerf-Testtoken ohne Zugriff außerhalb der Testumgebung. Ein guter Fehlerbericht nennt das Ziel, das das Token erhalten hat, nicht nur den Status 200 einer Anfrage.

Sallyport

Sallyport führt API-Aufrufe und SSH-Befehle für Ihren KI-Agenten aus. Die Schlüssel bleiben in einem lokalen Tresor auf Ihrem Mac; Sie geben jeden Lauf frei, und jede Aktion landet in einem versiegelten Journal.

© 2026 Sallyport · Open Source unter Apache-2.0 · Oleg Sotnikov