Zurück zum Blog

Folgen und abonnieren

Absicherung von GraphQL

Fastly Security Research Team

Fastly Security Research Team, Fastly

Simran Khalsa

Staff Security Researcher

GraphQL ist eine Open-Source-Query-Sprache, die als Alternative zu REST verwendet werden kann. Immer mehr Entwickler entscheiden sich für diese Sprache, da sie ihnen die Flexibilität bietet, die für die Pflege moderner, wachstumsstarker APIs erforderlich ist. GraphQL bietet Kunden die Möglichkeit, präzise Daten abzufragen, und erleichtert die Weiterentwicklung von APIs im Laufe der Zeit. 

Die Vorteile liegen auf der Hand, die Sicherheitsauswirkungen sind aber häufig weniger bekannt. Welche Funktionen können von Angreifern missbraucht werden? Welche unbeabsichtigten Konsequenzen hat die flexible Datenabfrage? Welche Schwachstellen können Angreifer am einfachsten ausnutzen?  

In diesem Artikel gehen wir diesen Fragen nach und geben Ihnen Standardeinstellungen und Kontrollmechanismen an die Hand, mit denen Sie bei der Implementierung von GraphQL für mehr Sicherheit sorgen können. Wir konzentrieren uns dabei auf drei Kategorien: riskante Konfigurationen, böswillige Queries und Web-API-Schwachstellen. Packen wir’s an! 

Konfigurationstipps

GraphQL bietet legitime Funktionen, die von Angreifern missbraucht werden können – einschließlich Introspektion, Feldvorschläge und Debug-Modus. Bei der Bereitstellung entsprechender Implementierungen ist also Vorsicht geboten. 

1. Introspektion vermeiden

Herausforderung: GraphQL ist introspektiv. Sie können bei einem GraphQL Schema also Details abfragen, die umfassende Informationen über seine Datenstrukturen liefern, einschließlich Argumenten, Feldern, Typen, Beschreibungen und veraltetem Typenstatus. Wenn diese Informationen nach außen dringen, können zusätzliche Angriffsflächen entstehen, die möglicherweise weitere Schwachstellen enthalten, die Angreifer ausnutzen können.  

Wenn Sie beispielsweise alle Typen in einem Schema auflisten und zu jedem davon genauere Informationen abrufen möchten, können Sie eine Introspektions-Query verwenden: 

{
  __schema {
    types {
      name
      kind
      description
      fields {
        name
      }
    }
  }
}

Ebenso bietet GraphQL eine integrierte Entwicklungsumgebung (IDE) namens GraphiQL (man beachte das „i“), in der Nutzer Queries erstellen können, indem sie sich in einer nutzerfreundlichen Oberfläche durch Felder und Eingaben klicken. GraphiQL ist eine weitere Methode, um das unterstützte Schema zu verstehen und anschließend zusätzliche Angriffsflächen wie vorhandene Queries oder Mutationen auszuspionieren.

Eine derartige Server-Query könnte das fehlende Puzzleteil bei der Planung eines schwerwiegenden, komplexen Angriffs liefern. Nehmen wir an, wir entdecken mithilfe der Introspektion ein Objekt namens UploadFile

{
  "name": "UploadFile",
  "kind": "OBJECT",
  "description": null,
  "fields": [
    {
      "name": "content"
    },
    {
      "name": "filename"
    },
    {
      "name": "result"
    }
  ]
}

Eine denkbare Angriffsmethode, um diese Entdeckung auszunutzen, wäre beispielsweise ein Traversal-Angriff, der darauf abzielt, auf Dateien und Verzeichnisse zuzugreifen, die außerhalb des Web-Root-Ordners gespeichert sind, oder diese zu verändern. Nehmen wir für unser Beispiel einfach an, dass das Argument „filename“ eine beliebige Zeichenfolge zulässt und Schreibzugriff auf einen beliebigen Speicherort im Dateisystem des Servers bietet. Als Nächstes erstellen wir wie folgt eine Query, um eine Datei namens poc.php zu erzeugen:

mutation {
  uploadFile(filename:”../../../../var/www/html/app/poc/poc.php”, content: “<?php
phpinfo(); ?>”){
    result
  }
}

Sobald wir diesen Pfad über unseren Angriffsvektor aufrufen, können wir beliebigen Code ausführen. Von dort aus könnten wir versuchen, eine Reverse Shell zu erhalten, um mit der zugrunde liegenden Serverumgebung zu interagieren. 

Lösung: Introspektion ist in der Entwicklung nützlich. Wenn Sie aber Zugriff auf geschützte vertrauliche Informationen gewähren, sollten Sie sie vermeiden.  

Es mag verlockend sein, Nutzern das Erlernen von API-Queries durch Introspektion zu erleichtern, die Bereitstellung von separater Dokumentation (zum Beispiel readthedocs) ist allerdings sicherer. Indem Sie die Introspektion deaktivieren, beheben Sie zwar keine Schwachstellen, aber zumindest machen Sie Angreifern das Leben dadurch nicht leichter.

Bei vielen GraphQL Implementierungen ist die Introspektion standardmäßig aktiviert. Der sicherste Ansatz besteht also darin, sie für das ganze System zu deaktivieren. Glücklicherweise gibt es hilfreiche Ressourcen zur Einschränkung der Introspektion in gängigen Frameworks und Programmiersprachen wie Ruby, NodeJS, Java, Python und PHP. Nuclei bietet sogar ein Template an, mit dem Sie die Introspektion Ihrer GraphQL Implementierungen testen können.

2. Feldvorschläge deaktivieren

Herausforderung: Wenn die Introspektion deaktiviert ist, können Angreifer versuchen, mithilfe einer Funktion, die im Allgemeinen als „Feldvorschläge“ bezeichnet wird, eine Brute-Force-Attacke auf das GraphQL Schema durchzuführen. Feldvorschläge werden durch Angabe eines falschen Feldnamens in einer Query ausgelöst, woraufhin in einer Fehlerantwort Felder mit ähnlichen Namen offenlegt werden. 

Zum Beispiel führt das Senden einer Namens-Query (query { name }) zu folgender Antwort:

{
  "errors": [
    {
      "message": "Cannot query field \"name\" on type \"Query\". Did you mean \"node\"?",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ]
    }
  ]
}

Lösung: Feldvorschläge können eine praktische Funktion sein, wenn ein Entwickler versucht, eine API mit GraphQL oder gegen öffentliche APIs zu integrieren. Dennoch sollte die Feldvorschlagsfunktion mit Vorsicht gehandhabt werden. Sie sollten erwägen, diese Funktion in jeder Umgebung zu deaktivieren, die Zugriff auf geschützte vertrauliche Informationen bietet.

3. „Debug“-Modus deaktivieren

Herausforderung: Aus Fehlern lernt man bekanntlich am besten. Sie geben uns Aufschluss über das Geschehen, wenn etwas schiefläuft. Ein unsachgemäßer Umgang mit Fehlern kann jedoch zu einer Reihe von Sicherheitsproblemen in GraphQL führen. 

GraphQL lässt sich im „Debug“-Modus ausführen. Die Hauptfunktion dieses Modus besteht in der detaillierten Anzeige von Anfragefehlern zur Unterstützung bei der Entwicklung. Es ist äußerst problematisch, wenn Sie Ihre GraphQL Implementierung in der Produktivumgebung mit aktiviertem „Debug“-Modus ausführen, da es dabei zu übermäßig vielen Fehlern wie Stack Traces kommt. Außerdem werden dabei weitere vertrauliche Informationen offengelegt, was nicht nur die Sicherheit, sondern auch die Compliance gefährdet.  

Beispiel für einen Stack Trace

Lösung: Vergewissern Sie sich, dass der „Debug“-Modus in der Produktivumgebung deaktiviert ist, und klammern Sie Stack Traces aus, bevor die Informationen an die Clients zurückgegeben werden. 

Wenn Sie Stack Traces loggen möchten, können Sie dies auch tun, ohne sie an den Nutzer zurückzugeben, indem Sie sie nur Entwicklern zur Verfügung stellen. Eine gängige Methode zur besseren Fehlerüberwachung ist die Implementierung einer Middleware, mit der Sie eine Anfrage prüfen und ändern und somit die Maskierung oder Bereinigung von Fehlern erzwingen können. 

Tipps für böswillige Angriffe

Angreifer können böswillige GraphQL Queries erstellen, die ihnen die Türen für Denial-of-Service (DoS)-, Brute-Force- und Enumeration-Angriffe öffnen können.  

4. Maximale Tiefe festlegen

Herausforderung: Die Tiefe jeder GraphQL Query ergibt sich aus der Anzahl der verschachtelten Felder und der Anzahl der Objekte innerhalb dieser Felder. Anstatt normale Anfragen zu senden, könnten Angreifer ohne großen Aufwand Queries erstellen, deren Komplexität exponentiell steigen, das System überlasten und zum Denial of Service (DoS) führen kann. Dies kann auch versehentlich passieren, wenn ein normaler Nutzer nicht weiß, wie er eine Query richtig erstellt. 

Wenn Typen in GraphQL aufeinander verweisen, öffnet dies den Server für die Möglichkeit zyklischer Queries, die exponentiell wachsen, Ressourcen in Beschlag nehmen und den Server zum Absturz bringen könnten.  

Beispielsweise könnte eine GraphQL Implementierung einen Zirkelverweis enthalten, der wie folgt definiert ist:

type Blog {
  comments(first: Int, after: String)
}

Type Comment {
  blog: Comment
}

Type Query {
  blog(id: ID!): Blog
}

Da sich damit sowohl die Kommentare in einem Blog als auch ein Blog mit Kommentaren abfragen lassen, können böswillige Akteure kostspielige, verschachtelte Queries erstellen und damit die Menge der geladenen Objekte exponentiell erhöhen. Ein Beispiel:

query nefariousQuery {              
  blog(id: “some-id”) {             
    comments(first: 9999) {     
      blog {
  comments(first: 9999) {
    blog {
      # ... repeat  
    }       
        }
      }
    }
  }
}

Lösung: Das Festlegen einer maximalen Tiefe kann GraphQL Angriffe, die es auf Tiefe und Komplexität abgesehen haben, abschwächen. Allerdings reicht die Query-Tiefe in einigen Fällen nicht aus, um den vollen Umfang der potenziellen Kosten zu ermitteln. Eine Query kann nämlich ein bestimmtes Feld enthalten, dessen Auflösung länger dauert, was wiederum zu höheren Compute-Kosten führt. 

Eine Query-Kostenanalyse kann Abhilfe verschaffen, indem Feldern Kosten zugewiesen werden. So lässt sich sicherstellen, dass der Server Felder ablehnen kann, sollten diese zu hohe Kosten verursachen. Bevor Sie sich aber ans Werk machen und diese Maßnahme implementieren, sollten Sie sich zunächst einmal vergewissern, ob dies auch wirklich nötig ist. Wenn Ihr Service keine teuren verschachtelten Beziehungen enthält oder die Last womöglich ohnehin bewältigen kann, sind solche Vorbeugungsmaßnahmen vielleicht gar nicht erforderlich. 

5. Batching neu definieren

Herausforderung: Ein wesentlicher Vorteil von GraphQL ist das Query Batching, also die Möglichkeit, eine Gruppe von Anfragen in einer einzigen Anfrage zusammenzufassen. Wenn aber nicht schon im Vorfeld bestimmte Sicherheitsmaßnahmen getroffen werden, kann das Query Batching in GraphQL Angreifern profitable Möglichkeiten eröffnen. 

Das folgende Code Snippet ist beispielsweise eine Batch Query, die verwendet wird, um mehrere Instanzen eines Nutzerobjekts anzufordern, was einen Brute-Force-Angriff erleichtern könnte: 

query {
  user(id: "101") {
    name
  }
  second:user(id: "102") {
    name
  }
  third:user(id: "103") {
    name
  }
}

Angreifer könnten damit sogar so weit gehen, dass sie mit einer einzigen Anfrage eine Auflistung aller möglichen Nutzer erhalten könnten. Dies ist eine für GraphQL spezifische Art von Brute-Force-Angriff, die einen in der Regel nur schwer erkennbaren Exploit ermöglicht, da Sie mehrere Objektinstanzen in einem einzigen Request anfordern können. Im Gegensatz dazu würde eine REST-API von einem Angreifer verlangen, dass er für jede Objektinstanz eine eigene Anfrage sendet. 

Da das Query Batching als einziger Request auftritt, könnte ein Missbrauch gängige Appsec-Tools wie Web Application Firewalls (WAFs), Runtime Application Self Protection (RASP), Intrusion-Detection- und Intrusion-Prevention-Systeme (IDS/IPS) oder Security Information and Event Management Systems (SIEMs) umgehen. 

Lösung: Eine Möglichkeit, GraphQL Query-Batching-Angriffe angemessen zu bekämpfen, besteht in der Einführung von Rate-Limiting-Regeln für Objekte. Sie könnten zum Beispiel tracken, wie viele verschiedene Objektinstanzen ein Nutzer angefordert hat, und diese blockieren, wenn zu viele Objekte angefordert wurden. 

Eine weitere Möglichkeit, diese Art von Angriffen zu verhindern, besteht darin, sensible Objekte vom Query Batching auszuschließen. So müssen Angreifer einen anderen Weg für den Zugriff auf diese Objekte (zum Beispiel eine REST-API) wählen, die eine Anfrage pro Objekt stellt. 

Neben diesen beiden Methoden kann es auch hilfreich sein, die Anzahl der Vorgänge zu begrenzen, die gebatcht und gleichzeitig ausgeführt werden können. 

6. Eingaben frühzeitig validieren

Herausforderung: GraphQL unterscheidet sich nicht wesentlich von anderen API-Architekturen. Anwendungen, die GraphQL nutzen, sind nach wie vor anfällig für bekannte und gefürchtete Sicherheitslücken. Es ist Aufgabe des Entwicklers, Eingaben ordnungsgemäß zu validieren und zu bereinigen, um schädliche Anfragen zu verhindern. 

Schauen wir uns zum Beispiel an, wie wir eine OS-Command-Injection-Schwachstelle über GraphQL ausnutzen könnten. 

Stellen Sie sich einen Onlineshop vor, bei dem der Lagerbestand eines bestimmten Artikels überprüft werden soll. Aus historischen Gründen sendet der Server Queries an ein veraltetes System und verwendet dabei ein Shell-Skript mit den Argumenten itemId, vendorId und color:

inventorycount.sh 80 200 red

Dieses Skript sorgt dafür, dass die Bestandszahlen des Artikels freigelegt und in der Anfrage zurückgegeben werden. Da in der Anwendung aber keine Schutzmechanismen implementiert sind, kann ein Angreifer wie folgt einen beliebigen Befehl eingeben:

red ; env ;

Die resultierende GraphQL Query würde wie folgt aussehen:

query {
    inventoryCount(itemId:80, vendorId:200 color:”red; env ;”,)
}

Diese Query gibt sodann den Inhalt von Umgebungsvariablen zurück, die Geheimnisse und andere vertrauliche Informationen enthalten können. 

Beispielantwort

Lösung: Generell sollten die Inputs so früh wie möglich im Datenfluss validiert werden. Auch wenn diese bereinigt und validiert werden, sollten sie nicht dazu verwendet werden, einem Nutzer die Kontrolle über den Datenfluss zu geben. Alle eingehenden Daten sollten mit GraphQL Skalar- und Enum-Datentypen validiert werden. 

Sie können auch nutzerdefinierte GraphQL Validatoren für komplexere Validierungen schreiben. Die Zulassungsliste für GraphQL Queries kann demnach potenzielle Auswirkungen minimieren, indem dem Server mitgeteilt wird, dass er keine Queries durchlassen soll, die nicht vorab genehmigt wurden. Wenn Sie aber trotz allem immer noch nicht ruhig schlafen können oder sich in einer Komplexitätsspirale verfangen, empfehlen wir Ihnen natürlich unsere Next-Gen WAF (ehemals Signal Sciences).  

Nächste Schritte

GraphQL ist ein neuer Standard für die Interaktion mit APIs, der Sicherheitsauswirkungen und Angriffsmöglichkeiten mit sich bringt, die wir im Auge behalten müssen. 

Voraussetzung für die Entwicklung sicherer Software ist ein Verständnis der zugrunde liegenden Sicherheitsprinzipien der jeweiligen Technologie, mit der Sie arbeiten. Sich der Sicherheitslücken bewusst zu sein, die in jeder Phase des Entwicklungszyklus auftreten können, ist der erste Schritt in Richtung einer Zukunft mit weniger Zwischenfällen und Panikmomenten, sei es bei Projekten, die GraphQL verwenden, oder bei anderen Projekten. 

Der beste Weg zur Sicherheit ist bekanntlich eine tiefgreifende Verteidigung. Für den Fall, dass Sicherheitskontrollen versagen oder eine Sicherheitslücke auftritt, ist unsere Next-Gen WAF (ehemals Signal Sciences) jetzt mit GraphQL Unterstützung in der Betaversion verfügbar.