Tipps & Tricks: Dezember 2015 Bereich: PL/SQL Erstellung: 12/2015 HA Versionsinfo: 11.2, 12.1 Letzte Überarbeitung: 12/2015 HA SQL Injection erschweren durch DBMS_ASSERT In PL/SQL empfiehlt es sich aus vielen Gründen, mit statischem SQL zu arbeiten, soweit das möglich ist; genannt seien hier vor allem Wartbarkeit und Performance-Vorteile. Und so ganz nebenbei verwendet man automatisch Bind-Variablen, ohne groß etwas dafür tun zu müssen: Übergabe-Parameter von Prozeduren und Funktionen werden intern als Bind-Variablen umgesetzt, wenn sie in einem SQL-Befehl verwendet werden. Nun gibt es aber Fälle, in denen man nicht um dynamisches SQL herumkommt. Solange dabei von außen ausschließlich Werte übergeben werden, aber keine Spalten- oder Tabellennamen, kann man sich teilweise auch hier mit einer Syntax behelfen, die Bind-Variablen benutzt: USING. Ein einfaches Beispiel dazu: CREATE OR REPLACE PROCEDURE update_sal( p_ename IN emp.ename%TYPE, p_sal in emp.sal%type) IS BEGIN EXECUTE IMMEDIATE 'UPDATE EMP SET sal = :a WHERE ename = :b' using p_sal, p_ename; END update_sal; Oder allgemein: EXECUTE IMMEDIATE '... WHERE spalte = :a' USING p_val; Bei Ref Cursorn: OPEN ref_cur FOR 'SELECT .... WHERE spalte = :a' USING p_val; Das ist aber nicht immer praktikabel. Und spätestens dann, wenn Objektnamen oder Spaltennamen übergeben werden, ist es vorbei mit der Verwendung von Bind-Variablen. Hier muss man mit Konkatenierung arbeiten. Eine Möglichkeit, sich in solchen Fällen vor SQL Injection zu schützen, bietet das Package DBMS_ASSERT. Es beinhaltet eine Reihe von Funktionen, die übergebene Werte nach unterschiedlichen Kriterien überprüfen oder ändern. Gehen wir einmal von folgender Funktion aus, die nur das Prinzip verdeutlichen soll (ausführlichere Beispiele zu SQL Injection gibt es genügend im Netz): CREATE TYPE vt AS TABLE OF VARCHAR2 (100); / CREATE OR REPLACE FUNCTION get_werte (p_such_spalte IN VARCHAR2, p_tabelle IN VARCHAR2, p_where_spalte IN VARCHAR2, Muniqsoft GmbH Schulungszentrum, Grünwalder Weg 13a, 82008 Unterhaching, Tel. 089 / 679090-40 IT-Consulting & Support, Witneystraße 1, 82008 Unterhaching, Tel. 089 / 6228 6789-0 Seite 1 von 6 p_wert IN VARCHAR2) RETURN vt IS v_cur SYS_REFCURSOR; v_sql VARCHAR2 (2000); v_arr vt; BEGIN v_sql := 'select ' || p_such_spalte || ' from ' || p_tabelle || ' where ' || p_where_spalte || ' = ''' || p_wert||''''; OPEN v_cur FOR v_sql; FETCH v_cur BULK COLLECT INTO v_arr; CLOSE v_cur; RETURN v_arr; END get_werte; / SELECT COLUMN_VALUE FROM TABLE (get_werte ('sal','scott.emp', 'ename', 'SMITH')) / COLUMN_VALUE -------------------------------------------------------------------------------800 SELECT COLUMN_VALUE FROM TABLE (get_werte ('sal','scott.emp', 'ename', 'SMITH'' OR 1=1 --')) / COLUMN_VALUE -------------------------------------------------------------------------------800 ... 1300 14 rows selected. Es leuchtet ein, dass p_wert in anderer Form abgesichert werden muss als die übrigen drei Parameter. Mit DBMS_ASSERT können beide Arten von Eingaben abgesichert werden: Eingegebene Werte (wie p_wert) können mit ENQUOTE_LITERAL abgesichert werden Eingegenebe Bezeichner (Spalten- und Tabellennamen wie die übrigen drei Parameter) können mit ENQUOTE_NAME abgesichert werden Ausserdem können eingegenebe Tabellennamen mit SIMPLE_SQL_NAME, QUALIFIED_SQL_NAME, SQL_OBJECT_NAME und SCHEMA_NAME überprüft werden. Bei diesen Funktionen wird der übergebene Wert nicht verändert, aber wenn die Überprüfung fehlschlägt, führt dies zu einem Fehler. Diese Funktionen wollen wir uns nun näher anschauen. ENQUOTE_LITERAL Diese Funktion dient zur Absicherung eingegbener Literale, also String-Werte. Sie umgibt den übergebenen Wert mit einfachen Hochkommata (die man dann natürlich im Quelltext nicht mehr mühsam ergänzen muss) und Muniqsoft GmbH Schulungszentrum, Grünwalder Weg 13a, 82008 Unterhaching, Tel. 089 / 679090-40 IT-Consulting & Support, Witneystraße 1, 82008 Unterhaching, Tel. 089 / 6228 6789-0 Seite 2 von 6 Diese Funktion dient zur Absicherung eingegbener Literale, also String-Werte. Sie umgibt den übergebenen Wert mit einfachen Hochkommata (die man dann natürlich im Quelltext nicht mehr mühsam ergänzen muss) und überprüft, ob er intern ein einzelnes einfaches Hochkomma enthält. Letzeres führt zu einem Fehler. Gepaarte einfache Hochkommata sind erlaubt, weil ja auch Werte vorkommen können, die ein solches enthalten (Stichwort: Escape). Damit soll verhindert werden, dass durch die Hintertür eine Zusatzbedingung á la "SMITH' OR 1=1 --" mit angegeben wird. SELECT DBMS_ASSERT.ENQUOTE_LITERAL ( 'SMITH') FROM DUAL; DBMS_ASSERT.ENQUOTE_LITERAL('SMITH') -------------------------------------------------------------------------------'SMITH' SELECT DBMS_ASSERT.ENQUOTE_LITERAL ( 'O''''Conner') FROM DUAL; DBMS_ASSERT.ENQUOTE_LITERAL('O''''CONNER') -------------------------------------------------------------------------------'O''Conner' SELECT DBMS_ASSERT.ENQUOTE_LITERAL ( 'SMITH'' OR 1=1 --') FROM DUAL; -- Der klassische Fall Error at line 1 ORA-06502: PL/SQL: numerischer oder Wertefehler ORA-06512: in "SYS.DBMS_ASSERT", Zeile 409 ORA-06512: in "SYS.DBMS_ASSERT", Zeile 493 ENQUOTE_NAME In analoger Weise arbeitet ENQUOTE_NAME, nur dass hier keine einfachen Hochkommata ergänzt werden, sondern doppelte. Standardmäßig wird der Bezeichner ausserdem in Großbuchstaben gewandelt, was aber durch einen zweiten Parameter unterbunden werden kann. Wird ein Wert mitgegeben, der bereits von doppelten Hochkommata eingeschlossen ist, wird kein zweites Paar ergänzt, und die Schreibweise bleibt auch unverändert. Intern darf der Wert kein doppeltes Hochkomma enthalten; in Version 11g konnte man noch Paare von doppelten Hochkommata mitgeben, in 12c wird auch das unterbunden. ENQUOTE_NAME dient rein der Absicherung der Eingabe: Alles, was sich innerhalb doppelter Hochkommata befinden, wird von Oracle als ein Bezeichner betrachtet - womit auch gesagt ist, dass die Methode nicht tauglich ist, wenn der Schemaname dem Objektnamen vorangestellt mitgegeben werden kann. Es findet keine inhaltliche Überprüfung statt. SELECT DBMS_ASSERT.ENQUOTE_NAME('emp') FROM DUAL; DBMS_ASSERT.ENQUOTE_NAME('EMP') -------------------------------------------------------------------------------"EMP" SELECT DBMS_ASSERT.ENQUOTE_NAME('"emp"') FROM DUAL; DBMS_ASSERT.ENQUOTE_NAME('"EMP"') -------------------------------------------------------------------------------"emp" SELECT DBMS_ASSERT.ENQUOTE_NAME('scott.emp') FROM DUAL; DBMS_ASSERT.ENQUOTE_NAME('SCOTT.EMP') Muniqsoft GmbH Schulungszentrum, Grünwalder Weg 13a, 82008 Unterhaching, Tel. 089 / 679090-40 IT-Consulting & Support, Witneystraße 1, 82008 Unterhaching, Tel. 089 / 6228 6789-0 Seite 3 von 6 -------------------------------------------------------------------------------"SCOTT.EMP" Mit diesen beiden Funktionen kann obiges Beispiel nun umgebaut werden: CREATE OR REPLACE FUNCTION get_werte (p_such_spalte IN VARCHAR2, p_tabelle IN VARCHAR2, p_where_spalte IN VARCHAR2, p_wert IN VARCHAR2) RETURN vt IS v_cur SYS_REFCURSOR; v_sql VARCHAR2 (2000); v_arr vt; BEGIN v_sql := 'select ' || DBMS_ASSERT.ENQUOTE_NAME(p_such_spalte) || ' from ' ||DBMS_ASSERT.ENQUOTE_NAME( p_tabelle) || ' where ' || DBMS_ASSERT.ENQUOTE_NAME(p_where_spalte) || ' = ' ||DBMS_ASSERT.ENQUOTE_LITERAL (p_wert); OPEN v_cur FOR v_sql; FETCH v_cur BULK COLLECT INTO v_arr; CLOSE v_cur; RETURN v_arr; END get_werte; / SELECT COLUMN_VALUE FROM TABLE (get_werte ('sal','emp', 'ename', 'SMITH')) / COLUMN_VALUE -------------------------------------------------------------------------------800 SELECT COLUMN_VALUE FROM TABLE (get_werte ('sal','emp', 'ename', 'SMITH'' OR 1=1 --')) / Error at line 2 ORA-06502: PL/SQL: numerischer oder Wertefehler ORA-06512: in "SYS.DBMS_ASSERT", Zeile 409 ORA-06512: in "SYS.DBMS_ASSERT", Zeile 493 ORA-06512: in "SCOTT.GET_WERTE", Zeile 11 SELECT COLUMN_VALUE FROM TABLE (get_werte ('sal','scott.emp', 'ename', 'SMITH')) / Error at line 1 ORA-00942: Tabelle oder View nicht vorhanden SIMPLE_SQL_NAME und QUALIFIED_SQL_NAME Diese Funktionen überprüfen ausschießlich, ob der eingegebene Wert den Oracle-Namenskonventionen Muniqsoft GmbH Schulungszentrum, Grünwalder Weg 13a, 82008 Unterhaching, Tel. 089 / 679090-40 IT-Consulting & Support, Witneystraße 1, 82008 Unterhaching, Tel. 089 / 6228 6789-0 Seite 4 von 6 Diese Funktionen überprüfen ausschießlich, ob der eingegebene Wert den Oracle-Namenskonventionen entspricht. Nicht überprüft wird dabei allerdings die Länge der Eingabe. Der Unterschied zwischen den beiden Funktionen besteht darin, dass SIMPLE_SQL_NAME einen einzelnen Bezeichner erwartet, während QUALIFIED_SQL_NAME mit einem qualifizierten Oracle-Bezeichner umgehen kann. Letzerer kann Punkte und "@" enthalten. Da innerhab von doppelten Hochkommata alles erlaubt ist, kann man damit diese Art der Überprüfung damit theoretisch aushebeln; allerdings taugt dann die Eingabe auch nicht mehr als Objektname. SELECT DBMS_ASSERT.SIMPLE_SQL_NAME ( 'emp') FROM DUAL; DBMS_ASSERT.SIMPLE_SQL_NAME('EMP') -------------------------------------------------------------------------------emp SELECT DBMS_ASSERT.SIMPLE_SQL_NAME('emp or 1=1') FROM DUAL; Error at line 1 ORA-44003: Ungültiger SQL-Name ORA-06512: in "SYS.DBMS_ASSERT", Zeile 206 SELECT DBMS_ASSERT.SIMPLE_SQL_NAME ( '"emp or 1=1"') FROM DUAL; DBMS_ASSERT.SIMPLE_SQL_NAME('"EMPOR1=1"') -------------------------------------------------------------------------------"emp or 1=1" SELECT DBMS_ASSERT.QUALIFIED_SQL_NAME ( 'scott.emp@test') FROM DUAL; DBMS_ASSERT.QUALIFIED_SQL_NAME('SCOTT.EMP@TEST') -------------------------------------------------------------------------------scott.emp@test SQL_OBJECT_NAME und SCHEMA_NAME Hier findet auch eine inhaltliche Überprüfung statt. Das heisst, es müssen nicht nur die Namenskonventionen eingehalten werden, sondern das Objekt bzw. das Schema muss auch vorhanden sein (Ausnahme: Angabe eines Datenbank-Links). Bei SQL_OBJECT_NAME kann ein Schemaname davor angegeben werden, muss aber nicht. Falls keiner angegeben wird, muss sich das Objekt im aktuellen Schema befinden. Hat ein User keine Zugriffsrechte auf das Objekt, schlägt die Überprüfung mit SQL_OBJECT_NAME ebenfalls fehl. SCHEMA_NAME arbeitet case-sensitive, SQL_OBJECT_NAME dagegen nicht. -- als User SCOTT: SELECT DBMS_ASSERT.SQL_OBJECT_NAME ( 'emp') FROM DUAL; DBMS_ASSERT.SQL_OBJECT_NAME('EMP') -------------------------------------------------------------------------------emp SELECT DBMS_ASSERT.SQL_OBJECT_NAME ( 'scott.emp') FROM DUAL: DBMS_ASSERT.SQL_OBJECT_NAME('SCOTT.EMP') -------------------------------------------------------------------------------scott.emp SELECT DBMS_ASSERT.SQL_OBJECT_NAME ( 'DUMMY.TESTTAB') FROM DUAL; Muniqsoft GmbH Schulungszentrum, Grünwalder Weg 13a, 82008 Unterhaching, Tel. 089 / 679090-40 IT-Consulting & Support, Witneystraße 1, 82008 Unterhaching, Tel. 089 / 6228 6789-0 Seite 5 von 6 Error at line 1 ORA-44002: Ungültiger Objektname ORA-06512: in "SYS.DBMS_ASSERT", Zeile 383 SELECT DBMS_ASSERT.SCHEMA_NAME ( 'scott') FROM DUAL; Error at line 1 ORA-44001: Ungültiges Schema ORA-06512: in "SYS.DBMS_ASSERT", Zeile 333 SELECT DBMS_ASSERT.SCHEMA_NAME ( 'SCOTT') FROM DUAL: DBMS_ASSERT.SCHEMA_NAME('SCOTT') -------------------------------------------------------------------------------SCOTT Damit könenn wir unser obiges Beispiel nochmal umbauen, so dass es auch wieder mit vorangestellten Schemanamen umgehen kann: CREATE OR REPLACE FUNCTION get_werte (p_such_spalte IN VARCHAR2, p_tabelle IN VARCHAR2, p_where_spalte IN VARCHAR2, p_wert IN VARCHAR2) RETURN vt IS v_cur SYS_REFCURSOR; v_sql VARCHAR2 (2000); v_arr vt; BEGIN v_sql := 'select ' || DBMS_ASSERT.ENQUOTE_NAME(p_such_spalte) || ' from ' ||DBMS_ASSERT.SQL_OBJECT_NAME( p_tabelle) || ' where ' || DBMS_ASSERT.ENQUOTE_NAME(p_where_spalte) || ' = ' ||DBMS_ASSERT.ENQUOTE_LITERAL (p_wert); OPEN v_cur FOR v_sql; FETCH v_cur BULK COLLECT INTO v_arr; CLOSE v_cur; RETURN v_arr; END get_werte; / SELECT COLUMN_VALUE FROM TABLE (get_werte ('sal','scott.emp', 'ename', 'SMITH')) / COLUMN_VALUE -------------------------------------------------------------------------------800 Fazit: Versierte Hacker werden wohl immer noch Mittel und Wege finden, aber schon allein der Einsatz von DBMS_ASSERT.ENQUOTE_LITERAL macht ihnen das Leben deutlich schwerer. Weitere Informationen zu SQL Injection erhalten Sie in unserem Security und APEX Security Kurs. Muniqsoft GmbH Schulungszentrum, Grünwalder Weg 13a, 82008 Unterhaching, Tel. 089 / 679090-40 IT-Consulting & Support, Witneystraße 1, 82008 Unterhaching, Tel. 089 / 6228 6789-0 Seite 6 von 6