it-swarm.com.de

Sollten Sie sich vor unerwarteten Werten durch externe APIs schützen?

Nehmen wir an, Sie codieren eine Funktion, die Eingaben von einer externen API MyAPI entgegennimmt.

Diese externe API MyAPI hat einen Vertrag, der besagt, dass sie ein string oder ein number zurückgibt.

Wird empfohlen, sich vor Dingen wie null, undefined, boolean usw. zu schützen, obwohl dies nicht Teil der API von MyAPI ist? Insbesondere, da Sie keine Kontrolle über diese API haben, können Sie die Garantie nicht durch eine statische Typanalyse übernehmen. Ist es also besser, auf Nummer sicher zu gehen?

Ich denke in Bezug auf das Robustheitsprinzip .

51
Adam Thompson

Sie sollten den Eingaben in Ihre Software niemals vertrauen, unabhängig von der Quelle. Es ist nicht nur wichtig, die Typen zu validieren, sondern auch die Eingabebereiche und die Geschäftslogik. Laut einem Kommentar wird dies durch OWASP gut beschrieben

Wenn Sie dies nicht tun, erhalten Sie bestenfalls Mülldaten, die Sie später bereinigen müssen. Im schlimmsten Fall besteht jedoch die Möglichkeit böswilliger Exploits, wenn dieser Upstream-Dienst auf irgendeine Weise kompromittiert wird (siehe den Ziel-Hack). Zu den dazwischen liegenden Problemen gehört, dass Ihre Anwendung in einen nicht behebbaren Zustand versetzt wird.


Aus den Kommentaren kann ich ersehen, dass meine Antwort vielleicht etwas erweitert werden könnte.

Mit "Niemals den Eingaben vertrauen" meine ich einfach, dass Sie nicht davon ausgehen können, dass Sie immer gültige und vertrauenswürdige Informationen von vor- oder nachgelagerten Systemen erhalten. Daher sollten Sie diese Eingaben immer nach besten Kräften bereinigen oder ablehnen es.

Ein Argument tauchte in den Kommentaren auf, die ich als Beispiel ansprechen werde. Ja, Sie müssen Ihrem Betriebssystem bis zu einem gewissen Grad vertrauen, aber es ist nicht unangemessen, beispielsweise die Ergebnisse eines Zufallszahlengenerators abzulehnen, wenn Sie ihn nach einer Zahl zwischen 1 und 10 fragen, und er antwortet mit "bob".

Ebenso sollten Sie im Fall des OP auf jeden Fall sicherstellen, dass Ihre Anwendung nur gültige Eingaben vom Upstream-Dienst akzeptiert. Was Sie tun, wenn es nicht in Ordnung ist, liegt bei Ihnen und hängt in hohem Maße von der tatsächlichen Geschäftsfunktion ab, die Sie ausführen möchten. Sie würden es jedoch minimal für ein späteres Debuggen protokollieren und ansonsten sicherstellen, dass Ihre Anwendung nicht funktioniert in einen nicht wiederherstellbaren oder unsicheren Zustand.

Sie können zwar nie jede mögliche Eingabe kennen, die Ihnen jemand/etwas geben könnte, aber Sie können sicherlich die zulässigen Eingaben basierend auf den Geschäftsanforderungen einschränken und darauf basierend eine Form der Eingabe-Whitelist durchführen.

103
Paul

Ja natürlich. Aber warum denkst du, dass die Antwort anders sein könnte?

Sie möchten sicher nicht, dass sich Ihr Programm auf unvorhersehbare Weise verhält, falls die API nicht das zurückgibt, was der Vertrag sagt, nicht wahr? Zumindest muss man sich also irgendwie mit einem solchen Verhalten auseinandersetzen . Eine minimale Form der Fehlerbehandlung ist immer den (sehr minimalen!) Aufwand wert, und es gibt absolut keine Entschuldigung dafür, so etwas nicht zu implementieren.

Wie viel Aufwand Sie investieren sollten, um einen solchen Fall zu behandeln, hängt jedoch stark von der Groß- und Kleinschreibung ab und kann nur im Kontext Ihres Systems beantwortet werden. Oft reicht ein kurzer Protokolleintrag und das ordnungsgemäße Beenden der Anwendung aus. Manchmal ist es besser, eine detaillierte Ausnahmebehandlung zu implementieren, die sich mit verschiedenen Formen "falscher" Rückgabewerte befasst, und möglicherweise eine Fallback-Strategie zu implementieren.

Aber es macht einen verdammt großen Unterschied, wenn Sie nur eine interne Anwendung zur Formatierung von Tabellenkalkulationen schreiben, die von weniger als 10 Personen verwendet wird und bei der die finanziellen Auswirkungen eines Anwendungsabsturzes recht gering sind, oder wenn Sie ein neues autonomes Autofahren erstellen System, bei dem ein Anwendungsabsturz Leben kosten kann.

Es gibt also keine Abkürzung gegen , die darüber nachdenken, was Sie tun . Die Verwendung Ihres gesunden Menschenverstandes ist immer obligatorisch.

33
Doc Brown

Das Robustheitsprinzip - insbesondere die Hälfte davon "Sei liberal in dem, was du akzeptierst" - ist eine sehr schlechte Idee in Software. Es wurde ursprünglich im Zusammenhang mit Hardware entwickelt, bei der physikalische Einschränkungen technische Toleranzen sehr wichtig machen. In der Software haben Sie jedoch zwei Möglichkeiten, wenn jemand Ihnen fehlerhafte oder anderweitig falsche Eingaben sendet. Sie können es entweder ablehnen (vorzugsweise mit einer Erklärung, was schief gelaufen ist) oder versuchen, herauszufinden, was es bedeuten sollte.

EDIT: Es stellte sich heraus, dass ich mich in der obigen Aussage geirrt habe. Das Robustheitsprinzip stammt nicht aus der Welt der Hardware, sondern aus der Internetarchitektur, insbesondere RFC 1958 . Es sagt aus:

3.9 Seien Sie beim Senden streng und beim Empfangen tolerant. Implementierungen müssen beim Senden an das Netzwerk genau den Spezifikationen entsprechen und fehlerhafte Eingaben vom Netzwerk tolerieren. Verwerfen Sie im Zweifelsfall fehlerhafte Eingaben stillschweigend, ohne eine Fehlermeldung zurückzugeben, es sei denn, dies ist in der Spezifikation vorgeschrieben.

Dies ist eindeutig von Anfang bis Ende einfach falsch. Aus den in diesem Beitrag genannten Gründen ist es schwierig, sich einen falscheren Begriff der Fehlerbehandlung vorzustellen, als "fehlerhafte Eingaben stillschweigend zu verwerfen, ohne eine Fehlermeldung zurückzugeben".

Siehe auch das IETF-Papier Die schädlichen Folgen des Robustheitsprinzips für weitere Ausführungen zu diesem Punkt.

Niemals, niemals, niemals Wählen Sie diese zweite Option, es sei denn, Sie verfügen über Ressourcen, die dem Google-Suchteam entsprechen, um Ihr Projekt zu bearbeiten Denn genau das ist nötig, um ein Computerprogramm zu entwickeln, das in dieser speziellen Problemdomäne alles erledigt, was einem anständigen Job nahe kommt. (Und selbst dann scheinen die Vorschläge von Google etwa die Hälfte der Zeit direkt aus dem linken Feld zu kommen.) Wenn Sie dies versuchen, werden Sie massive Kopfschmerzen bekommen, bei denen Ihr Programm häufig versucht, sie zu interpretieren schlechte Eingabe als X, wenn der Absender wirklich Y meinte.

Das ist aus zwei Gründen schlecht. Das Offensichtliche ist, dass Sie dann schlechte Daten in Ihrem System haben. Das weniger offensichtliche ist, dass in vielen Fällen weder Sie noch der Absender bemerken, dass etwas schief gelaufen ist, bis viel später etwas in Ihrem Gesicht explodiert und Sie plötzlich ein großes, teures Durcheinander zu beheben haben und keine Ahnung haben was schief gelaufen ist, weil der spürbare Effekt so weit von der Grundursache entfernt ist.

Aus diesem Grund gibt es das Fail Fast-Prinzip. Sparen Sie allen Beteiligten Kopfschmerzen, indem Sie sie auf Ihre APIs anwenden.

23
Mason Wheeler

Im Allgemeinen sollte Code so erstellt werden, dass die folgenden Einschränkungen, wann immer dies praktikabel ist, eingehalten werden:

  1. Bei korrekter Eingabe die richtige Ausgabe erzeugen.

  2. Wenn eine gültige Eingabe gegeben wird (die möglicherweise korrekt ist oder nicht), erzeugen Sie eine gültige Ausgabe (ebenfalls).

  3. Wenn Sie eine ungültige Eingabe erhalten, verarbeiten Sie diese ohne Nebenwirkungen, die über die durch normale Eingaben verursachten oder als Signalisierung eines Fehlers verursachten hinausgehen.

In vielen Situationen durchlaufen Programme im Wesentlichen verschiedene Datenblöcke, ohne sich besonders darum zu kümmern, ob sie gültig sind. Wenn solche Blöcke ungültige Daten enthalten, würde die Ausgabe des Programms wahrscheinlich ungültige Daten enthalten. Sofern ein Programm nicht speziell dafür ausgelegt ist, alle Daten zu validieren und sicherzustellen, dass es keine ungültige Ausgabe erzeugt auch wenn eine ungültige Eingabe erfolgt, sollten Programme, die seine Ausgabe verarbeiten, die Möglichkeit ungültiger Daten in ihm berücksichtigen.

Eine frühzeitige Validierung von Daten ist häufig wünschenswert, aber nicht immer besonders praktisch. Unter anderem, wenn die Gültigkeit eines Datenblocks vom Inhalt anderer Datenblöcke abhängt und wenn der Großteil der Daten, die in eine bestimmte Folge von Schritten eingespeist werden, auf dem Weg herausgefiltert wird, wodurch die Validierung auf Daten beschränkt wird, die sie durchlaufen Alle Phasen bieten möglicherweise eine viel bessere Leistung als der Versuch, alles zu validieren.

Selbst wenn von einem Programm nur erwartet wird, dass es vorvalidierte Daten erhält, ist es oft gut, wenn es die oben genannten Einschränkungen einhält sowieso wann immer dies praktikabel ist. Das Wiederholen der vollständigen Validierung bei jedem Verarbeitungsschritt ist häufig ein erheblicher Leistungsverlust, aber die begrenzte Menge an Validierung, die erforderlich ist, um die oben genannten Einschränkungen aufrechtzuerhalten, kann viel billiger sein.

13
supercat

Vergleichen wir die beiden Szenarien und versuchen wir, eine Schlussfolgerung zu ziehen.

Szenario 1 Unsere Anwendung geht davon aus, dass sich die externe API gemäß der Vereinbarung verhält.

Szenario 2 Unsere Anwendung geht davon aus, dass sich die externe API möglicherweise schlecht verhält, und fügt daher Vorsichtsmaßnahmen hinzu.

Im Allgemeinen besteht die Möglichkeit, dass eine API oder Software gegen die Vereinbarungen verstößt. kann auf einen Fehler oder unerwartete Bedingungen zurückzuführen sein. Sogar eine API kann Probleme in den internen Systemen haben, die zu unerwarteten Ergebnissen führen.

Wenn unser Programm unter der Annahme geschrieben ist, dass die externe API die Vereinbarungen einhält und keine Vorsichtsmaßnahmen hinzufügt; Wer wird die Partei sein, die sich den Problemen stellt? Wir werden diejenigen sein, die Integrationscode geschrieben haben.

Zum Beispiel die von Ihnen ausgewählten Nullwerte. Angenommen, gemäß der API-Vereinbarung sollte die Antwort Werte ungleich Null haben. Wenn es jedoch plötzlich verletzt wird, führt unser Programm zu NPEs.

Daher glaube ich, dass es besser ist, sicherzustellen, dass Ihre Anwendung zusätzlichen Code enthält, um unerwartete Szenarien anzugehen.

3
lkamal

Im Allgemeinen müssen Sie sich immer vor fehlerhaften Eingaben schützen, aber je nach Art der API bedeutet "Schutz" verschiedene Dinge.

Für eine externe API zu einem Server möchten Sie nicht versehentlich einen Befehl erstellen, der den Status des Servers zum Absturz bringt oder gefährdet. Sie müssen sich also davor schützen.

Für eine API wie z. Bei einer Containerklasse (Liste, Vektor usw.) ist das Auslösen von Ausnahmen ein perfektes Ergebnis. Eine Beeinträchtigung des Status der Klasseninstanz kann bis zu einem gewissen Grad akzeptabel sein (z. B. wird ein sortierter Container mit einem fehlerhaften Vergleichsoperator nicht sortiert) Ein Absturz der Anwendung kann akzeptabel sein, aber den Status der Anwendung beeinträchtigen - z Schreiben in zufällige Speicherorte, die nichts mit der Klasseninstanz zu tun haben - ist höchstwahrscheinlich nicht der Fall.

1
Peter

Sie sollten eingehende Daten immer validieren - vom Benutzer eingegeben oder auf andere Weise -, damit Sie über einen Prozess verfügen, der verarbeitet werden kann, wenn die von dieser externen API abgerufenen Daten ungültig sind.

Im Allgemeinen sollte jede Naht, an der sich außerorganisatorische Systeme treffen, Authentifizierung, Autorisierung (falls nicht einfach durch Authentifizierung definiert) und Validierung erfordern.

1
StarTrekRedneck

Um eine etwas andere Meinung zu äußern: Ich denke, es kann akzeptabel sein, nur mit den Daten zu arbeiten, die Sie erhalten, auch wenn sie gegen den Vertrag verstoßen. Dies hängt von der Verwendung ab: Es MUSS eine Zeichenfolge für Sie sein, oder es ist etwas, das Sie nur anzeigen/nicht verwenden usw. Im letzteren Fall akzeptieren Sie es einfach. Ich habe eine API, die nur 1% der von einer anderen API gelieferten Daten benötigt. Es ist mir egal, welche Art von Daten in den 99% enthalten sind, daher werde ich sie niemals überprüfen.

Es muss ein Gleichgewicht zwischen "Fehler haben, weil ich meine Eingaben nicht ausreichend überprüfe" und "Ich lehne gültige Daten ab, weil ich zu streng bin" bestehen.

0
Christian Sauer

Ich gehe davon aus, dass ich immer jede Eingabe in mein System überprüfe. Das bedeutet, dass jeder von einer API zurückgegebene Parameter überprüft werden sollte, auch wenn mein Programm ihn nicht verwendet. Ich neige auch dazu, jeden Parameter, den ich an eine API sende, auf Richtigkeit zu überprüfen. Es gibt nur zwei Ausnahmen von dieser Regel, siehe unten.

Der Grund für das Testen ist, dass mein Programm sich auf nichts verlassen kann, wenn die API/Eingabe aus irgendeinem Grund falsch ist. Vielleicht war mein Programm mit einer alten Version der API verknüpft, die etwas anderes macht als ich glaube? Vielleicht ist mein Programm auf einen Fehler im externen Programm gestoßen, der noch nie zuvor aufgetreten ist. Oder noch schlimmer, passiert die ganze Zeit, aber niemand kümmert sich darum! Vielleicht wird das externe Programm von einem Hacker getäuscht, um Dinge zurückzugeben, die mein Programm oder das System verletzen können?

Die zwei Ausnahmen, um alles in meiner Welt zu testen, sind:

  1. Leistung nach sorgfältiger Leistungsmessung:

    • optimieren Sie niemals, bevor Sie gemessen haben. Das Testen aller eingegebenen/zurückgegebenen Daten dauert im Vergleich zum eigentlichen Anruf meistens sehr wenig, sodass das Entfernen häufig wenig oder gar nichts spart. Ich würde den Fehlererkennungscode immer noch behalten, ihn aber auskommentieren, vielleicht durch ein Makro oder einfach wegkommentieren.
  2. Wenn Sie keine Ahnung haben, was Sie mit einem Fehler tun sollen

    • es gibt Zeiten, in denen Ihr Design nicht oft die Behandlung von Fehlern zulässt, die Sie finden würden. Möglicherweise sollten Sie einen Fehler protokollieren, aber es gibt keine Fehlerprotokollierung im System. Es ist fast immer möglich, einen Weg zu finden, sich an den Fehler zu "erinnern", sodass zumindest Sie als Entwickler später danach suchen können. Fehlerzähler sind eine gute Sache in einem System, auch wenn Sie sich dafür entscheiden, keine Protokollierung zu haben.

Es ist eine wichtige Frage, wie sorgfältig die Ein-/Rückgabewerte überprüft werden müssen. Wenn die API beispielsweise eine Zeichenfolge zurückgeben soll, würde ich Folgendes überprüfen:

  • der Datentyp ist tatsächlich eine Zeichenfolge

  • und diese Länge liegt zwischen min und max Werten. Überprüfen Sie die Zeichenfolgen immer auf die maximale Größe, die mein Programm erwarten kann (die Rückgabe zu großer Zeichenfolgen ist ein klassisches Sicherheitsproblem in vernetzten Systemen).

  • Einige Zeichenfolgen sollten auf "unzulässige" Zeichen oder Inhalte überprüft werden, wenn dies relevant ist. Wenn Ihr Programm die Zeichenfolge möglicherweise später an eine Datenbank sendet, empfiehlt es sich, nach Datenbankangriffen zu suchen (Suche nach SQL-Injection). Diese Tests werden am besten an den Grenzen meines Systems durchgeführt, wo ich genau bestimmen kann, woher der Angriff stammt, und ich kann früh scheitern. Das Durchführen eines vollständigen SQL-Injection-Tests kann schwierig sein, wenn Zeichenfolgen später kombiniert werden. Daher sollte der Test vor dem Aufrufen der Datenbank durchgeführt werden. Wenn Sie jedoch frühzeitig Probleme feststellen, kann dies hilfreich sein.

Der Grund für das Testen von Parametern, die ich an die API sende, besteht darin, sicherzustellen, dass ich ein korrektes Ergebnis zurückerhalte. Auch diese Tests vor dem Aufrufen einer API sind möglicherweise unnötig, erfordern jedoch nur sehr wenig Leistung und können Fehler in meinem Programm erkennen. Daher sind die Tests bei der Entwicklung eines Systems am wertvollsten (aber heutzutage scheint sich jedes System in ständiger Entwicklung zu befinden). Abhängig von den Parametern können die Tests mehr oder weniger gründlich sein, aber ich stelle häufig fest, dass Sie für die meisten Parameter, die mein Programm erstellen kann, häufig zulässige Min- und Max-Werte festlegen können. Vielleicht sollte eine Zeichenfolge immer mindestens 2 Zeichen haben und maximal 2000 Zeichen lang sein? Das Minimum und das Maximum sollten innerhalb dessen liegen, was die API zulässt, da ich weiß, dass mein Programm niemals den gesamten Bereich einiger Parameter verwenden wird.

0
ghellquist