Schützen Sie Ihre GraphQL-API vor Sicherheitslücken

Hallo habr Ich präsentiere Ihnen die Übersetzung des Artikels Schutz Ihrer GraphQL-API vor Sicherheitslücken .


GraphQL wird schnell zur Wahl für Entwickler, die eine API für ihre Client-Anwendung erstellen müssen. Wie alle neuen Technologien unterliegt GraphQL jedoch einigen inhärenten Sicherheitsrisiken. Unabhängig davon, ob Sie ein Drittanbieterprojekt oder eine umfangreiche Unternehmensanwendung erstellen, müssen Sie sicherstellen, dass Sie sich vor diesen Bedrohungen schützen.


Bild


Obwohl die in diesem Beitrag aufgeführten Bedrohungen spezifisch für GraphQL sind, führt Ihre Implementierung eine Reihe neuer Bedrohungen ein, die behoben werden müssen. Es ist auch wichtig, dass Sie die Bedrohungen kennen, denen Anwendungen ausgesetzt sind, die im Netzwerk ausgeführt werden.


Bedrohung: Große, tief verschachtelte Abfragen, deren Berechnung teuer ist


Lösung : Schachtelungstiefenbegrenzung


Die von GraphQL bereitgestellte Leistung ist mit einigen neuen Sicherheitsbedrohungen verbunden. Am häufigsten sind tief verschachtelte Abfragen, die zu teuren Berechnungen und riesigen JSON-Werten führen, die sich negativ auf das Netzwerk und seine Bandbreite auswirken können.


Der richtige Weg, um Ihre API vor solchen Angriffen zu schützen, besteht darin, die Tiefe der Anforderungen so zu begrenzen, dass böswillige tiefe Anforderungen blockiert werden, bis das Ergebnis berechnet ist.


GraphQL Depth Limit bietet eine einfache Schnittstelle zur Begrenzung der Abfragetiefe.


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) ] }))) 

Bedrohung: Brute Force anfällige Mutationsanforderungen


Lösung : Begrenzen Sie die Anzahl der Anfragen


Das Durchsuchen von Logins und Passwörtern ist der älteste Trick in der Geschichte des Hackens. In den letzten zehn Jahren sind im Internet so viele Datenlecks aufgetreten, dass kürzlich eine Datenbank mit 772.904.991 eindeutigen E-Mails und 21.222.975 eindeutigen Kennwörtern gefunden wurde. Um zu überprüfen, ob Informationen über Ihre E-Mail- Adresse und Ihr Passwort durchgesickert sind, hat Troy Hunt sogar eine Website erstellt, für die er unter anderem diese Datenbank verwendet hat.


Glücklicherweise haben Sie eine einfache Möglichkeit, Angreifern das Busting zu erschweren und teuer zu machen, was Sie zu einem weniger attraktiven Ziel für sie macht.


Mit dem GraphQL Rate Limit- Plugin können Sie Einschränkungen für Ihre Abfragen auf drei verschiedene Arten festlegen: mithilfe benutzerdefinierter graphql-shield-Direktiven oder mithilfe der Funktion zum Begrenzen der Basisanzahl von Abfragen.


Mit diesem Plugin können Sie ein Zeitfenster und ein Limit für die Anzahl der Anfragen festlegen. Durch das Festlegen eines großen Zeitfensters für sehr anfällige Anforderungen, z. B. das Anmelden, und eines kleinen Zeitfensters für weniger anfällige Anforderungen können Sie für normale Benutzer eine angenehme Erfahrung aufrechterhalten und werden für Angreifer zum Albtraum.


Erstellen Sie eine Anweisung, um die Anzahl der Anforderungen zu begrenzen:


Hier benötigen Sie für jede Anfrage eine eindeutige Kennung. Sie können die IP-Adresse des Benutzers oder eine andere Kennung verwenden, die für jeden Benutzer eindeutig ist und jeder Anforderung entspricht.


 const rateLimitDirective = createRateLimitDirective({ identifyContext: (context) => { return context.id }, }) 

Fügen Sie Ihrem Schema eine Direktive hinzu:


 import { createRateLimitDirective } from 'graphql-rate-limit' export const schema = { typeDefs, resolvers, schemaDirectives: { rateLimit: rateLimitDirective, }, } export default schema 

Fügen Sie schließlich Ihrer anfälligen Abfrage eine Direktive hinzu:


 #        60  Login(input: LoginInput!): User @rateLimit( window: "60s" max: 10 message: "You are doing that too often. Please wait 60 seconds before trying again." ) 

Bedrohung: Der Benutzer kann benutzerspezifische Daten beeinflussen


Lösung : Nehmen Sie diese Daten nach Möglichkeit aus einer Benutzersitzung


Es ist leicht anzunehmen, dass es sich lohnt, den Benutzer entscheiden zu lassen, welche Ressource er aktualisieren möchte, wenn Sie dem Benutzer erlauben möchten, eine Ressource zu aktualisieren. Was aber, wenn ein Benutzer eine Ressourcenkennung erhält, auf die er eigentlich keinen Zugriff haben sollte?


Angenommen, wir haben eine updateUser-Mutationsanforderung, mit der der Benutzer sein Profil aktualisieren kann.


 mutation UpdateUser($input: {"id": "test123" , "email": "test@example.com"}) { UpdateUser(input: $input) { id firstName lastName } } 

Wenn auf der Serverseite kein Schutz vorhanden ist, kann der Angreifer mit einer Liste von Kennungen möglicherweise die E-Mail-Adresse für jeden Benutzer aktualisieren. Die naheliegende Lösung besteht darin, eine Prüfung hinzuzufügen, um sicherzustellen, dass die ID des aktuellen Benutzers mit der ID in den Eingabefeldern übereinstimmt.


Mach das nicht:


 function updateUser({ id, email }) { return User.findOneAndUpdate({ _id: id }, { email }) .catch(error => { throw error; }); } 

Eine weniger offensichtliche, aber korrekte Möglichkeit, dieses Problem zu lösen, besteht darin, zu verhindern, dass der Bezeichner als Eingabe verwendet wird, und den Benutzer-Bezeichner aus dem Kontextobjekt zu verwenden.


Mach das:


 function updateUser({ email }, context) { return User.findOneAndUpdate({ _id: context.user._id }, { email }) .catch(error => { throw error; }); } 

Vielleicht ist dies ein eher triviales Beispiel, aber das Ausführen solcher Aktionen für jedes der Objekte, mit denen der Benutzer direkt interagiert, kann Sie vor vielen riskanten Fehlern schützen.


Erledigung mehrerer teurer Anfragen gleichzeitig


Lösung : Kostenlimit abfragen


Indem wir jeder Anfrage einen Preis zuweisen und den Höchstpreis pro Anfrage angeben, können wir uns vor Eindringlingen schützen, die möglicherweise versuchen, zu viele teure Anfragen gleichzeitig zu erfüllen.


Das GraphQL Cost Analysis- Plugin bietet eine einfache Möglichkeit, die Kosten für Abfragen und das maximale Kostenlimit anzugeben.


Bestimmen Sie die maximalen Kosten:


 app.use( '/graphql', graphqlExpress(req => { return { schema, rootValue: null, validationRules: [ costAnalysis({ variables: req.body.variables, maximumCost: 1000, }), ], } }) ) 

Bestimmen Sie die Kosten für jede Anfrage:


 Query: { Article: { multipliers: ['limit'], useMultipliers: true, complexity: 3, }, } 

Bedrohung: Offenlegung von Implementierungsdetails von GraphQL


Lösung : Deaktivieren Sie die Introspektion im Code "Kampf"


GraphQL ist ein äußerst nützliches Entwicklungswerkzeug. Es ist so leistungsstark, dass es sogar Ihr Schema, Ihre Anfragen und Abonnements für Sie dokumentiert. Diese Informationen können eine Goldmine für Angreifer sein, die Schwachstellen in Ihrer Anwendung finden möchten.


Das GraphQL Display Introspection- Plugin verhindert, dass Ihr Schema an die Öffentlichkeit gelangt . Importieren Sie einfach das Plugin und wenden Sie es auf Ihre Validierungsregeln an.


 import express from 'express'; import bodyParser from 'body-parser'; import { graphqlExpress } from 'graphql-server-express'; + import NoIntrospection from 'graphql-disable-introspection'; const myGraphQLSchema = // ...    ! const PORT = 3000; var app = express(); app.use('/graphql', bodyParser.json(), graphqlExpress({ schema: myGraphQLSchema, validationRules: [NoIntrospection] })); app.listen(PORT); 

Source: https://habr.com/ru/post/de481840/


All Articles