|
Schutz vor "SQL Injection": So bleibt Ihre APEX-Anwendung sicher!
SQL Injection ist ein vieldiskutierter Begriff im
Bereich der Web-Anwendungen. Mit SQL Injection verändert ein Angreifer die SQL-Kommandos Ihrer
Anwendung - er injiziert eigene SQL-Anweisungen und verändert
damit das Verhalten der Anwendung. Ist eine Anwendung für SQL Injection anfällig,
so könnten Sicherheitsregeln ausgehebelt werden.
SQL Injection ist von der verwendeten
Technologie unabhängig. So
können alle Web-Anwendungen anfällig sein, egal ob
sie mit Java, PHP, Application Express oder anderen Technologien
erstellt wurden. Das Problem entsteht, wenn SQL-Anweisungen in
der Anwendung dynamisch durch Stringverkettung zusammengesetzt
werden. Bei Web-Anwendungen kommt dazu, dass Variableninhalte vielfach durch
URL-Manipulationen verändert werden können.
Die gute Nachricht zuerst: Oracle
Application Express bietet Ihnen einige Möglichkeiten, sich
vor SQL Injection zu schützen. So sind
Komponenten, die Sie mit den
Application Express-Assistenten erstellt haben,
nicht anfällig für SQL Injection.
Wenn Sie also ein Formular auf eine Tabelle oder auf eine Stored
Procedure mit den Application
Express-Assistenten erstellen, so sind Sie auf der sicheren
Seite. Wenn Sie Berichte mit "eigenen" SQL-Abfragen oder PL/SQL-Prozesse hinterlegen,
sind diese zwar grundsätzlich anfällig
- es gibt jedoch auch hier zahlreiche Möglichkeiten,
die Anwendungen zu schützen. Im heutigen
Tipp erfahren Sie, wann Ihre Anwendungen anfällig sind, und wie Sie
in diesen Fällen SQL Injection verhindern.
Zusätzlichen Schutz bringt die
Oracle Virtual Private Database,
welche vor einiger Zeit in diesem Forum vorgestellt wurde
(Mandantenfähige Anwendungen). In diesem Fall
werden Zugriffsregeln direkt an der Datenbanktabelle
hinterlegt - ein SQL Injection-Angriff, welcher darauf zielt, alle
Zeilen einer Tabelle zu sehen, geht dann ins Leere.
Zunächst wollen wir eine
Application Express-Anwendung erzeugen, die für SQL Injection
anfällig ist. Erstellen
Sie ein einfaches Szenario anhand der Tabellen EMP
und DEPT.
Hinterlegen Sie zwei Berichte - einen auf die Tabelle DEPT
und einen auf die Tabelle EMP.
Verwenden Sie für den Bericht
auf EMP die
untenstehende SQL-Abfrage.
Die
meisten von Ihnen würden diese Abfrage wahrscheinlich nicht so
formulieren - tun Sie es trotzdem. Wir kommen weiter hinten zur
"richtigen" Vorgehensweise.
Legen Sie anschließend
noch ein ausgeblendetes Element P1_DEPTNO
und erzeugen Sie im Bericht
auf DEPT einen
Link (Abbildung 1), so dass der Bericht auf EMP die
Zeilen der Abteilung anzeigt, welche vorher im Bericht auf DEPT angeklickt
wurde.
Abbildung 1: Einrichten des Hyperlinks im Bericht auf DEPT
Ihre Seite sollte dann wie folgt aussehen:
Abbildung 2: Ausgangssituation: Zwei verlinkte Berichte
Klicken Sie nun die Hyperlinks im Bericht auf DEPT an -
sieht soweit alles ganz gut aus, oder ...?
Ändern Sie nun die URL oben von ...
... in ....
Nun sehen Sie alle Zeilen ... es geht aber noch weiter: Wenn Sie auf diese
Weise ein UNION ALL
übergeben, können Sie jede beliebige
Tabelle anzeigen - man könnte beispielsweise abfragen, welche
Datenbankobjekte vorhanden sind - das Ergebnis würde so
aussehen.
Abbildung 3: Abfrage der Data Dictionary View USER_OBJECTS mit SQL Injection
Dies ist natürlich nicht nur für SQL SELECT-Kommandos
bedeutsam - auch INSERT, UPDATE oder DELETE-Kommandos könnten so
verändert werden. Insofern bestünde nicht nur die Gefahr des
"Ausspähens", sondern auch die der Manipulation. Wie jedoch
bereits oben erwähnt: Mit Application
Express lassen sich solche Probleme leicht
beheben.
Eine Möglichkeit ist
das Application Express-Feature:
Session State Security.
Mit Session State Security lässt sich das Ändern von
Elementen durch Manipulationen an der URL verhindern.
Manchmal ist es jedoch beabsichtigt, dass
Parameter durch URL direkt gesetzt werden (URL-Versand per Mail). Auch
ohne Session State Security
gilt jedoch: Beachtet man als Application
Express-Entwickler einige Grundsätze, so sind diese Angriffe
leicht zu verhindern.
1. Verwenden Sie BIND-Variablen!
In
obigem Beispiel muss das Berichts-SQL nur marginal
verändert werden - die Sicherheits-Wirkung ist um so größer ...
Ob Sie ein Element mit &NAME.
oder mit :NAME ansprechen, macht
für SQL und PL/SQL einen gewaltigen Unterschied. In beiden
Fällen wird der Name des Elements durch dessen Inhalt ersetzt.
Wenn Sie &NAME.
verwenden, geschicht dies jedoch vor
Ausführung der
SQL-Abfrage. Wenn Sie :NAME
verwenden, wird die SQL-Abfrage zuerst gelesen und analysiert - erst
dann findet die Ersetzung statt. Die Folge: Im ersten Fall kann die
Struktur des SQL-Kommandos noch (bspw.
durch ein zusätzliches OR 1=1) verändert
werden - im zweiten Fall nicht mehr.
Gehen wir das nochmal
durch:
Wenn Sie P1_DEPTNO
nun auf "99' or 1=1 --"
setzen, sieht das SQL danach so aus ...
Das "--" kommentiert in einem SQL-Kommando den Rest der Zeile aus. So
einfach war also eine SQL-Injection!
Dem gegenüber
stehen die Bind-Variablen. In diesem Fall findet keine Ersetzung auf
String-Ebene statt - Die Datenbank liest und analysiert die Abfrage vor
der Ersetzung - erst danach wird :P1_DEPTNO
ersetzt. Probieren Sie es
aus: Ändern Sie den Bericht entsprechend um und testen Sie es
nochmals ...
Abbildung 4: SQL Injection-Versuch mit Bind-Variablen
Im besten Fall sehen Sie einfach keine
Zeilen oder Sie bekommen (wie in Abbildung 4) eine Fehlermeldung. Wenn
Ihre Berichte, wie in diesem Beispiel dargestellt, eine feste Struktur
haben, sollten Sie stets Bind-Variablen (:NAME) verwenden und die
einfache Ersetzung &NAME. unterlassen.
Besonderes Augenmerk auf mögliche SQL-Injection sollten Sie legen,
wenn Sie dynamisches SQL
verwenden - wenn Sie also einen PL/SQL-Block, welcher ein SQL-Kommando
zurückliefert, als Berichtsquelle verwenden.
So ist die folgende Berichtsdefinition problematisch ...
Application Express führt in
diesem Fall zunächst den PL/SQL-Block aus. Dieser gibt in der
Variable v_sql
eine SQL-Abfrage zurück, welche anschließend als
Berichtsabfrage ausgeführt wird. Hier
wurde offensichtlich eine Bind-Variable (:P1_DEPTNO)
verwendet - das sieht auf den ersten Blick also gut
aus ...
... ist es aber nicht. Analysieren Sie zunächst, welchen
Inhalt die Variable v_sql nach
Ausführung des PL/SQL-Blocks hat - genau dies wird
anschließend als Berichtsabfrage verwendet
(angenommen P1_DEPTNO
sei 10).
Die
Ersetzung wurde schon im vom PL/SQL-Block im ersten
Schritt vorgenommen - daher kann die eigentliche SQL-Abfrage
nicht mehr
von der Bind-Variable profitieren (Im Grunde genommen ist es so als
hätten Sie &P1_DEPTNO.
verwendet). Besser wäre es, das SQL wie
folgt zusammenzusetzen:
In diesem Fall wird die Bind-Variable auch
in der Variable v_sql
zurückgegeben. Nun
gibt es allerdings einige Fälle, in denen die Verwendung von
Bind-Variablen nicht
möglich ist. So dürfen Bind-Variablen
nur für Abfragen (SELECT) und DML-Anweisungen (INSERT, UPDATE,
DELETE) verwendet werden - und dort auch nicht überall,
sondern
nur als Parameter in der WHERE-Klausel oder in der
SELECT-Liste. Bind-Variablen können niemals
genutzt werden, um Tabellen- oder
Spaltennamen variablel zu gestalten. Das führt zum zweiten Mittel gegen
SQL Injection.
2. Parameter vor Verwendung im SQL überprüfen: DBMS_ASSERT
Wenn die Verwendung von Bindevariablen nicht möglich ist, müssen
die Parameter vor dem "Einbauen" ins SQL überprüft werden. Dazu gibt
es ein eigenes PL/SQL-Paket: DBMS_ASSERT
(Dokumentation). Dieses Paket schauen wir uns nun
anhand eines Beispiels an. Eine Berichtsabfrage soll dynamisch sein; die
selektierte Spalte und die Tabellen ergeben sich aus APEX-Elementen.
Weder die Spalte (P1_SELECTED_COLUMN) noch
die Tabelle (P1_TABLE) können
als Bindevariablen festgelegt werden. Die
Datenbank muss Tabellennamen und selektierte Spalten schließlich kennen,
wenn das SQL geparst wird. Das "Einbauen" der Elementinhalte in die SQL-Abfrage
ist hier also unumgänglich. Also müssen
die Inhalte vorher mit DBMS_ASSERT geprüft werden. Wenn es an Datenbank-Objekte
(Tabellen, Spalten, Views, Prozeduren und andere) geht, ist die Funktion
ENQUOTE_NAME die Richtige. In SQL*Plus sieht die Verwendung wie folgt aus: Ein
übergebener Objektname wird von der Funktion in doppelte Anführungszeichen
eingeschlossen und zurückgegeben.
Diese Zeichenfolge kann nun gefahrlos als Tabellen-, Spalten oder
sonstiger Objektname in die SQL-Abfrage eingebaut werden. Durch
die doppelten Anführungszeichen ist deutlich, dass der komplette
Inhalt als (Objekt-)Name aufgefasst werden soll.
Wie reagiert die Funktion jedoch auf einen SQL-Injection-Angriff?
Ein Beispiel: Der
Angreifer setzt den Text
EMP" -- in das APEX-Element ein (Das Ziel ist,
den Objektnamen selbst mit einem doppelten Anführungszeichen
abzuschließen und den Rest auszukommentieren).
DBMS_ASSERT liefert den Text nicht mehr zurück - es wird dagegen eine
Fehlermeldung ausgelöst. Damit ist deutlich, dass hier eine SQL-Injection
versucht wurde - der Entwickler muss darauf reagieren - so wie es sein soll.
Die zweite wichtige Funktion in DBMS_ASSERT ist ENQUOTE_LITERAL. Sie funktioniert
wie folgt:
Diese Funktion schließt den übergebenen Text in einfache Anführungszeichen
ein - damit kann er als Literal in einer SQL-Abfrage verwendet werden. Diesen
Test sollten Sie für Parameter in der SELECT-Liste oder WHERE-Klausel dann durchführen,
wenn Sie (aus welchen Gründen auch immer) keine Bindevariablen verwenden möchten.
Versuchen Sie nochmals einen "Angriff":
Wiederum hat DBMS_ASSERT den Angriff erkannt; sobald ein einfaches Hochkomma
im Text vorkommt, wird eine SQL Injection-Attacke vermutet. Doch es gibt ab und
zu immer noch Fälle, in denen das einfache Hochkomma nötig und wichtig ist. Wenn
man bspw. den Namen O'Neill übergeben möchte, sähe das so aus.
Wird ein Hochkomma also in Oracle-typischer Manier maskiert (verdoppelt),
so wird es von DBMS_ASSERT akzeptiert - schließlich
kann es dann auch keine SQL Injection mehr auslösen.
Als Zusammenfassung wenden wir nun DBMS_ASSERT auf obiges Beispiel
für einen Bericht mit dynamischer SQL-Abfrage an:
Nimmt man als EMP für P1_TABLE, SAL für P1_SELECTED_COLUMN und 30 für P1_DEPTNO, so
würde folgende SQL-Abfrage generiert und ausgeführt.
Zusammenfassung
Mit den hier vorgestellten Regeln sind Application Express-Anwendungen
sicher vor SQL Injection-Angriffen.
-
Nutzen Sie stets Bindevariablen (Doppelpunkt-Syntax) in SQL-Abfragen
und niemals die Ersetzungszeichenfolgen (&-Syntax).
-
Wenn Bindevariablen nicht genutzt werden, prüfen Sie alle
Parameter vor dem Einbauen in das SQL-Kommando mit DBMS_ASSERT.
Zurück zur Community-Seite
|