Definition
Als „Injection“ wird eine Angriffsart bezeichnet, bei der versucht wird, Befehle in eine Anwendung einzuschmuggeln (zu injizieren), die an dieser Stelle eigentlich nur Daten erwartet. Möglich ist das durch die fehlende bzw. nicht richtig funktionierende Filterung der Daten, die die Anwendung vom Benutzer oder von anderen Systemen entgegennimmt.
Durch dieses Injizieren von Befehlen können Anweisungen oft direkt ans Backend der Anwendung gegeben werden, um dort ausgeführt zu werden. Die Webanwendung wird quasi als Infrastruktur dazu missbraucht. Im besten Fall für den Angreifer wird das Ergebnis dieser Abfragen direkt an ihn zurückgemeldet – z.B. in Form eines HttpResponses, der im Browser direkt angezeigt wird. In manchen Fällen fehlt aber dieses direkte Feedback, so dass ein Befehl zwar ausgeführt wird, man aber keine Antwort bekommt. In diesem Fall spricht man von „Blind Injection“.
Unterschieden werden mehrere Injection-Arten:
- SQL-Injection – dabei werden SQL-Befehle an ein DBMS gesendet. Dies ist die verbreitetste und bekannteste Art von Injection.
- OS-Injection – hier werden Befehle ans Betriebssystem (Operating System) injiziert.
- LDAP-Injection – das Senden von Befehlen an ein Active Directory über das LDAP-Protokoll.
Wir fokussieren uns auf SQL-Injection – auch als „SQLi“ abgekürzt und gerne „Sequal Injection“ ausgesprochen.
SQL-Injection
Mit SQL-Injection wird versucht, Datenbank-Anweisungen über die Webanwendung einzuschleusen. Diese Schwachstelle wird immer schwerer auszunutzen, da viele Entwicklungsframeworks die Benutzereingabe automatisieren und es verhindern, dass man SQL-Fragmente eingeben kann. Trotzdem kann man Fehler bei der Programmierung machen, die dafür sorgen, dass man doch noch SQL injizieren kann. Außerdem sind Fehler in den Frameworks möglich, so dass es manchmal auch hier noch Schwachstellen gibt, wo keine sein sollten.
Es wurde oft empfohlen, anstatt dynamisierte SQL-Befehle zu verwenden, nur Stored Procedures zu verwenden – also DB-Abfragen, die in der DB gespeichert sind und nur noch mit Parametern aufgerufen werden können. Aber auch bei Verwendung von Stored Procedures ist man nicht automatisch davor gefeit, dass SQLi möglich ist. Auch hier kann man Fehler bei der Parameterübernahme machen, wenn die Stored Procedure nicht richtig definiert ist.
SQLi Schwachstellen können durch automatisierte Tools meist recht einfach entdeckt werden, die Eingabefelder und -parameter mit Angriffsvektoren testen und die Reaktion beobachten. Auch bei der Quellcodeanalyse hat man sehr gute Chancen, diese Schwachstellen zu entdecken.
Prinzip des Angriffs
Ein Trivialbeispiel zeigt ein recht sorgloses Mapping einer Id in der Datenbank, das über die URL direkt durchgeschleust wird. Das SQL-Statement, das letztendlich auf der Datenbank abgeschickt werden soll, wäre folgendermaßen:
SQL:
SELECT * FROM account WHERE id = 1
Die Antwort wäre dann der Tabelleninhalt der Tabelle “account”, bei dem die “id” gleich “1“ ist. Die Webanwendung könnte folgendermaßen aussehen – sie verwendet einen Parameter, der die Id übernimmt und intern so durchreicht, bis sie beim SQL-Statement landet.
URL: http://banking.com/account?id=1
Das Risiko ist hier, dass der Parameter ungeprüft und ungefiltert übernommen wird, so dass man auch noch weitere Daten mitgeben kann. Hier ein Versuch, das SQL-Statement zu manipulieren: (Syntax-Probleme lassen wir zunächst außer Acht)
URL: http://banking.com/account?id=1 or 1=1
Wird der Parameter id also nur durchgereicht, dann landet im SQL-Statement folgendes:
SQL: SELECT * FROM account WHERE id = 1 or 1=1
Der Angreifer ist nun zufrieden, denn er sieht jetzt alle Account-Daten, und nicht nur die für die er möglicherweise berechtigt ist. Das Ergebnis ist also nicht mehr das, was sich der Entwickler gedacht hat. Geplant war ja, dass nur ein einziger Datensatz zurückkommt, und zwar der mit der korrekten ID. Was passiert aber in dem Fall?
Anstatt Daten werden hier weitere Anweisungen entgegengenommen. Daten und Anweisungen werden hier vermischt, was unbedingt vermieden werden soll.
Das ist die Essenz von SQL-Injection. Die Manipulation von SQL-Abfragen, durch Einschleusen (Injizieren) von Anweisungen, wo eigentlich nur Daten zugelassen werden sollten.
Wie wird das ganze möglich?
Indem bei der Bearbeitung eines Http-Requests so etwas programmiert wird:
String query = “SELECT * FROM account WHERE ID = “ + httpRequest.getParameter(“id”);
Prävention
Die ungeprüfte Übernahme von Request-Parametern ist eine Voraussetzung, dass SQL-Injection funktioniert. Gefährliche Benutzereingaben, mit denen SQL-Befehle ausgeführt werden, können dadurch ungehindert bis zum SQL-Interpreter vordringen. Dieser kann nicht unterscheiden, was von der Business-Logik der Anwendung kommt, und was vom Angreifer. Deshalb ist die effektivste Maßnahme, sämtliche Eingabedaten zu filtern, egal ob Benutzereingaben oder Daten, die über weitere Schnittstellen ins System gelangen.
SQL-Statements
Um bei SQL-Statements effektiv zu verhindern, dass über einen entsprechenden Angriffsvektor SQL-Befehle anstatt nur Daten injiziert werden, gibt es mehrere Möglichkeiten. Die bevorzugte Möglichkeit ist, im Quellcode eine sichere API zu verwenden, die SQLi nicht zulässt, denn dadurch wird der Aufruf des Interpreters vermieden, so dass hier keine Verwechslung zwischen Anweisung und Daten möglich ist. Die sichere API stellt außerdem eine typgebundene Schnittstelle dar, die es beispielsweise nicht zulässt, dass Strings verwendet werden, wenn nur numerische Werte erwartet werden.
Wenn eine sichere API nicht verfügbar sein sollte, z.B. weil in der verwendeten Version der Programmiersprache keine verfügbar ist, dann kann entweder selbst versucht werden, potentiell gefährliche Zeichen auszufiltern und sorgfältig zu entschärfen. Oder es kann eine 3rd-Party Library verwendet werden, die etwas Ähnliches macht. OWASP’s ESAPI Library stellt beispielsweise solche Methoden bereit.
Eine weitere Möglichkeit ist die Verwendung von Positivlisten („whitelisting“), in der die zugelassenen Zeichen aufgeführt sind. Werden Zeichen verwendet, die nicht in der Liste enthalten sind, dann werden sie ausgefiltert oder es wird eine Exception geworfen. Allerdings ist diese Möglichkeit in der Praxis oft nicht möglich, nämlich dann, wenn Sonderzeichen zugelassen werden müssen. Vorstellbar wäre hier eine Anwendung, die Eingabefelder für HTML-Code anbietet, oder für Reguläre Ausdrücke. Für Standard-Eingabefelder, wie Adressdaten, sollten Positivlisten aber möglich sein.
Besonders betont werden sollte, dass sämtliche Maßnahmen zur Verhinderung von SQLi unbedingt Server-seitig umgesetzt werden müssen. Es reicht nicht aus, im Client entsprechende Filterungen durchzuführen, da sie auf dem Weg zum Server manipuliert werden können.
Verwenden einer sicheren API in Java
Fangen wir mit dem Negativbeispiel an, wo man sehen kann, wie man es nicht machen darf.
String query =
„SELECT account_balance FROM user_data WHERE user_name = “ +
request.getParameter(„customerName“);
Statement stmt = connection.createStatement( query );
ResultSet results = stmt.executeQuery( );
Hier wird ohne Rücksicht auf manipulierte Eingaben der Wert des Requestparameters “customerName” direkt an das SQL-Statement gehängt. Es findet weder eine Filterung der Eingaben statt, noch wird eine sichere API verwendet. Ein Angriffsvektor in Form eines SQL-Kommandos kann hier also sehr leicht injiziert werden.
Als sichere API gilt beispielsweise die Klasse java.sql.PreparedStatement, denn sie sorgt dafür, dass keine Anweisungen injiziert werden, wo nur Daten vorgesehen sind. Sie nimmt also dem Interpreter die Interpretation ab, indem sie vorher die Zuordnung regelt.
String custname = request.getParameter(„customerName“);
String query =
„SELECT account_balance FROM user_data WHERE user_name = ?“;PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString( 1, custname);
ResultSet results = pstmt.executeQuery();
Wichtig dabei ist, dass keine Ausnahmen bei der Parameterzuweisung gemacht werden. Denkbar wäre ja auch, dass man sowohl einige Parameter über die Setter-Methoden von PreparedStatement zuweist, andere wiederum durch String-Konkatenation – was aber ein grober Fehler wäre.
String custname = request.getParameter(„customerName“);
String query =
„SELECT account_balance FROM user_data WHERE user_name = ? “ +„ORDER BY “ + request.getParameter(„sortOrder“);
Hier sehen wir eine Mischung aus Parametern, die über den Platzhalter („?“) der sicheren API zugewiesen werden und einer String-Konkatenation. Das sieht man tatsächlich manchmal bei Codereviews, wo ein Entwickler vielleicht im Nachhinein etwas hinzugefügt hat, ohne zu wissen wie man es richtig macht.
Also: keine Ausnahmen machen bei der Parameter-Übernahme. Ausschließlich die Funktionen der API dafür verwenden.
Ein weiteres Beispiel einer sicheren API ist die Hibernate Query Language (HQL). Im Gegensatz zur Hibernate Criteria API kann man mit HQL nämlich auch den Fehler begehen, durch String-Konkatenation die verhängnisvolle Entscheidung dem SQL-Interpreter zu überlassen. Auch hier kann man also potentiell Verbotenes tun, wenn man String-Konkatenationen im SQL-Befehl macht. Richtig ist stattdessen folgendes:
@Entity
@NamedQuery(name=“findByDescription“,
query=“FROM Inventory i WHERE i.productDescription = :productDescription“
)public class Inventory implements Serializable {
@Id private long id;
private String productDescription;
}// use case
String userSuppliedParameter = request.getParameter(„ProductDescription“);// perform input validation to detect attacks
List<Inventory> list = session.getNamedQuery(„findByDescription“).setParameter(„productDescription“, userSuppliedParameter).list();
Durch die Verwendung von Hibernate Criteria Queries ist man auf der sicheren Seite, was die Prävention von SQLi angeht:
String userSuppliedParameter = request.getParameter(„ProductDescription“);
// perform input validation to detect attacks
Inventory inv = (Inventory) session.createCriteria(Inventory.class).add(Restrictions.eq(„productDescription“,
userSuppliedParameter)).uniqueResult();
Prüfung und Entschärfung von Eingabedaten in Java
Das Problem ist manchmal, dass keine sichere API zur Verfügung steht, aus welchem Grund auch immer. In solchen Fällen kann eine Library verwendet werden, die für eine Filterung oder für ein entsprechendes Coding sorgt, wie beispielsweise die OWASP ESAPI:
Codec ORACLE_CODEC = new OracleCodec();
String query = „SELECT user_id FROM user_data WHERE user_name = ‚“ +
ESAPI.encoder().encodeForSQL( ORACLE_CODEC, req.getParameter(„userID“) ) +
„‚ and user_password = ‚“ +
ESAPI.encoder().encodeForSQL( ORACLE_CODEC, req.getParameter(„pwd“) ) +“‚“;
Dies sollte nur als Zusatzmaßnahme gemacht werden, oder als Notlösung für alte Software. Die Verwendung einer sicheren API ist auf jeden Fall vorzuziehen. Allerdings ist es auch die einzig bekannte Maßnahme für andere Injection-Typen außer SQL-Injection. Die Strategie dabei sollte sein, zunächst die Benutzereingaben zu normalisieren, und sie dann noch zusätzlich mit einer Positivliste abzugleichen.
Hier ein weiteres Beispiel mit der Klasse org.owasp.esapi.reference.DefaultValidator der ESAPI. Die komplette Beschreibung findet sich wie üblich in der API-Dokumentation.
private boolean isValid() {
boolean valid = false;
try {
firstName = ESAPI.validator().getValidInput(„CustomerFirstName“, firstName, „SafeString“, 30, false, true);
lastName = ESAPI.validator().getValidInput(„CustomerLastName“, lastName, „SafeString“, 30, false, true);
email = ESAPI.validator().getValidInput(„CustomerEmail“, email, „Email“, 254, false, true);
valid = true;
} catch (IntrusionException | ValidationException e) {
System.out.println(e.getMessage());
}
return valid;
}
Stored Procedures
Hier sehen wir eine fehlerhafte Stored Procedure:
CREATE PROCEDURE VerifyUser
@username varchar(50),
@password varchar(50)
AS
BEGIN
DECLARE @sql nvarchar(500);
SET @sql = ‚SELECT * FROM UserTable
WHERE UserName = “‘ + @username + “‘
AND Password = “‘ + @password + “‘ ‚;
EXEC(@sql);
END
GO
Durch folgende Parameter…
- Username = Hans‘ —
- Password = password
…würde die Stored Procedure folgendes SQL generieren und ausführen:
SELECT * FROM UserTable WHERE UserName = ‚Hans‘ –‚ AND Password = ‚any password‘
Die zweite Hälfte des Statements ist auskommentiert und dadurch würde der Rest ignoriert werden. Genau das ist die Absicht bei SQLi. Falsch ist hierbei, dass bei der Definition des SQL das Statement mit den Parametern konkateniert wird, anstatt es sauber zu definieren. Zwar ist diese Art eine Stored Procedure zu schreiben zugegebenermaßen etwas gekünstelt, aber damit soll gezeigt werden, dass man auch hier Fehler machen kann, die SQLi zulassen.
Korrekt ist folgende Stored Procedure:
CREATE PROCEDURE VerifyUser
@username varchar(50),
@password varchar(50)
AS
BEGIN
SELECT * FROM UserTable
WHERE UserName = @username
AND Password = @password;
END
GO
Auf diese Art würde ein Execution-Plan für das SQL-Statement erzeugt werden, bevor die Query ausgeführt wird. Der Plan würde nur die Ausführung der original Query erlauben. Parameterwerte die SQLi enthalten würden nicht ausgeführt werden, da sie nicht Bestandteil des Execution-Plans sind. Die Eingabe Hans‘ — würde also behandelt werden wie der entsprechende Benutzername, auch wenn er Sonderzeichen wie das Hochkomma, das Leerzeichen und die 2 Minuszeichen enthält. Der SQL-Interpreter muss diese nicht mehr interpretieren, sondern weiß von Anfang an, dass hier keine Anweisung enthalten ist. In anderen Worten: Diese Abfrage würde nach einem Benutzer mit diesem exakten Namen und dem exakten Passwort suchen, anstatt die Abfrage komplett neu zu interpretieren und ein für den Entwickler unerwartetes Ergebnis liefern. SQL-Injection ist bei der richtigen Verwendung der Stored Procedure also nicht mehr möglich.
Fazit
Injection ist eine sehr verbreitete Schwachstelle, bei der Eingabedaten mit Befehlen vermischt werden, was bei falscher Behandlung im Code zum Ausführen von verhängnisvollen Anweisungen kommen kann, wie Umgehen der Authentifizierung, Austricksen der Autorisierung, sowie manipulieren von Daten, bis hin zum Ausführen von Code.
Glücklicherweise sind diese Schwachstellen meist relativ einfach zu beseitigen, wenn man den Code entsprechend umschreibt. Hier wird besonders deutlich, wie wichtig es ist, die Schwachstelle und die Angriffe zu kennen, damit man sich durch die richtigen Präventionsmaßnahmen effektiv dagegen schützt.