SQL-Einschleusung
Es ist an der Zeit, sich mit SQL-Injection zu befassen. Lange Zeit war sie der unangefochtene König der OWASP Top 10, und das seit Jahren in Folge. Obwohl sie schon so alt ist (über 20 Jahre) und der Spitzenplatz auf der Liste etwas verloren gegangen ist, ist sie immer noch eine unglaublich beliebte und gefährliche Sicherheitslücke.
SQL-Injection (SQLi), eine Sicherheitslücke im Internet, ist nach wie vor eine der am häufigsten von Angreifern genutzten "Hacking"-Techniken, da sie es ihnen ermöglicht, eine Datenbank zu manipulieren und wichtige Informationen aus ihr zu extrahieren. Noch beunruhigender ist, dass ein Angreifer sich selbst zum Administrator des Datenbankservers machen und verheerende Dinge tun kann, wie die Zerstörung von Datenbanken, die Manipulation von Transaktionen, die Offenlegung von Daten und die Anfälligkeit für weitere Probleme.
Werfen wir einen kurzen Blick darauf, wie das geschieht
SQL (oder Structured Query Language) ist die Sprache, die für die Kommunikation mit relationalen Datenbanken verwendet wird; es ist die Abfragesprache, die von Entwicklern, Datenbankadministratoren und Anwendungen verwendet wird, um die riesigen Datenmengen zu verwalten, die jeden Tag erzeugt werden.
Innerhalb einer Anwendung gibt es zwei Kontexte: einen für Daten und einen für Code. Der Code-Kontext sagt dem Computer, was er ausführen soll, und trennt ihn von den zu verarbeitenden Daten. Eine SQL-Injektion liegt vor, wenn ein Angreifer Daten eingibt, die vom SQL-Interpreter fälschlicherweise als Code behandelt werden, wodurch er wertvolle Informationen aus der Anwendung gewinnen kann.
Auswirkungen eines SQL-Injection-Angriffs
Eine SQL-Injektion kann für jede Webanwendung extrem schädlich sein und war die bevorzugte Technik hinter so vielen aufsehenerregenden Sicherheitsverletzungen, da sie Angreifern unbefugten Zugang zu wichtigen Daten verschafft. Sie können so viele Informationen einsehen, von Dingen wie Benutzernamen und Kennwörtern bis hin zu Kreditkartendaten und persönlichen Identifikationsnummern.
Nachdem sie sich Zugang zu diesen Daten verschafft haben, können Angreifer Konten übernehmen, Passwörter zurücksetzen, einen ausgedehnten Online-Einkaufsbummel machen oder andere (viel schlimmere) Arten von Betrug begehen.
Das vielleicht Beunruhigendste an SQLi ist jedoch, dass ein Angreifer, wenn er unentdeckt bleibt, über lange Zeiträume eine Hintertür in das System offen halten kann. Wie Sie sich vorstellen können, würde dies zu wiederholten Datenverletzungen führen, egal wie lange die Hintertür offen gehalten wird. Beängstigende Sache.
Schauen wir uns ein paar Beispiele an, um besser zu verstehen, wie dies in der Praxis aussieht.
SQLi-Beispiele
SQLi umfasst verschiedene Anfälligkeitstechniken, mit denen unterschiedliche Situationen angegangen werden können. Im Folgenden sind nur einige der häufigsten SQLi-Beispiele aufgeführt:
SQLi-Typen
Okay, 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-Injektion. Bei dieser Art von Angriff wird derselbe Kommunikationskanal für den Angriff und das Abrufen der Ergebnisse verwendet.
Im Folgenden werden die beiden Arten von In-Band-SQLi-Angriffen beschrieben:
- Union-basierter 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-basedSQLi - Der Angreifer nutzt die Fehlermeldungen der Datenbank, um deren Struktur zu verstehen. Bei diesem Angriff kann der Angreifer falsche Anfragen senden oder Aktionen durchführen, um den Server dazu zu bringen, Fehlermeldungen anzuzeigen, damit er Datenbankinformationen erhalten kann. Aus diesem Grund ist es wichtig, dass Entwickler das Senden von Fehlermeldungen oder Protokollmeldungen in der Live-Umgebung vermeiden; stattdessen sollten sie mit eingeschränktem Zugriff gespeichert werden.
Inferentielle SQLi
Inferentielle oder blinde SQLi-Angriffe sind komplizierter und können mehr Zeit in Anspruch nehmen. Außerdem erhält der Angreifer die Ergebnisse des Angriffs nicht sofort, was ihn zu einem blinden Angriff macht.
Der Angreifer sendet die Nutzdaten über HTTP-Anfragen an den Datenbankserver, um die Datenbank des Benutzers umzustrukturieren, und beobachtet dann die Antwort und das Verhalten der Anwendung, um festzustellen, ob der Angriff erfolgreich war oder nicht.
Dies sind zwei Arten von inferentiellen SQLi-Angriffen:
- Boolesches Blind-SQLi - Bei diesem Angriff wird eine Abfrage an die Datenbank gesendet, um ein boolesches Ergebnis (wahr oder falsch) zu erhalten, und der Angreifer beobachtet die HTTP-Antwort, um das boolesche Ergebnis vorherzusagen.
- Zeitbasiertes Blind SQLi - Bei diesem Angriff sendet der Angreifer eine Abfrage an die Datenbank, um sie einige Sekunden warten zu lassen, bevor er die Antwort sendet, und der Angreifer beurteilt die Abfrageergebnisse anhand der Antwortzeit der HTTP-Anfrage.
Out-of-band SQLi
Dies ist eine seltenere Art des SQLi-Angriffs, die von den aktivierten Funktionen des Datenbankservers abhängt. Er kommt in Fällen vor, in denen der Angreifer die anderen Angriffsarten nicht wirklich nutzen kann.
Zum Beispiel, wenn er nicht denselben Kommunikationskanal für den In-Band-Angriff nutzen kann oder die HTTP-Antwort nicht eindeutig genug ist, um die Abfrageergebnisse zu ermitteln.
Außerdem ist dieser Angriff nicht sehr verbreitet, da er stark von der Fähigkeit des Datenbankservers abhängt, HTTP- oder DNS-Anfragen zu stellen, um die erforderlichen Daten an den Angreifer zu senden.
Wie man sich gegen SQLi verteidigt
Glücklicherweise hat die Tatsache, dass SQL-Injection so alt und so häufig ist, den Vorteil, dass es Möglichkeiten gibt, sie zu verhindern. Die Anwendung dieser Präventionstechniken ist nicht nur eine gute Programmiergewohnheit, sondern stärkt auch die Sicherheit eines Unternehmens gegen SQLi.
Es gibt mehrere Möglichkeiten, Datenbankserver vor dieser Art von Angriffen zu schützen, z. B. Eingabevalidierung, Verwendung einer Web Application Firewall (WAF), Sicherung von Datenbanken, Einsatz von Sicherheitsteams oder -systemen von Drittanbietern und Schreiben von narrensicheren SQL-Abfragen.
Schauen wir uns ein Beispiel zur Verhinderung von SQL-Injections in Python an, indem wir eine der oben genannten Sicherheitsmaßnahmen anwenden.
Python-Beispiel
In diesem Beispiel verwendet der Angreifer eine boolesche, blinde SQL-Injektion, um wichtige Informationen aus dem System abzugreifen.
Python: Angreifbar
Angenommen, in der Datenbank gibt es eine Tabelle namens "sample_data". In dieser Tabelle werden Benutzernamen und Passwörter für die Benutzer der Anwendung gespeichert.
Erlauben Sie nun dem Benutzer, mit den folgenden Befehlen einen Wert aus dieser Datenbanktabelle zu suchen:
import mysql.connector
db = mysql.connector.connect
#Bad Practice. Vermeiden Sie dies! Dies ist nur zum Lernen.
(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-Einschleusung
Wenn der Benutzer hier einen Namen in die Suche eingibt, z. B. Alicia, gibt es kein Problem mit der Ausgabe.
Wenn der Benutzer jedoch etwas wie Alicia'; DROP TABLE sample_data; eingibt, hat dies erhebliche Auswirkungen auf die Datenbank.
Python: Abhilfe
Die SQL-Anweisung sollte wie folgt geändert werden, um den Angriff zu verhindern:
cur.execute("SELECT * FROM sample_data WHERE Name = %s;", (Name,))
Jetzt behandelt das System die Benutzereingabe als Zeichenkette, auch wenn der Benutzer versucht, SQL-Abfragen einzuschleusen, und behandelt die Benutzereingabe nur als den Wert des Namens.
Diese einfache Änderung kann böswillige Aktivitäten in zukünftigen Abfragen verhindern und das System vor Angriffen durch Benutzereingaben schützen.
Java-Beispiel
In diesem Beispiel verwenden wir auch eine Datenbanktabelle namens "sample_data", in der die Benutzerdaten der Anwendung gespeichert werden.
Eine einfache Anmeldeseite nimmt einen Benutzernamen und ein Kennwort an, und die Java-Datei, die ein Servlet (LoginServlet) ist, überprüft sie anhand der Datenbank, um die Anmeldung zu ermöglichen.
Java: Anfälliges Beispiel
Mit Hilfe der Tabelle "sample_data" in der Datenbank ermöglicht das System den Benutzern die Durchführung von Anmeldevorgängen unter Verwendung ihrer Anmeldedaten als Eingabe.
Es gibt eine Abfrage in der LoginServlet-Datei, um den Login-Vorgang unterzubringen, die lautet:
//Bad Example. Do not use string concatenation.
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()) {
// Login Successful if match is found
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");
}
}
Es folgt die Abfrage für die Benutzeranmeldung:
select * from sample_data where username='username' and password ='password'
SQL-Einschleusung
Das System funktioniert perfekt, wenn die Eingabe gültig ist. Nehmen wir zum Beispiel an, der Benutzername ist wieder Alicia und das Passwort ist geheim.
Das System gibt die Daten des Benutzers mit diesen Anmeldedaten zurück. Ein Angreifer kann jedoch die Benutzeranfrage mit Postman und cURL für eine SQL-Injektion manipulieren.
Der Hacker kann beispielsweise einen falschen Benutzernamen ( Alicia) und das Kennwort 'oder '1'='1' senden.
In diesem Fall stimmen der Benutzername und das Passwort nicht überein, aber die Bedingung '1'='1' wird immer wahr sein, so dass der Anmeldevorgang erfolgreich sein wird.
Java: Prävention
Um dies zu verhindern, müssen wir den Code von LoginValidation ändern und PreparedStatement anstelle von Statement für die Abfrageausführung verwenden. Diese Änderung verhindert die Verkettung von Benutzernamen und Passwort in der Abfrage und behandelt sie als Setter-Daten, um SQL-Injection zu vermeiden.
Nachfolgend finden Sie den geänderten 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 Benutzereingabe und verhindern die SQL-Injektion.
Beispiele
Nun werden wir uns einige weitere Beispiele in verschiedenen Sprachen ansehen, um besser zu verstehen, wie dies in der Praxis aussieht.
C# - Unsicher
Dieses Beispiel ist aufgrund der Verwendung von `FromRawSql` unsicher. Diese Methode bindet die Parameter nicht und versucht auch nicht, sie zu umgehen. 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 durch die Funktion `FromSqlInterpolated`, die die interpolierten Werte übernimmt und parametrisiert.
Dies ist zwar im Allgemeinen sicher, birgt aber die Gefahr, dass es dem unsicheren `FromRawSql` sehr ähnlich ist.
var blogs = context.Posts
.FromSqlInterpolated($"SELECT * FROM Posts WHERE state = {state} AND author = {author}")
.ToList();
Java - Sicher: Hibernate - Benannte Abfrage + Native Abfrage
Hibernate bietet mit der "Native Query" und der "Named Query" zwei Methoden zur sicheren Erstellung von Abfragen. Beide erlauben die Angabe von Speicherorten 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 Annotation eines `Query`-Attributs auf einer jplq-Repository-Schnittstelle können sie mehrere 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 Bibliothek `pg` ermöglicht die Methode `query` eine Parametrisierung, indem sie über ihren zweiten Parameter Parameterwerte bereitstellt.
const { posts } = await db.query('SELECT * FROM Post WHERE state = $1 AND author = $2', [state, author])
Javascript - Sicher: Sequelize
Die Bibliothek `sequelize` bietet eine Möglichkeit, eine Abfrage durch ihr zweites Argument zu parametrisieren, das Einstellungen für die Abfrage enthält. Dazu gehört eine Liste von Werten, die als Parameter an die Abfrage gebunden werden sollen, entweder nach Name oder Index.