Prompt Injection
Andrea Hauser
So sicher ist GraphQL
Um Angriffswege auf GraphQL-Implementationen zu verstehen, müssen zuerst die Grundlagen von GraphQL selbst verstanden werden. GraphQL bietet eine Möglichkeit Daten abzufragen zu manipulieren sowie das Erhalten von Echtzeit-Updates. In diesem Artikel werden Abfragen und Manipulationen behandelt.
Das Abfragen von Daten sieht folgendermassen aus:
query{ human(id: "1000") { name height } }
Diese Abfrage für die Person mit der ID 1000
wird dann beispielsweise wie folgt beantwortet:
{ "data": { "human": { "name": "Luke Skywalker", "height": 1.72 } } }
Dabei fällt auf, dass die erhaltenen Daten die gleiche Struktur haben, wie die ausgestellte Query. Die Daten im oberen Beispiel sind schön dargestellt, allerdings sieht das Ganze im Request-Body des POST-Requests meist etwas weniger schön aus:
{"operationName":null,"variables":{},"query":"{\n human(id: \"1000\") {\n name\n height\n }\n}\n"}
Und der Response-Body sieht wie folgt aus:
{"data":{"human":{"name":"Luke Skywalker","height":1.72}}}
Mit dem Beispiel für Mutationen werden gleichzeitig Variablen einführen. Dabei sieht die Mutation selbst wie folgt aus:
mutation ($ep: Episode!, $review: ReviewInput!) { createReview(episode: $ep, review: $review) { stars commentary } }
Gleichzeitig müssen dafür allerdings noch die Variablen $ep
und $review
definiert werden, dies geschieht wie folgt:
{ "ep": "JEDI", "review": { "stars": 5, "commentary": "This is a great movie!" } }
Im POST-Request-Body sieht das Ganze dann wie folgt aus:
{"operationName":null,"variables":{"ep":"JEDI","review":{"stars":5,"commentary":"This is a great movie!"}},"query":"mutation ($ep: Episode!, $review: ReviewInput!) {\n createReview(episode: $ep, review: $review) {\n stars\n commentary\n }\n}\n"}
Die Response auf diese Mutation sieht wie folgt aus:
{ "data": { "createReview": { "stars": 5, "commentary": "Test is a great movie!" } } }
Neben der effektiven Kreierung des Reviews mit createReview(...)
wird in diesem Beispiel zudem noch das Resultat der eben durchgeführten Aktion abgefragt. Dabei handelt es sich um stars
und commentary
in den geschweiften Klammern nach createReview(...)
. Die Abfrage des Resultats der Mutation ist in diesem Beispiel nicht sehr spannend, wenn an dieser Stelle jedoch eine Mutation wie login(username,password)
wäre, könnte dort als Resultat zum Beispiel der Login-Status und das Authentication-Token abgefragt werden.
Damit sind die wichtigsten Grundlagen abgedeckt, die für das weitere Verständnis notwendig sind. Die in diesem Artikel verwendeten Grundlagenbeispiele von GraphQL stammen direkt von graphql.org. Für weiterführende Beispiele und Erklärungen wird empfohlen direkt darauf zurückzugreifen, denn die dort vorhandene Dokumentation ist umfangreich und verständlich.
Die nächsten Abschnitte behandeln jeweils konkrete Angriffsmöglichkeiten auf einen GraphQL-Endpunkt.
In den einfachsten Fällen wird der GraphQL-Endpunkt mit der URL /graphql
angesprochen. Der Endpunkt kann allerdings durch Entwickler frei gewählt werden. Wenn der Request nicht an /graphql
oder etwas ähnliches geht, muss der POST Request Body genauer untersucht werden. Wenn sich darin Schlüsselwörter wie query
oder mutation
befinden oder es viele Zeichen wie \n
darin hat, kann es sich ebenfalls um einen GraphQL-Endpunkt handeln. Wie POST Request und Response Bodies im Detail aussehen, wurde bereits in der Einführung angesprochen.
Eine weitere Möglichkeit GraphQL-Endpunkte zu identifizieren liegt darin, bewusst Fehlermeldungen zu provozieren. Wenn an einen GraphQL-Endpunkt das folgende geschickt wird query={}
und die Antwort etwas in diese Richtung beinhaltet "Syntax Error: Expected Name, found }"
kann davon ausgegangen werden, dass es sich um einen GraphQL-Endpunkt handeln.
Ebenfalls wichtig zu beachten ist, dass GraphQL-Endpunkte sowohl über GET wie auch POST Requests ansprechbar sein können. Es sollten immer beide Varianten versucht werden.
Wenn nicht sauber deaktiviert, können durch das Anhängen von ?debug=1
in der URL deskriptive Fehlermeldungen sowie Stacktraces aktiviert werden. Bei Angriffen auf GraphQL-Endpunkte können diese Stacktraces das Debugging von fehlgeschlagenen Angriffen deutlich einfacher machen. Es sollte sichergestellt werden, dass in produktiven Umgebungen keine technischen Fehlermeldungen eingesehen werden können und die Debug URL deaktiviert wurde.
Bei Introspection-Queries handelt es sich um Abfragen, die es einem Erlauben das gesamte Schema einer Applikation abzufragen; das heisst alle möglichen Queries und Mutationen sowie alle definierten Typen aufzulisten. Die Default-Setups von GraphQL verhindern Introspection-Queries nicht. Die Informationen, die durch eine Introspection-Query gewonnen werden können, erleichtern einem Angreifer die Arbeit. Eine umfassende Introspection-Query sieht wie folgt aus:
query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } }
Die Response auf eine solche Introspection-Query ist meist sehr gross, diese manuell zu verarbeiten und die spannenden Informationen daraus zu gewinnen ist sehr aufwendig. Es gibt Tools wie zum Beispiel GraphQL Voyager, welche die Resultate von Introspection-Queries visualisieren können. Solche Visualisierungen können dann ausgewertet werden und die spannenden Bereiche gezielter angegangen werden. Es kann allerdings auch sein, dass durch diese Visualisierung klar wird, dass auf Datenfelder zugegriffen werden können, die nicht öffentlich sein sollten.
Um das einfache und vollständige Auswerten des gesamten Schemas zu verhindern, sollte Introspection in der produktiven Umgebung deaktiviert werden. Das Deaktivieren der GraphQL Introspection ist je nach eingesetzter Sprache unterschiedlich einfach. Für PHP zum Beispiel kann Introspection wie folgt deaktiviert werden:
<?php use GraphQL\GraphQL; use GraphQL\Validator\Rules\DisableIntrospection; use GraphQL\Validator\DocumentValidator; DocumentValidator::addRule(new DisableIntrospection());
Die genauen Details für weitere verwendete Implementationssprache können am einfachsten in der Dokumentation der Implementation selbst gefunden werden.
Neben Introspection selbst können Entwickler auch GraphiQL nutzen. Bei GraphiQL handelt es sich um die GraphQL IDE. Die IDE ist zum Beispiel unter Endpunkten wie /graphiql
, /graphql/console
oder /__graphql
erreichbar. Damit werden die gleichen Informationen wie mit Introspection verfügbar gemacht, allerdings in einer schönen beziehungsweise lesbaren Form.
GraphiQL sollte lediglich in Entwicklungs- oder Testumgebungen eingesetzt werden, die nicht der Öffentlichkeit zugänglich sind.
Wenn Queries falsch geschrieben werden, kann es zu Loops zwischen zwei Queries kommen. Diese können dann immer weiter ineinander verschachtelt werden und so zu einer Überlastung des Servers und damit zu Denial of Service führen. Als Beispiel wird eine Applikation vorgestellt, die Autoren und deren Werke aufführt. Mit einer Query kann ein Autor abgefragt werden, das Resultat beinhaltet seine Werke. Zudem besteht eine Query bei der von einem Werk der Autor abgefragt werden kann. Diese beiden Queries lassen sich beliebig tief ineinander verschachteln.
query { author(id:1) { books { author { books { author { books { author { … } } } } } } } }
Es gibt unterschiedliche Ansätze, wie dieses Problem behoben werden kann. Einerseits kann Depth Limiting umgesetzt werden. Dabei wird eine maximale Limite festgelegt, wie tief Queries ineinander verschachtelt werden dürfen. Andererseits kann eine maximale Limite für die Ausführungszeit einer Query festgelegt werden und die Ausführung einer Query wird nach dieser Limite abgebrochen.
In der GraphQL-Dokumentation wird Batching wie folgt beschrieben:
Without additional consideration, a naive GraphQL service could be very “chatty” or repeatedly load data from your databases. This is commonly solved by a batching technique, where multiple requests for data from a backend are collected over a short period of time and then dispatched in a single request to an underlying database or microservice.
Dies bedeutet, dass in einer Request mehrere Mutationen auf einmal mitgegeben werden können, die danach eine nach der anderen abgearbeitet werden. Damit lassen sich zum Beispiel Brute-Force-Angriffe auf Login-Verfahren und Zweifaktor-Mechanismen mit Tokens durchführen. Da nur ein einziger grosser POST-Request dafür benötigt wird, kann es sein, dass ein solcher Brute-Force-Angriff durch klassische WAFs nicht erkannt wird. Um solche Angriffe zu verhindern müssen Brute-Force-Angriffe in der Business-Logik erkannt und behandelt werden.
Neben all den GraphQL spezifischen Schwachstellen soll nicht vergessen werden, dass die internen Services und Datenbanken, die hinter der GraphQL-API stecken, an klassischen Schwachstellen aus der OWASP Top 10 oder ähnlichem leiden können. GraphQL setzt zudem keine Authentisierung und Autorisierung um, die Implementierung dieser Konzepte ist den Entwicklern im Backend überlassen und sollten ebenfalls immer überprüft werden, wenn eine GraphQL-API angetroffen wird.
Mit jeder neuen Technologie gibt es weitere Stolperfalle für Entwickler. Die hier aufgeführten Angriffe und deren Gegenmassnahmen können als erster Schritt für die Umsetzung einer sichereren GraphQL Applikation genutzt werden.
Unsere Spezialisten kontaktieren Sie gern!
Andrea Hauser
Andrea Hauser
Andrea Hauser
Andrea Hauser
Unsere Spezialisten kontaktieren Sie gern!