Skip to main content

SQL-Injection-Angriffe in Node.js verhindern

Artikel von
Headshot of Lucien Chemaly

Lucien Chemaly

20. Februar 2024

0 Min. Lesezeit

Mit der zunehmenden Abhängigkeit von Softwaresystemen nehmen auch die Sicherheitsbedrohungen zu. Eine besonders relevante Bedrohung für Entwicklerinnen und Entwickler, insbesondere für diejenigen, die mit Node.js arbeiten, ist SQL-Injection.

Bei einer SQL-Injection-Attacke wird schädlicher SQL-Code in ein System eingeschleust. Dadurch können vertrauliche Informationen offengelegt, Daten beschädigt oder gelöscht und Angreifern mitunter unbefugter Zugriff gewährt werden. Diese Bedrohung abzuwehren, ist schwierig und macht deutlich, wie wichtig es ist, die Sicherheit Ihrer Node.js-Anwendungen zu gewährleisten.

In diesem Artikel erfahren Sie, warum SQL-Injection-Angriffe eine erhebliche Bedrohung darstellen und wie Sie Ihre Node.js-Anwendungen davor schützen können.

SQL-Injection in Node.js-Anwendungen verstehen

SQL-Injection-Angriffe treten vor allem dann auf, wenn eine Node.js-Anwendung Benutzereingaben unsachgemäß verarbeitet. Werden Benutzereingaben ohne angemessene Validierung, Bereinigung oder Abfragekonstruktion in eine SQL-Abfrage eingefügt, entsteht eine Schwachstelle, die Angreifer ausnutzen können, um die Abfrage zu manipulieren. Das kann zu unbefugtem Zugriff oder Datenlecks führen. Wenn Sie alle früheren und aktuellen Schwachstellen für Node.js erkunden möchten, finden Sie eine Liste in der Snyk Vulnerability Database.

Express-Node.js-App mit SQL-Injection-Schwachstelle

Um besser zu verstehen, wie SQL-Injection funktioniert, erstellen wir mit Node.js, Express und einer PostgreSQL-Datenbank schnell eine anfällige App. Die Anwendung verarbeitet Benutzereingaben aus einem Formular, erstellt eine SQL-Abfrage und führt sie in der Datenbank aus, um Daten abzurufen.

Laden Sie zunächst Node.js herunter und installieren Sie es auf Ihrem Computer. Node.js ist die Laufzeitumgebung, in der Ihr JavaScript-Code serverseitig ausgeführt wird.

Erstellen Sie dann ein neues Verzeichnis für Ihr Projekt (d. h. vulnerable-app) und wechseln Sie mit dem Befehl cd vulnerable-app in Ihrem Terminal in dieses Verzeichnis.

Führen Sie in Ihrem Projektverzeichnis den Befehl npm init -y aus. Damit wird eine Datei package.json mit Standardwerten erstellt. Sie dient als Manifestdatei für Ihr Node.js-Projekt.

Installieren Sie Express und die pg-Bibliothek mit dem Befehl npm install express pg. Express ist ein schlankes Webanwendungs-Framework und pg ein Node.js-Client für PostgreSQL.

Laden Sie als Nächstes PostgreSQL herunter und installieren Sie es. Erstellen Sie nach der Installation eine neue Datenbank mit dem Namen vulnerable_db. Erstellen Sie darin eine Tabelle mit dem Namen users und einer Spalte namens name. Der SQL-Befehl dafür lautet:

   CREATE TABLE users (
       id SERIAL PRIMARY KEY,
       name VARCHAR(255)
   );

Ausführliche Informationen zur Installation und Ausführung von PostgreSQL finden Sie in dieser W3Schools-Dokumentation.

Erstellen Sie eine Datei namens app.js und fügen Sie den folgenden Code ein:

const express = require("express");
const { Client } = require("pg");
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json()); // To handle JSON input
const client = new Client({
  host: "your-host",
  database: "your-database",
  username: "your-username",
  password: "your-password",
  port: "your-port",
});
client.connect();
app.post("/search", (req, res) => {
  const userInput = req.body.userInput;
  const query = "SELECT * FROM users WHERE name = '" + userInput + "';";
  client.query(query, (err, result) => {
    if (err) throw err;
    res.send(result.rows);
  });
});
app.post("/create", (req, res) => {
  const { name } = req.body;
  if (!name) {
    res.status(400).send("Name is required");
    return;
  }
  const query = "INSERT INTO users (name) VALUES ($1) RETURNING *;";
  client.query(query, [name], (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).send("Internal Server Error");
      return;
    }
    res.send(result.rows[0]); // Respond with the created user record
  });
});
app.listen(3000, () => {
  console.log("Server is running on port 3000");
});

Dieser Code initialisiert einen Express-Server und richtet mithilfe der pg-Bibliothek Verbindungen zu einer PostgreSQL-Datenbank ein. Außerdem wird der Server so konfiguriert, dass er URL-kodierte und JSON-Eingaben verarbeitet.

Es werden zwei Routen definiert: eine POST /search-Route und eine POST /create-Route. Die Route /search übernimmt Benutzereingaben aus dem Request-Body, erstellt eine SQL-Abfrage und führt sie in der Datenbank aus. Anschließend gibt sie das Ergebnis zurück. Aufgrund der Verkettung von Benutzereingaben in der Abfrage ist sie jedoch anfällig für SQL-Injection. Die Route /create extrahiert einen Wert name aus dem Request-Body, prüft, ob er vorhanden ist, und erstellt dann eine sichere parametrisierte SQL-Abfrage, um einen neuen Benutzerdatensatz in die Datenbank einzufügen. Anschließend gibt sie den erstellten Datensatz zurück. Zum Schluss startet der Server und überwacht Port 3000. Eine Konsolenmeldung zeigt an, dass er bereit ist.

Hinweis: Ersetzen Sie your-username, your-password, your-database, your-host und your-port durch die Konfigurationswerte, die Sie für PostgreSQL festgelegt haben.

Da die Eingaben nicht validiert und bereinigt werden, ist der Code anfällig für SQL-Injection. Ein Angreifer kann beispielsweise den Wert ' OR '1'='1'; -- in das Formular eingeben. Diese Eingabe beendet die ursprüngliche Abfrage und ändert sie in SELECT * FROM users WHERE name = '' OR '1'='1'; --';. Dadurch werden alle Datensätze aus der Tabelle users zurückgegeben, da '1'='1' immer wahr ist. Dies ist ein klassisches Beispiel für SQL-Injection durch Zeichenfolgenverkettung.

Nachdem alles eingerichtet ist, können Sie Ihren Server starten. Führen Sie dazu im Terminal den Befehl node app.js aus. Dadurch wird der Server gestartet. Die Ausgabe Server is running on port 3000 sollte anzeigen, dass der Server erfolgreich läuft:

Terminal mit einer gestarteten Node.js-App: node app.js und der Meldung „Server läuft auf Port 3000.“

Um die Anwendung zu testen, müssen Sie über Ihr Terminal oder Ihre Shell einige Benutzer erstellen:

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User1"}'

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User2"}'

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User3"}'

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User4"}'

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User5"}'

Wenn Sie anschließend nach einem bestimmten Benutzer suchen möchten, etwa zur Benutzerverifizierung, zur Aktualisierung von Daten oder für Audits und Monitoring, müssen Sie eine Suchanfrage nach den Benutzerdaten stellen. Führen Sie den folgenden Befehl aus:

curl -X POST http://localhost:3000/search -H "Content-Type: application/json" -d '{"userInput":"User1"}'

Die Ausgabe sollte etwa so aussehen:

[{"id":1,"name":"User1"}]

Fügen Sie nun SQL-Injection hinzu, um mit dem folgenden Befehl alle Benutzer abzurufen:

curl -X POST http://localhost:3000/search -H "Content-Type: application/json" -d "{\"userInput\":\"' OR '1'='1'; -- \"}"

Die Ausgabe sollte etwa so aussehen:

[{"id":1,"name":"User1"},{"id":2,"name":"User2"},{"id":3,"name":"User3"},{"id":4,"name":"User4"},{"id":5,"name":"User5"},{"id":6,"name":"User2"},{"id":7,"name":"User2"},{"id":8,"name":"User3"},{"id":9,"name":"User4"},{"id":10,"name":"User5"}]

In diesem Befehl ist die JSON-Datenstruktur von äußeren doppelten Anführungszeichen (" ") umschlossen, sodass eine vollständige JSON-Zeichenfolge entsteht. Die Anführungszeichen innerhalb der JSON-Daten werden mit einem Backslash (\) maskiert, damit sie korrekt als Teil der JSON-Zeichenfolge behandelt werden. Der Wert von userInput ist ' OR '1'='1'; -- . Wie zuvor erklärt, manipuliert dieser Wert die SQL-Abfrage so, dass alle Benutzerdatensätze zurückgegeben werden, indem die Abfragebedingung so verändert wird, dass sie immer wahr ist.

So verhindern Sie SQL-Injection-Schwachstellen in Node.js

Sie wissen nun, wie leicht SQL-Injection-Angriffe auftreten können. Lernen Sie jetzt einige Möglichkeiten kennen, wie Sie Ihre App davor schützen und sie für Ihre Benutzer sicher und robust machen.

Abfrage-Platzhalter verwenden

Um SQL-Injection zu verhindern, sollten Sie SQL-Abfragen nicht durch Zeichenfolgenverkettung erstellen. Angreifer können Abfragen manipulieren, indem sie über Eingabefelder schädlichen SQL-Code einschleusen. Verwenden Sie stattdessen Abfrage-Platzhalter, damit Benutzereingaben als Daten und nicht als ausführbarer Code behandelt werden:

app.post('/search', (req, res) => {
    const userInput = req.body.userInput;
    const query = "SELECT * FROM users WHERE name = $1;";
    client.query(query, [userInput], (err, result) => {
        if (err) throw err;
        res.send(result.rows);
    });
});

Prepared Statements verwenden

Eine der wirksamsten Methoden, SQL-Injection-Angriffe zu verhindern, ist die Verwendung von Prepared Statements. Sie stellen sicher, dass Benutzereingaben immer als Daten und nicht als ausführbarer Code behandelt werden.

So passen Sie Ihre /search-Route an, damit sie mit der pg-Bibliothek Prepared Statements verwendet:

app.post('/search', (req, res) => {
    const userInput = req.body.userInput;
    const query = "SELECT * FROM users WHERE name = $1;";
    const preparedQuery = {
        text: query,
        values: [userInput]
    };
    client.query(preparedQuery, (err, result) => {
        if (err) throw err;
        res.send(result.rows);
    });
});

In diesem Code ist das Prepared Statement als Objekt mit einer text-Eigenschaft für die SQL-Abfrage und einer values-Eigenschaft für die Benutzereingabe definiert. Durch diese Trennung können Sie die SQL-Abfragevorlage einmal festlegen und bei jeder Ausführung des Prepared Statements unterschiedliche Werte für den Platzhalter angeben. So lassen sich Benutzereingaben in SQL-Abfragen sicherer und effizienter verarbeiten.

Benutzereingaben validieren und bereinigen

Die Überprüfung von Benutzereingaben ist entscheidend, um sicherzustellen, dass sie bestimmte Anforderungen erfüllen, etwa an Format, Länge oder Wertebereich. Validierungsbibliotheken wie joi oder express-validator können diesen Prozess vereinfachen. Sie bieten vordefinierte Validierungsregeln und Funktionen, mit denen sich Anforderungen an Eingaben leichter durchsetzen und Validierungen automatisieren lassen.

Ebenso wichtig ist es, Eingaben zu bereinigen, indem potenziell schädliche Zeichen entfernt oder maskiert werden. So bleibt die Anwendungsumgebung sicher und robust. Bei der Bereinigung von Eingaben werden Benutzereingaben so aufbereitet, dass sie bei der Verarbeitung durch die Anwendung weder Schaden noch unerwartetes Verhalten verursachen.

So setzen Sie Validierung und Bereinigung mit der Bibliothek express-validator um:

 const { body, validationResult } = require('express-validator');

app.post("/search", [
  body('userInput').trim().escape().isAlphanumeric(),
], (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }

  const userInput = req.body.userInput;
  const query = "SELECT * FROM users WHERE name = $1;";

  client.query(query, [userInput], (err, result) => {
      if (err) throw err;
      res.send(result.rows);
  });
});

Aktuelle Versionen von ORM- und SQL-Bibliotheken verwenden

Achten Sie stets darauf, die aktuellen Versionen Ihrer ORM- und SQL-Bibliotheken zu verwenden. Die Maintainer dieser Bibliotheken veröffentlichen regelmäßig Updates und Patches, um bekannte Sicherheitslücken zu schließen:

npm install pg@latest

Code regelmäßig überprüfen und Sicherheits-Audits durchführen

Führen Sie regelmäßig Code-Reviews und Sicherheits-Audits durch, um Schwachstellen frühzeitig zu erkennen. Nutzen Sie Tools wie npm audit oder snyk, um bekannte Schwachstellen in Ihren Abhängigkeiten automatisiert aufzuspüren.

Schwachstellen mit der Snyk-IDE-Erweiterung für Visual Studio Code erkennen

Snyk ist ein speziell für Entwickler entwickeltes Sicherheitstool, das dabei hilft, Schwachstellen im Code und in Open-Source-Abhängigkeiten zu erkennen und zu beheben. Es lässt sich nahtlos in den Entwicklungsworkflow integrieren und überwacht und scannt kontinuierlich nach Sicherheitslücken, darunter auch solche, die mit SQL-Injection zusammenhängen.

Snyk erkennt nicht nur Schwachstellen, sondern erstellt auch automatisierte Pull Requests mit Korrekturen. Das erleichtert die Behebung und verhindert potenzielle Sicherheitsprobleme. Snyk lässt sich in verschiedene Entwicklungsplattformen wie GitHub, GitLab und Bitbucket integrieren. Außerdem gibt es eine spezielle Erweiterung für Visual Studio Code, die während des Programmierens Feedback in Echtzeit liefert.

Eine Snyk-Erweiterung einrichten

Die Einrichtung einer Snyk-Erweiterung ist ganz einfach. Laden Sie zunächst Visual Studio Code herunter und installieren Sie es, falls noch nicht geschehen. Starten Sie Visual Studio Code nach der Installation und öffnen Sie die Ansicht Erweiterungen. Drücken Sie dazu unter Windows Ctrl + Shift + X oder unter macOS Command + Shift + X.

Geben Sie in der Ansicht Erweiterungen in der Suchleiste „Snyk“ ein und klicken Sie bei der Snyk-Erweiterung auf Installieren:

Visual Studio Code Marketplace mit einer Suche nach „snyk“ und der Snyk Security-Erweiterung mit Installieren-Schaltflächen.

Nach der Installation sollte die Erweiterung Snyk in der linken Navigationsleiste von Visual Studio Code angezeigt werden. Klicken Sie darauf und anschließend auf Arbeitsbereich vertrauen und verbinden. Melden Sie sich dann bei Ihrem Snyk-Konto an, um die Erweiterung zu verwenden. Falls Sie noch kein Snyk-Konto haben, können Sie kostenlos eines erstellen:

Visual Studio Code mit der Snyk-Erweiterung: Die Schaltfläche „Arbeitsbereich vertrauen und verbinden“ ist hervorgehoben, daneben werden Details zum Schwachstellen-Scanner angezeigt.

Anschließend werden Sie aufgefordert, sich anzumelden und zu authentifizieren. Danach sollte die Meldung Authentifiziert erscheinen:

Snyk-Seite zur Bestätigung der Authentifizierung mit der Meldung „Authentifiziert“ und „Ihr Konto wurde authentifiziert. Snyk kann jetzt verwendet werden.“

Nach der Installation und Konfiguration scannt die Snyk-Erweiterung Ihr Projekt automatisch auf bekannte Schwachstellen, darunter auch mögliche SQL-Injection-Risiken.

Rufen Sie Ihr Konto auf der Snyk-Plattform auf und folgen Sie den Anweisungen, um Snyk Code zu aktivieren. Die Aktivierung von Snyk Code ist wichtig, damit Snyk Schwachstellen im Code wie SQL-Injections erkennen kann.

Wenn die Option Snyk Code in Ihrem Snyk-Konto nicht aktiviert ist, erkennt die Snyk-Erweiterung in Visual Studio Code keine SQL-Injections, die auf schlechte Programmierpraktiken zurückzuführen sind. Sie erkennt dann lediglich Schwachstellen wie veraltete Bibliotheken.

Snyk beschränkt sich nicht darauf, Schwachstellen zu erkennen, sondern bietet auch Vorschläge und Referenzen zu ihrer Behebung. Das kann besonders bei großen Codebasen oder knappen Zeitplänen eine große Hilfe sein.

Den gesamten Code für dieses Tutorial finden Sie im folgenden GitHub-Repository.

Fazit

In diesem Artikel haben Sie erfahren, wie SQL-Injections in Node.js-Anwendungen entstehen, und verschiedene Strategien zu ihrer Vermeidung kennengelernt. Von der Aktualisierung Ihrer ORM- und SQL-Bibliotheken und der Bereinigung von Benutzereingaben über die Verwendung von Abfrage-Platzhaltern bis hin zum Einsatz der Snyk-IDE-Erweiterung für Visual Studio Code stehen Ihnen zahlreiche Maßnahmen zur Verfügung, um Ihre Node.js-Anwendungen vor SQL-Injection-Angriffen zu schützen.

Die Snyk-Erweiterung hilft nicht nur dabei, SQL-Injection-Schwachstellen zu erkennen, sondern zeigt auch, wie Sie sie beheben können – wie ein virtueller Sicherheitsmentor. Wenn Sie Snyk in Ihren Entwicklungsworkflow integrieren, reagieren Sie nicht nur auf Sicherheitsbedrohungen, sondern beugen ihnen proaktiv vor. So bleiben Ihre Anwendungen robust und sicher, bevor sie in die Produktion gehen.

Denken Sie bei Ihrer weiteren Arbeit mit Node.js daran: Sicherheit ist keine einmalige Aufgabe, sondern ein fortlaufender Prozess. Bleiben Sie über die neuesten Best Practices für Sicherheit auf dem Laufenden, lernen Sie kontinuierlich dazu und schützen Sie Ihre Anwendungen. Ihre Nutzer werden es Ihnen danken, und Sie können beruhigt sein, weil Ihre Daten und die Ihrer Nutzer sicher sind.

Starten Sie mit Capture-the-Flag

Erfahren Sie, wie Sie Capture-the-Flag-Herausforderungen lösen – mit unserem jederzeit verfügbaren virtuellen 101-Workshop.

Weiterlesen

blog feature ai lilac
Blog

Mit agentischer Behebung Ihren Schwachstellen-Backlog in Pull Requests verwandeln

Der Evo Remediation Agent ist jetzt in der öffentlichen Vorschau verfügbar und erweitert Evo Agentic AppSec um die automatisierte Auflösung von Abhängigkeiten. AppSec-Teams können jetzt kontinuierliche Behebungskampagnen durchführen

AI Background min crop
Blog

KI ruft alle Risiken auf den Plan, die Sie akzeptiert haben: Sieben Erkenntnisse aus dem AI Security Panel: Unfiltered

KI macht Angriffe günstiger und schneller und legt Risiken offen, die Unternehmen bereits tragen. Vier Sicherheitsverantwortliche erklären, warum Abwehr in Maschinengeschwindigkeit und schnellere Behebung jetzt unverzichtbar sind.

blog feature ai violet
Blog

VulnBump: Können KI-Agenten anfällige Abhängigkeiten beheben, ohne den Build zu beschädigen?

VulnBump testet KI-Agenten anhand von 307 realen Upgrades von Abhängigkeiten. Eine Behebung zählt nur, wenn die Sicherheitslücke geschlossen ist und Build und Tests weiterhin erfolgreich sind.