Was ist SQL-Injection?
SQL-Injection ist eine der Top 10 Sicherheitsanfälligkeiten in Webanwendungen. Einfach ausgedrückt bedeutet SQL-Injection, dass SQL-Code über Benutzereingaben in eine Abfrage eingeschleust/eingefügt wird. Es kann in allen Anwendungen vorkommen, die relationale Datenbanken wie Oracle, MySQL, PostgreSQL und SQL Server verwenden.
Um eine SQL-Injection durchzuführen, versucht ein böswilliger Benutzer zunächst, einen Ort in der Anwendung zu finden, an dem er SQL-Code zusammen mit Daten einbetten kann. Das kann die Login-Seite einer Webanwendung oder ein anderer Ort sein. Wenn also Daten, die mit SQL-Code versehen sind, von der Anwendung empfangen werden, wird der SQL-Code zusammen mit der Anwendungsabfrage ausgeführt.
Auswirkungen von SQL-Injection
- Ein böswilliger Benutzer kann unbefugten Zugriff auf Ihre Anwendung erhalten und Daten stehlen.
- Er kann Daten in Ihrer Datenbank ändern, löschen und Ihre Anwendung lahmlegen.
- Ein Hacker kann auch die Kontrolle über das System erlangen, auf dem der Datenbankserver läuft, indem er datenbankspezifische Systembefehle ausführt.
Wie funktioniert SQL-Injection?
Angenommen, wir haben eine Datenbanktabelle namens tbluser, die Daten von Anwendungsnutzern speichert. Die userId ist die Primärsäule der Tabelle. Wir haben eine Funktionalität in der Anwendung, die es Ihnen ermöglicht, Informationen über die userId zu erhalten. Der Wert der userId wird aus der Benutzeranfrage erhalten.
Schauen wir uns den folgenden Beispielcode an.
String userId = {Daten vom Endbenutzer erhalten};
String sqlQuery = "select * from tbluser where userId = " + userId;
1. Gültige Benutzereingabe
Wenn die obige Abfrage mit gültigen Daten ausgeführt wird, d.h. userId-Wert 132, sieht sie wie folgt aus.
Eingabedaten: 132
Ausgeführte Abfrage: select * from tbluser where userId=132
Ergebnis: Die Abfrage gibt die Daten des Benutzers mit der userId 132 zurück. In diesem Fall findet keine SQL-Injection statt.
2. Hacker-Benutzereingabe
Ein Hacker kann Benutzeranfragen mit Werkzeugen wie Postman, cURL usw. ändern, um SQL-Code als Daten zu senden und so jegliche Validierungen auf der Benutzeroberfläche zu umgehen.
Eingabedaten: 2 oder 1=1
Ausgeführte Abfrage: select * from tbluser where userId=2 oder 1=1
Ergebnis: Jetzt hat die obige Abfrage zwei Bedingungen mit dem SQL-OR-Ausdruck.
- userId=2: Dieser Teil wird Tabellenzeilen entsprechen, die den userId-Wert ‚2‘ haben.
- 1=1: Dieser Teil wird immer als wahr ausgewertet. Die Abfrage gibt also alle Zeilen der Tabelle zurück.
Arten von SQL-Injection
Schauen wir uns die vier Arten von SQL-Injections an.
1. Boolean-basierte SQL-Injection
Das obige Beispiel ist ein Fall von Boolean-basierter SQL-Injection. Es verwendet einen booleschen Ausdruck, der zu wahr oder falsch ausgewertet wird. Es kann verwendet werden, um zusätzliche Informationen aus der Datenbank zu erhalten. Zum Beispiel;
Eingabedaten: 2 oder 1=1
SQL-Abfrage: select first_name, last_name from tbl_employee where empId=2 oder 1=1
2. Union-basierte SQL-Injection
Der SQL-Union-Operator kombiniert Daten aus zwei verschiedenen Abfragen mit der gleichen Anzahl von Spalten. In diesem Fall wird der Union-Operator verwendet, um Daten aus anderen Tabellen zu erhalten.
Eingabedaten: 2 union select username, password from tbluser
Abfrage: Select first_name, last_name from tbl_employee where empId=2 union select username, password from tbluser
Durch die Verwendung von Union-basierter SQL-Injection kann ein Angreifer Benutzeranmeldeinformationen erhalten.
3. Zeitbasierte SQL-Injection
Bei der zeitbasierten SQL-Injection werden spezielle Funktionen in die Abfrage eingefügt, die die Ausführung für eine bestimmte Zeit anhalten. Dieser Angriff verlangsamt den Datenbankserver. Er kann Ihre Anwendung lahmlegen, indem er die Leistung des Datenbankservers beeinträchtigt. Zum Beispiel in MySQL:
Eingabedaten: 2 + SLEEP(5)
Abfrage: select emp_id, first_name, last_name from tbl_employee where empId=2 + SLEEP(5)
Im obigen Beispiel wird die Ausführung der Abfrage für 5 Sekunden angehalten.
4. Fehlerbasierte SQL-Injection
In dieser Variante versucht der Angreifer, Informationen wie einen Fehlercode und eine Nachricht aus der Datenbank zu erhalten. Der Angreifer injiziert SQL, die syntaktisch falsch sind, so dass der Datenbankserver Fehlercode und Nachrichten zurückgibt, die verwendet werden können, um Informationen über die Datenbank und das System zu erhalten.
Java SQL-Injection-Beispiel
Wir verwenden eine einfache Java-Webanwendung, um SQL-Injection zu demonstrieren. Wir haben Login.html, eine grundlegende Login-Seite, die Benutzernamen und Passwort vom Benutzer erhält und an LoginServlet sendet.
Das LoginServlet erhält Benutzername und Passwort aus der Anfrage und validiert sie gegen die Werte in der Datenbank. Wenn die Authentifizierung erfolgreich ist, leitet das Servlet den Benutzer zur Startseite weiter, andernfalls gibt es einen Fehler zurück.
Login.html-Code:
<!DOCTYPE html>
<html lang="de">
<head>
<title>Sql Injection Demo</title>
</head>
<body>
<form name="frmLogin" method="POST" action="https://localhost:8080/Web1/LoginServlet">
<table>
<tr>
<td>Benutzername</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>Passwort</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2"><button type="submit">Login</button></td>
</tr>
</table>
</form>
</body>
</html>
LoginServlet.java-Code:
package com.journaldev.examples;
import java.io.IOException;
import java.sql.*;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (Exception e) {}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
boolean success = false;
String username = request.getParameter("username");
String password = request.getParameter("password");
// Unsichere Abfrage, die String-Konkatenation verwendet
String query = "select * from tbluser 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 erfolgreich, wenn Übereinstimmung gefunden wird
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");
}
}
}
Datenbankabfragen [MySQL]:
create database user;
create table tbluser(username varchar(32) primary key, password varchar(32));
insert into tbluser (username, password) values ('john', 'secret');
insert into tbluser (username, password) values ('mike', 'pass10');
1. Wenn gültiger Benutzername und Passwort auf der Login-Seite eingegeben werden
Eingegebener Benutzername: john
Eingegebenes Passwort: secret
Abfrage: select * from tbluser where username=’john‘ and password = ’secret‘
Ergebnis: Benutzername und Passwort existieren in der Datenbank, daher ist die Authentifizierung erfolgreich. Der Benutzer wird zur Startseite weitergeleitet.
2. Unbefugten Zugang zum System mit SQL-Injection erlangen
Eingegebener Benutzername: dummy
Eingegebenes Passwort: ‚ oder ‚1‘=’1
Abfrage: select * from tbluser where username=’dummy‘ and password = “ oder ‚1‘=’1′
Ergebnis: Der eingegebene Benutzername und das Passwort existieren nicht in der Datenbank, aber die Authentifizierung ist erfolgreich. Warum?
Es liegt an der SQL-Injection, da wir ‚ oder ‚1‘=’1 als Passwort eingegeben haben. Es gibt 3 Bedingungen in der Abfrage.
- username=’dummy‘: Es wird zu falsch ausgewertet, da es keinen Benutzer mit dem Benutzernamen dummy in der Tabelle gibt.
- password = “: Es wird zu falsch ausgewertet, da es kein leeres Passwort in der Tabelle gibt.
- ‚1‘=’1′: Es wird zu wahr ausgewertet, da dies ein statischer String-Vergleich ist.
Wenn wir alle 3 Bedingungen kombinieren, d.h., falsch und falsch oder wahr => Das Endergebnis wird wahr sein.
Verhinderung von SQL-Injection im Java-Code
Die einfachste Lösung ist die Verwendung von PreparedStatement anstelle von Statement, um die Abfrage auszuführen.
Anstatt Benutzername und Passwort in die Abfrage zu konkatenieren, stellen wir sie der Abfrage über die Setter-Methoden von PreparedStatement zur Verfügung.
Nun wird der Wert von Benutzername und Passwort, die aus der Anfrage erhalten wurden, nur als Daten behandelt, sodass keine SQL-Injection stattfinden wird.
Schauen wir uns den modifizierten Servlet-Code an.
String query = "select * from tbluser 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()) {
// Login erfolgreich, wenn Übereinstimmung gefunden wird
success = true;
}
rs.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
stmt.close();
conn.close();
} catch (Exception e) {
}
}
Lassen Sie uns verstehen, was in diesem Fall passiert.
Abfrage: select * from tbluser where username = ? and password = ?
Das Fragezeichen (?) in der obigen Abfrage wird als positionaler Parameter bezeichnet. Es gibt 2 positionale Parameter in der obigen Abfrage. Wir konkatenieren Benutzername und Passwort nicht zur Abfrage. Wir verwenden Methoden, die in PreparedStatement verfügbar sind, um die Benutzereingabe bereitzustellen.
Wir haben den ersten Parameter mit stmt.setString(1, username) und den zweiten Parameter mit stmt.setString(2, password) gesetzt. Die zugrundeliegende JDBC-API kümmert sich um das Sanitizing der Werte, um SQL-Injection zu vermeiden.
Best Practices zur Vermeidung von SQL-Injection
- Daten validieren, bevor sie in der Abfrage verwendet werden.
- Verwenden Sie keine gängigen Wörter als Tabellen- oder Spaltennamen. Viele Anwendungen verwenden tbluser oder tblaccount, um Benutzerdaten zu speichern. E-Mail, Vorname, Nachname sind gängige Spaltennamen.
- Konkatenieren Sie Daten (die als Benutzereingabe empfangen werden) nicht direkt, um SQL-Abfragen zu erstellen.
- Verwenden Sie Frameworks wie Hibernate und Spring Data JPA für die Datenschicht einer Anwendung.
- Verwenden Sie positionale Parameter in der Abfrage. Wenn Sie reines JDBC verwenden, verwenden Sie PreparedStatement, um die Abfrage auszuführen.
- Beschränken Sie den Zugriff der Anwendung auf die Datenbank über Berechtigungen und Zuschüsse.
- Geben Sie keine sensiblen Fehlercodes und -nachrichten an den Endbenutzer zurück.
- Führen Sie eine ordnungsgemäße Codeüberprüfung durch, damit kein Entwickler versehentlich unsicheren SQL-Code schreibt.
- Verwenden Sie Tools wie SQLMap, um SQL-Injection-Schwachstellen in Ihrer Anwendung zu finden und zu beheben.
Das ist alles zu Java SQL-Injection und wie sie verhindert wird.