Richtlinien

SQL-Injection

Es ist an der Zeit, sich mit SQL-Injection zu befassen. Lange Zeit war sie der unangefochtene Spitzenreiter der OWASP Top 10 – und das über Jahre hinweg. Obwohl die Schwachstelle bereits über 20 Jahre alt ist und in der Liste mittlerweile leicht vom ersten Platz verdrängt wurde, bleibt sie eine unglaublich verbreitete und gefährliche Bedrohung.

Als Web-Sicherheitslücke ist SQL-Injection (SQLi) nach wie vor eine der am häufigsten von Angreifern genutzten „Hacking“-Techniken, da sie es ermöglicht, Datenbanken zu manipulieren und sensible Informationen zu extrahieren. Noch alarmierender ist, dass sich ein Angreifer Administratorrechte für den Datenbankserver verschaffen und verheerende Schäden anrichten kann, wie etwa das Löschen von Datenbanken, die Manipulation von Transaktionen, die Offenlegung von Daten oder die Schaffung weiterer Sicherheitslücken.

Werfen wir einen kurzen Blick darauf, wie das passiert.

SQL (Structured Query Language) ist die Sprache, die für die Kommunikation mit relationalen Datenbanken verwendet wird. Sie ist die Abfragesprache, mit der Entwickler, Datenbankadministratoren und Anwendungen die riesigen Datenmengen verwalten, die täglich generiert werden.

Innerhalb einer Anwendung gibt es zwei Kontexte: einen für Daten und einen für Code. Der Code-Kontext weist Computer an, was ausgeführt werden soll, und trennt dies von den zu verarbeitenden Daten. Eine SQL-Injection tritt auf, wenn ein Angreifer Daten eingibt, die vom SQL-Interpreter fälschlicherweise als Code interpretiert werden, wodurch er wertvolle Informationen aus der Anwendung abgreifen kann.

Auswirkungen eines SQL-Injection-Angriffs

Eine SQL-Injection kann für jede Webanwendung extrem schädlich sein. Sie ist die bevorzugte Technik hinter zahlreichen hochkarätigen Sicherheitsvorfällen, da sie Angreifern unbefugten Zugriff auf kritische Daten verschafft. Sie können eine Vielzahl von Informationen einsehen, von Benutzernamen und Passwörtern bis hin zu Kreditkartendaten und persönlichen Identifikationsnummern.

Nachdem sie Zugriff auf diese Daten erlangt haben, können Angreifer Konten übernehmen, Passwörter zurücksetzen, ausgiebige Online-Shopping-Touren unternehmen oder andere (weitaus schlimmere) Arten von Betrug begehen.

Das vielleicht Alarmierendste an SQLi ist jedoch, dass ein Angreifer bei unentdecktem Zugriff über lange Zeiträume hinweg eine Hintertür im System offen halten kann. Wie Sie sich vorstellen können, führt dies zu wiederholten Datenpannen, solange diese Hintertür besteht. Ein beängstigendes Szenario.

Schauen wir uns einige Beispiele an, um besser zu verstehen, wie das in der Praxis aussieht.

SQLi-Beispiele

SQLi umfasst verschiedene Schwachstellentechniken für unterschiedliche Szenarien. Im Folgenden finden Sie einige der häufigsten SQLi-Beispiele:

Technique Description
Retrieving Hidden Data With this technique, attackers can modify any SQL query to gather more information from the database.
Data Examination Attackers can extract information about the version and structure of a database which helps them exploit further information. This technique may differ across databases.
Union Attacks Attackers can extract information about the version and structure of a database which helps them exploit further information. This technique may differ across databases.
Blind SQLi With Blind SQLi, attackers can implement the query on a database. The catch is that the attackers control this query, and it does not return any results in the application’s response.
Subverting Application Logic Attackers interfere with or manipulate a query to disrupt the application’s logic. To manipulate the query, attackers can combine the SQL comment sequence “--” and a WHERE clause.

‍

SQLi-Typen

Schauen wir uns nun die drei verschiedenen SQLi-Typen an.

In-Band SQLi

Dies ist eine der häufigsten, einfachsten und effizientesten Arten der SQL-Injection. Bei dieser Angriffsart wird derselbe Kommunikationskanal sowohl für den Angriff als auch für den Abruf der Ergebnisse verwendet.

Im Folgenden werden die zwei Arten von In-Band-SQLi-Angriffen erläutert:

  • Union-based SQLi – Der Union-basierte Angriff nutzt den UNION-Operator, um zwei oder mehr SQL-Abfragen, wie z. B. SELECT-Anweisungen, zu kombinieren, um die gewünschten Informationen und Ergebnisse in einer HTTP-GET-Antwort zu erhalten.
  • ‍Error-based SQLi – Der Angreifer nutzt die Fehlermeldungen der Datenbank, um deren Struktur zu verstehen. Bei diesem Angriff sendet der Angreifer möglicherweise fehlerhafte Anfragen oder führt Aktionen aus, die den Server dazu veranlassen, Fehlermeldungen anzuzeigen, damit er Datenbankinformationen erhält. Deshalb ist es wichtig, dass Entwickler vermeiden, Fehler oder Protokollmeldungen in der Live-Umgebung auszugeben; stattdessen sollten diese mit eingeschränktem Zugriff gespeichert werden.

Inferentiales SQLi

Inferentiale oder „blinde“ SQLi-Angriffe sind komplexer und ihre Ausführung kann mehr Zeit in Anspruch nehmen. Zudem erhält der Angreifer die Ergebnisse nicht unmittelbar, was den Angriff zu einem „blinden“ Angriff macht.

Der Angreifer sendet Payloads über HTTP-Anfragen an den Datenbankserver, um die Datenbank des Benutzers umzustrukturieren. Anschließend beobachtet er die Antwort und das Verhalten der Anwendung, um festzustellen, ob der Angriff erfolgreich war.

Es gibt zwei Arten von inferentiellen SQLi-Angriffen:

  • Boolean-basierte Blind SQLi – Bei diesem Angriff wird eine Abfrage an die Datenbank gesendet, die ein boolesches Ergebnis (wahr oder falsch) liefert. Der Angreifer beobachtet die HTTP-Antwort, um auf das boolesche Ergebnis zu schließen.
  • Zeitbasierte Blind SQLi – Hierbei sendet der Angreifer eine Abfrage an die Datenbank, die diese dazu veranlasst, vor dem Senden der Antwort einige Sekunden zu warten. Der Angreifer leitet die Abfrageergebnisse aus der Antwortzeit der HTTP-Anfrage ab.

Out-of-Band SQLi

Dies ist eine seltenere Art von SQLi-Angriff, die von den aktivierten Funktionen des Datenbankservers abhängt. Sie tritt in Fällen auf, in denen der Angreifer die anderen Angriffstypen nicht nutzen kann.

Zum Beispiel, wenn er denselben Kommunikationskanal nicht für einen In-Band-Angriff verwenden kann oder die HTTP-Antwort nicht eindeutig genug ist, um die Abfrageergebnisse zu ermitteln.
Darüber hinaus ist diese Methode eher unüblich, da sie stark darauf angewiesen ist, dass der Datenbankserver HTTP- oder DNS-Anfragen ausführen kann, um die erforderlichen Daten an den Angreifer zu übermitteln.

Schutz vor SQLi

Der Vorteil daran, dass SQL-Injection ein so altes und verbreitetes Problem ist, besteht glücklicherweise darin, dass es bewährte Methoden zu deren Verhinderung gibt. Die Anwendung solcher Schutzmaßnahmen ist nicht nur eine gute Programmierpraxis, sondern stärkt die Sicherheit eines Unternehmens gegen SQLi erheblich.

Es gibt verschiedene Möglichkeiten, Datenbankserver gegen diese Art von Angriffen abzusichern, wie etwa Eingabevalidierung, den Einsatz einer Web Application Firewall (WAF), die Härtung von Datenbanken, die Beauftragung externer Sicherheitsteams oder -systeme sowie das Schreiben manipulationssicherer SQL-Abfragen.

Schauen wir uns ein Beispiel für die Vermeidung von SQL-Injections in Python an, bei dem eine der oben genannten Sicherheitsmaßnahmen angewendet wird.

Python-Beispiel

In diesem Beispiel nutzt der Angreifer eine Boolean-basierte Blind SQL-Injection, um wichtige Informationen aus dem System abzugreifen.

Python: Anfällig

Angenommen, es gibt eine Tabelle namens „sample_data“ in der Datenbank. Diese Tabelle speichert Benutzernamen und Passwörter für die Anwendungsbenutzer.

Ermöglichen wir nun dem Benutzer, einen Wert aus dieser Datenbanktabelle über die folgenden Befehle zu finden:

import mysql.connector
db = mysql.connector.connect
#Schlechte Praxis. Vermeiden Sie dies! Nur zu Lernzwecken.
(host="localhost", user="newuser", passwd="pass", db="sample")
cur = db.cursor()
name = raw_input('Enter Name: ')
cur.execute("SELECT * FROM sample_data WHERE Name = '%s';" % name) for row in cur.fetchall(): print(row)
db.close()

SQL-Injection

Wenn der Benutzer hier einen Namen in die Suche eingibt, zum Beispiel Alicia, gibt es bei der Ausgabe keine Probleme.

Wenn der Benutzer jedoch etwas wie Alicia’; DROP TABLE sample_data; eingibt, hat dies schwerwiegende Auswirkungen auf die Datenbank.

Python: Behebung

Das SQL-Statement sollte wie folgt geändert werden, um den Angriff zu verhindern:

cur.execute("SELECT * FROM sample_data WHERE Name = %s;", (name,))

Das System behandelt die Benutzereingabe nun als Zeichenfolge, selbst wenn der Benutzer versucht, SQL-Abfragen einzuschleusen, und wertet die Eingabe ausschließlich als Wert für den Namen aus.

Diese einfache Änderung kann böswillige Aktivitäten in zukünftigen Abfragen verhindern und das System vor Angriffen durch Benutzereingaben schützen.

Java-Beispiel

Für dieses Beispiel verwenden wir ebenfalls eine Datenbanktabelle namens „sample_data“, in der die Benutzerdaten der Anwendung gespeichert sind.

Eine einfache Anmeldeseite nimmt einen Benutzernamen und ein Passwort entgegen. Die Java-Datei, ein Servlet (LoginServlet), validiert diese gegen die Datenbank, um den Anmeldevorgang zu ermöglichen.

Java: Anfälliges Beispiel

Unter Verwendung der Tabelle „sample_data“ in der Datenbank ermöglicht das System Benutzern die Anmeldung, indem es ihre Anmeldedaten als Eingabe entgegennimmt.

In der Datei LoginServlet befindet sich eine Abfrage für den Anmeldevorgang, die wie folgt aussieht:

//Schlechtes Beispiel. Verwenden Sie keine String-Verkettung.
String query = "select * from sample_data where username='" + username + "' and password = '" + password + "'";
        Connection conn = null;
        Statement stmt = null;
        try {
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/user", "root", "root");
            stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery(query);
            if (rs.next()) {
                // Anmeldung erfolgreich, wenn eine Übereinstimmung gefunden wurde
                success = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                stmt.close();
                conn.close();
            } catch (Exception e) {}
        }
        if (success) {
            response.sendRedirect("home.html");
        } else {
            response.sendRedirect("login.html?error=1");
        }
    }

Dies ist die Abfrage für die Benutzeranmeldung:

select * from sample_data where username='username' and password ='password'

SQL-Injection

Das System funktioniert einwandfrei, wenn die Eingabe gültig ist. Nehmen wir zum Beispiel wieder an, der Benutzername ist Alicia und das Passwort lautet secret.

Das System gibt die Daten des Benutzers mit diesen Anmeldedaten zurück. Ein Angreifer kann jedoch die Benutzeranfrage mithilfe von Postman und cURL für eine SQL-Injection manipulieren.

Der Hacker kann beispielsweise einen Dummy-Benutzernamen (Alicia) und das Passwort „or „1“=“1“ senden.

In diesem Fall stimmen Benutzername und Passwort zwar nicht überein, aber die Bedingung „1“=“1“ ist immer wahr, sodass der Anmeldevorgang erfolgreich ist.

Java: Prävention

Zur Prävention müssen wir den LoginValidation-Code anpassen und anstelle von Statement ein PreparedStatement für die Abfrageausführung verwenden. Diese Änderung verhindert die Verkettung von Benutzername und Passwort innerhalb der Abfrage und behandelt sie stattdessen als Setter-Daten, um SQL-Injection zu vermeiden.

Nachfolgend finden Sie den angepassten Code für LoginValidation:

String query = "select * from sample_data where username=? and password = ?";
Connection conn = null;
PreparedStatement stmt = null;
try {
    conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/user", "root", "root");
    stmt = conn.prepareStatement(query);
    stmt.setString(1, username);
    stmt.setString(2, password);
    ResultSet rs = stmt.executeQuery();
    if (rs.next()) {
       success = true;
       }
      rs.close();
      } catch (Exception e) {
         e.printStackTrace();
         } finally {
             try {
                 stmt.close();
                 conn.close();
            } catch (Exception e) {
            }
         }

In diesem Fall kümmern sich das PreparedStatement, die Setter und die zugrunde liegende JDBC-API um die Benutzereingaben und verhindern so SQL-Injection.

Beispiele

Wir sehen uns nun einige weitere Beispiele in verschiedenen Sprachen an, um besser zu verstehen, wie dies in der Praxis aussieht.

C# – Unsicher

Dieses Beispiel ist unsicher, da es `FromRawSql` verwendet. Diese Methode bindet die Parameter nicht und versucht auch nicht, sie zu maskieren. Daher sollte diese Methode unter allen Umständen vermieden werden.

var blogs = context.Posts
    .FromRawSql("SELECT * FROM Posts WHERE state = {0} AND author = {1}", state, author)
    .ToList();

C# – Sicher

Dieses Beispiel ist sicher, da `FromSqlInterpolated` verwendet wird, das die interpolierten Werte übernimmt und parametrisiert.

Obwohl dies im Allgemeinen sicher ist, besteht das Risiko, dass es `FromRawSql` sehr ähnlich sieht, was nicht sicher ist.

var blogs = context.Posts
    .FromSqlInterpolated($"SELECT * FROM Posts WHERE state = {state} AND author = {author}")
.ToList();

Java - Sicher: Hibernate - Named Query + Native Query

Hibernate bietet zwei Methoden zur sicheren Erstellung von Abfragen durch `Native Query` und `Named Query`. Beide ermöglichen die Angabe von Platzhaltern für Parameter.

@NamedNativeQuery(
        name = "find_post_by_state_and_author",
        query =
        "SELECT * " +
                "FROM Post " +
                "WHERE state = :state" + 
         " AND author = :author",
        resultClass = Post.class)

java
List<Post> posts = session.createNativeQuery(
        "SELECT * " +
        "FROM Post " +
        "WHERE state = :state" +
        " AND author = :author" )
        .addEntity(Post.class)
        .setParameter("state", state)
        .setParameter("author", author)
        .list();

Java - Sicher: jplq

Durch die Annotation eines `Query`-Attributs an einer jplq-Repository-Schnittstelle können diese verschiedene Formen annehmen und sind parametrisiert.

@Query("SELECT p FROM Post p WHERE u.state = ?1 and u.author = ?2")
Post findPostByStateAndAuthor(String state, int author);
@Query("SELECT p FROM Post p WHERE u.state = :state and u.author = :author")
User findPostByStateAndAuthor(@Param("state") String state, @Param("author") int author);

Javascript - Sicher: pg

Bei Verwendung der `pg`-Bibliothek ermöglicht die `query`-Methode eine Parametrisierung, indem Parameterwerte über den zweiten Parameter übergeben werden.

const { posts } = await db.query('SELECT * FROM Post WHERE state = $1 AND author = $2', [state, author])

Javascript – Sicher: Sequelize

Die Bibliothek `sequelize` bietet die Möglichkeit, Abfragen über ihr zweites Argument zu parametrisieren, das die Einstellungen für die Abfrage entgegennimmt. Dies umfasst eine Liste von Werten, die entweder namentlich oder über einen Index als Parameter an die Abfrage gebunden werden.

await sequelize.query(
    'SELECT * FROM Post WHERE state = $state AND author = $author',
    {
        bind: { state: state, author: author},
        type: QueryTypes.SELECT
    }
);
await sequelize.query(
    'SELECT * FROM Post WHERE state = $1 AND author = $2',
    {
        bind: [state, author],
        type: QueryTypes.SELECT
    }
);
Slogan

Sichere Programmierung in der Praxis

Entdecken Sie Richtlinien für sicheres Programmieren, um Schwachstellen wie die OWASP Top 10 zu verstehen und zu beheben, und tauchen Sie in geführte Trainingsmissionen ein, um diese in realistischen Anwendungssimulationen zu üben.

Weitere Richtlinien
Demo buchen