SQL Injection

Bei einem SQL-Injection-Angriff wird ein bösartiger Code in Zeichenfolgen eingefügt, die später zur Analyse und Ausführung an eine Instanz von SQL Server übergeben werden. Sie sollten jede Prozedur, die SQL-Anweisungen erstellt, nach Injection-Anfälligkeiten überprüfen, denn SQL Server führt alle empfangenen gültigen Abfragen aus. Auch parametrisierte Daten können von einem technisch versierten Angreifer manipuliert werden.

Die Primärform von SQL-Injection besteht aus dem direkten Einfügen des Codes in Benutzereingabevariablen, die mit SQL-Befehlen verkettet und ausgeführt werden. Bei einem weniger direkten Angriff wird bösartiger Code in Zeichenfolgen eingefügt, die in einer Tabelle oder als Metadaten gespeichert werden sollen. Wenn die gespeicherten Zeichenfolgen später in einem dynamischen SQL-Befehl verkettet werden, wird der bösartige Code ausgeführt.

Die Funktionalität des Injection-Prozesses besteht in der vorzeitigen Beendigung einer Textzeichenfolge und dem Anhängen eines neuen Befehls. Da vor der Ausführung des eingefügten Befehls möglicherweise weitere Zeichenfolgen daran angehängt wurden, beendet der Missetäter die eingefügte Zeichenfolge durch eine Kommentarmarkierung "--". Nachfolgender Text wird zur Ausführungszeit ignoriert.

Das folgende Skript zeigt ein einfaches SQL-Injection-Beispiel. Das Skript erstellt eine SQL-Abfrage, indem hartkodierte Zeichenfolgen mit einer vom Benutzer eingegebenen Zeichenfolge verkettet werden:

var Shipcity;
ShipCity = Request.form ("ShipCity");
var sql = "select * from OrdersTable where ShipCity = '" + ShipCity + "'";

Der Benutzer wird aufgefordert, den Namen einer Stadt einzugeben. Bei der Eingabe Redmond sieht die vom Skript erstellt Abfrage ungefähr wie die folgende aus:

SELECT * FROM OrdersTable WHERE ShipCity = 'Redmond'

Angenommen, der Benutzer gibt jedoch Folgendes ein:

Redmond'; drop table OrdersTable--

In diesem Fall wird die folgende Abfrage von dem Skript erstellt:

SELECT * FROM OrdersTable WHERE ShipCity = 'Redmond';drop table OrdersTable--'

Das Semikolon (;) bezeichnet das Ende der einen Abfrage und den Start einer anderen. Der doppelte Bindestrich (--) zeigt an, dass es sich beim restlichen Teil der aktuellen Zeile um einen Kommentar handelt, und die Zeile sollte ignoriert werden. Wenn der geänderte Code syntaktisch richtig ist, wird er vom Server ausgeführt. Wenn SQL Server die Anweisung verarbeitet, wird SQL Server zuerst alle Datensätze in OrdersTable auswählen, wobei für ShipCity der Wert Redmond gilt. Anschließend wird OrdersTable von SQL Server gelöscht.

Solange der eingefügte SQL-Code syntaktisch richtig ist, kann eine Manipulation nicht programmgesteuert erkannt werden. Aus diesem Grund müssen Sie die Gültigkeit aller Benutzereingaben überprüfen und sorgfältig den Code überprüfen, der in dem von Ihnen verwendeten Server die erstellten SQL-Befehle ausführt. Die bewährten Methoden zum Schreiben von Code werden in den nachfolgenden Abschnitten in diesem Thema beschrieben.

Überprüfen aller Eingaben

Überprüfen Sie immer alle Benutzereingaben, indem Sie Typ, Länge, Format und Bereich testen. Bei der Implementierung von Vorsichtsmaßnahmen gegen böswillige Eingaben sollten Sie die Architektur und die Bereitstellungsszenarien Ihrer Anwendung berücksichtigen. Denken Sie daran, dass Programme, die zur Ausführung in einer sicheren Umgebung entworfen wurden, in eine nicht sichere Umgebung kopiert werden können. Die folgenden Vorschläge sollten als bewährte Methoden angesehen werden:

  • Machen Sie keine Annahmen über Größe, Typ oder Inhalt der Daten, die von der Anwendung empfangen wurden. Beispielsweise sollten Sie die folgende Auswertung vornehmen:

    • Wie verhält sich Ihre Anwendung, wenn ein fehlgeleiteter oder böswilliger Benutzer eine 10 MB große MPEG-Datei an einer Stelle eingibt, an der die Anwendung eine Postleitzahl erwartet?

    • Wie verhält sich die Anwendung bei einer eingebetteten DROP TABLE-Anweisung in einem Textfeld?

  • Testen Sie Größe und Datentyp der Eingaben, und erzwingen Sie entsprechende Grenzwerte. Dies kann bei der Vermeidung absichtlicher Pufferüberläufe hilfreich sein.

  • Testen Sie den Inhalt der Zeichenfolgenvariablen, und akzeptieren Sie nur erwartete Werte. Lehnen Sie Einträge mit Binärdaten, Escapesequenzen und Befehlszeichen ab. Dies kann bei der Verhinderung von Injection-Skripts hilfreich sein und schützt möglicherweise vor einigen Pufferüberlauf-Exploits.

  • Überprüfen Sie alle Daten bei der Eingabe gegen das Schema, wenn Sie mit XML-Dokumenten arbeiten.

  • Erstellen Sie keine Transact-SQL-Anweisungen direkt aus den Benutzereingaben.

  • Verwenden Sie gespeicherte Prozeduren, um Benutzereingaben zu überprüfen.

  • In Multi-Tier-Umgebungen sollten alle Daten vor dem Zugang zu einer vertrauensvollen Zone überprüft werden. Daten, die den Verifizierungsprozess nicht bestehen, sollten zurückgewiesen und eine Fehlermeldung sollte an die vorherige Stufe zurückgegeben werden.

  • Implementieren Sie mehrere Überprüfungsebenen. Vorsichtsmaßnahmen, die Sie gegen gelegentliche böswillige Benutzer einsetzen, sind möglicherweise gegen technisch versierte Angreifer unwirksam. Die bessere Methode besteht aus dem Überprüfen der Eingaben in die Benutzeroberfläche und aller nachfolgenden Punkte, an denen eine Vertrauensgrenze gekreuzt wird.

    Beispielsweise kann die Datenüberprüfung in einer clientseitigen Anwendung einfache Skript-Injection verhindern. Wenn jedoch auf der nächsten Ebene davon ausgegangen wird, dass die Eingabe bereits überprüft wurde, erhalten böswillige Benutzer mit der Fähigkeit, einen Client zu umgehen, uneingeschränkten Systemzugriff.

  • Verketten Sie niemals Benutzereingaben, die nicht überprüft wurden. Die Zeichenfolgenverkettung ist der primäre Eingangspunkt für Script-Injection.

  • Akzeptieren Sie in Feldern, aus denen Dateinamen erstellt werden können, keine der folgenden Zeichenfolgen: AUX, CLOCK$, COM1 bis COM8, CON, CONFIG$, LPT1 bis LPT8, NUL und PRN.

Weisen Sie, wenn möglich, Eingaben mit den folgenden Zeichen zurück.

Eingabezeichen

Bedeutung in Transact-SQL

;

Abfragetrennzeichen

'

Trennzeichen für Datenzeichenfolgen.

--

Kommentartrennzeichen.

/* ... */

Kommentartrennzeichen. Text zwischen /* und */ wird vom Server nicht ausgewertet.

xp_

Wird am Start des Namens von erweiterten gespeicherten Katalogprozeduren verwendet, wie z. B. xp_cmdshell.

Verwenden von typsicheren SQL-Parametern

Die Parameters-Auflistung in SQL Server stellt die Überprüfung von Typ und Länge bereit. Wenn Sie die Parameters-Auflistung verwenden, werden Eingaben als Literalwert und nicht als ausführbarer Code behandelt. Ein weiterer Vorteil beim Verwenden der Parameters-Auflistung besteht im Erzwingen von Typ- und Längenprüfungen. Werte außerhalb des Bereichs lösen eine Ausnahme aus. Das folgende Codefragment zeigt die Verwendung der Parameters-Auflistung:

SqlDataAdapter myCommand = new SqlDataAdapter("AuthorLogin", conn);
myCommand.SelectCommand.CommandType = CommandType.StoredProcedure;
SqlParameter parm = myCommand.SelectCommand.Parameters.Add("@au_id",
     SqlDbType.VarChar, 11);
parm.Value = Login.Text;

In diesem Beispiel wird der @au_id-Parameter als Literalwert und nicht als ausführbarer Code behandelt. Bei diesem Wert werden Typ und Länge überprüft. Wenn der Wert @au_id keiner der angegebenen Typ- und Längeneinschränkungen entspricht, wird eine Ausnahme zurückgegeben.

Verwenden von parametrisierten Eingaben mit gespeicherten Prozeduren

Wenn gespeicherte Prozeduren ungefilterte Eingaben verwenden, sind sie möglicherweise anfällig für SQL Injection. Der folgende Code ist z. B. nicht gegen Angriffe geschützt:

SqlDataAdapter myCommand = 
new SqlDataAdapter("LoginStoredProcedure '" + 
                               Login.Text + "'", conn);

Wenn Sie gespeicherte Prozeduren verwenden, sollten Sie Parameter als Eingaben verwenden.

Verwenden der Parameterauflistung mit Dynamic SQL

Wenn die Verwendung von gespeicherten Prozeduren nicht möglich ist, können Sie weiterhin Parameter verwenden, wie in dem nachfolgenden Codebeispiel gezeigt wird:

SqlDataAdapter myCommand = new SqlDataAdapter(
"SELECT au_lname, au_fname FROM Authors WHERE au_id = @au_id", conn);
SQLParameter parm = myCommand.SelectCommand.Parameters.Add("@au_id", 
                        SqlDbType.VarChar, 11);
Parm.Value = Login.Text;

Filtern von Eingaben

Auch das Filtern von Eingaben kann sich beim Schutz vor SQL-Injection als hilfreich erweisen. Bei dieser Methode werden Escapezeichen entfernt. Die große Zeichenmenge kann aber möglicherweise Probleme verursachen, die dazu führen, dass diese Verteidigungsmaßnahme nicht verlässlich ist. Das folgende Beispiel sucht nach Zeichenfolgentrennzeichen.

private string SafeSqlLiteral(string inputSQL)
{
  return inputSQL.Replace("'", "''");
}

LIKE-Klauseln

Beachten Sie beim Verwenden einer LIKE-Klausel, dass Platzhalterzeichen weiterhin mit Escapezeichen versehen werden müssen:

s = s.Replace("[", "[[]");
s = s.Replace("%", "[%]");
s = s.Replace("_", "[_]");

Überprüfen von Code gegen SQL-Injection

Sie sollten den gesamten Code überprüfen, der EXECUTE, EXEC oder sp_executesql aufruft. Mithilfe von Abfragen, die den folgenden ähnlich sind, können Sie Prozeduren identifizieren, die diese Anweisungen enthalten. Diese Abfrage überprüft, ob nach den Wörtern EXECUTE oder EXEC 1, 2, 3 oder 4 Leerzeichen vorhanden sind.

SELECT object_Name(id) FROM syscomments

WHERE UPPER(text) LIKE '%EXECUTE (%'

OR UPPER(text) LIKE '%EXECUTE  (%'

OR UPPER(text) LIKE '%EXECUTE   (%'

OR UPPER(text) LIKE '%EXECUTE    (%'

OR UPPER(text) LIKE '%EXEC (%'

OR UPPER(text) LIKE '%EXEC  (%'

OR UPPER(text) LIKE '%EXEC   (%'

OR UPPER(text) LIKE '%EXEC    (%'

OR UPPER(text) LIKE '%SP_EXECUTESQL%'

Einzuschließende Parameter mit QUOTENAME() und REPLACE()

Überprüfen Sie für jede ausgewählte gespeicherte Prozedur, ob alle Variablen richtig behandelt werden, die in dynamischem Transact-SQL verwendet werden. Daten aus den Eingabeparametern der gespeicherten Prozedur oder Daten, die aus der Tabelle gelesen werden, sollten in QUOTENAME() oder REPLACE() eingeschlossen werden. Beachten Sie, dass der @variable-Wert, der an QUOTENAME() übergeben wird, zu sysname gehört. Er hat eine maximale Länge von 128 Zeichen.

@variable

Empfohlener Wrapper

Der Name des sicherungsfähigen Elements

QUOTENAME(@variable)

Zeichenfolge aus ≤ 128 Zeichen

QUOTENAME(@variable, '''')

Zeichenfolge aus > 128 Zeichen

REPLACE(@variable,'''', '''''')

Wenn Sie diese Technik verwenden, kann eine SET-Anweisung wie folgt überarbeitet werden:

--Before:

SET @temp = N'select * from authors where au_lname='''

+ @au_lname + N''''

--After:

SET @temp = N'select * from authors where au_lname='''

+ REPLACE(@au_lname,'''','''''') + N''''

Aktivieren von SQL-Injection durch das Abschneiden von Daten

Jede dynamische Transact-SQL-Anweisung, die einer Variablen zugewiesen ist, wird abgeschnitten, falls sie größer als der für die Variable zugewiesene Puffer ist. Ein Angreifer, der in der Lage ist, das Abschneiden der Anweisungen durch das Übergeben von unerwartet langen Zeichenfolgen an eine gespeicherte Prozedur zu erzwingen, kann das Ergebnis verändern. Beispielsweise ist die gespeicherte Prozedur, die von dem folgenden Skript erstellt wurde, anfällig für SQL-Injection durch aktiviertes Abschneiden.

CREATE PROCEDURE sp_MySetPassword

@loginname sysname,

@old sysname,

@new sysname

AS

-- Declare variable.

-- Note that the buffer here is only 200 characters long.

DECLARE @command varchar(200)

-- Construct the dynamic Transact-SQL.

-- In the following statement, we need a total of 154 characters

-- to set the password of 'sa'.

-- 26 for UPDATE statement, 16 for WHERE clause, 4 for 'sa', and 2 for

-- quotation marks surrounded by QUOTENAME(@loginname):

-- 200 – 26 – 16 – 4 – 2 = 154.

-- But because @new is declared as a sysname, this variable can only hold

-- 128 characters.

-- We can overcome this by passing some single quotation marks in @new.

SET @command= 'update Users set password=' + QUOTENAME(@new, '''') + ' where username=' + QUOTENAME(@loginname, '''') + ' AND password = ' + QUOTENAME(@old, '''')

-- Execute the command.

EXEC (@command)

GO

Mithilfe des Übergebens von 154 Zeichen an einen Puffer aus 128 Zeichen kann ein Angreifer ein neues Kennwort für sa festlegen, ohne das alte Kennwort zu kennen.

EXEC sp_MySetPassword 'sa', 'dummy', '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012'''''''''''''''''''''''''''''''''''''''''''''''''''

Aus diesem Grund sollten Sie einen großen Puffer für eine Befehlsvariable verwenden oder dynamisches Transact-SQL in der EXECUTE-Anweisung direkt ausführen.

Abschneiden beim Verwenden von QUOTENAME(@variable, '''') und REPLACE()

Zeichenfolgen, die von QUOTENAME() und REPLACE() zurückgegeben werden, werden automatisch abgeschnitten, falls Sie über dem zugewiesenen Speicherplatz liegen. Die im folgenden Beispiel erstellte, gespeicherte Prozedur zeigt, was in den Fällen passieren kann.

CREATE PROCEDURE sp_MySetPassword

@loginname sysname,

@old sysname,

@new sysname

AS

-- Declare variables.

DECLARE @login sysname

DECLARE @newpassword sysname

DECLARE @oldpassword sysname

DECLARE @command varchar(2000)

-- In the following statements, the data stored in temp variables

-- will be truncated because the buffer size of @login, @oldpassword,

-- and @newpassword is only 128 characters, but QUOTENAME() can return

-- up to 258 characters.

SET @login = QUOTENAME(@loginname, '''')

SET @oldpassword = QUOTENAME(@old, '''')

SET @newpassword = QUOTENAME(@new, '''')

-- Construct the dynamic Transact-SQL.

-- If @new contains 128 characters, then @newpassword will be '123... n

-- where n is the 127th character.

-- Because the string returned by QUOTENAME() will be truncated,

-- it can be made to look like the following statement:

-- UPDATE Users SET password ='1234. . .[127] WHERE username=' -- other stuff here

SET @command = 'UPDATE Users set password = ' + @newpassword

+ ' where username =' + @login + ' AND password = ' + @oldpassword;

-- Execute the command.

EXEC (@command)

GO

Daher wird die folgende Anweisung die Kennwörter von allen Benutzern auf den Wert festlegen, der im vorherigen Code übergeben wurde.

EXEC sp_MyProc '--', 'dummy', '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678'

Sie können das Abschneiden der Zeichenfolge durch das Überschreiten des zugewiesenen Pufferplatzes erzwingen, falls Sie REPLACE() verwenden. Die im folgenden Beispiel erstellte, gespeicherte Prozedur zeigt, was in den Fällen passieren kann.

CREATE PROCEDURE sp_MySetPassword

@loginname sysname,

@old sysname,

@new sysname

AS

-- Declare variables.

DECLARE @login sysname

DECLARE @newpassword sysname

DECLARE @oldpassword sysname

DECLARE @command varchar(2000)

-- In the following statements, data will be truncated because

-- the buffers allocated for @login, @oldpassword and @newpassword

-- can hold only 128 characters, but QUOTENAME() can return

-- up to 258 characters.

SET @login = REPLACE(@loginname, '''', '''''')

SET @oldpassword = REPLACE(@old, '''', '''''')

SET @newpassword = REPLACE(@new, '''', '''''')

-- Construct the dynamic Transact-SQL.

-- If @new contains 128 characters, @newpassword will be '123...n

-- where n is the 127th character.

-- Because the string returned by QUOTENAME() will be truncated, it

-- can be made to look like the following statement:

-- UPDATE Users SET password='1234…[127] WHERE username=' -- other stuff here

SET @command= 'update Users set password = ''' + @newpassword + ''' where username='''

+ @login + ''' AND password = ''' + @oldpassword + '''';

-- Execute the command.

EXEC (@command)

GO

Wie bei QUOTENAME() kann das Abschneiden der Zeichenfolge durch REPLACE() vermieden werden, indem temporäre Variablen deklariert werden, die für alle Fälle groß genug sind. Falls möglich, sollten Sie QUOTENAME() oder REPLACE() direkt innerhalb von dynamischem Transact-SQL aufrufen. Andernfalls können Sie den erforderlichen Puffer wie folgt berechnen. Für @outbuffer = QUOTENAME(@input) sollte die Größe von @outbuffer den Wert 2*(len(@input)+1). betragen. Falls Sie wie in dem vorherigen Beispiel REPLACE() und doppelte Anführungszeichen verwenden, reicht ein Puffer von 2*len(@input) aus.

Die folgende Berechnung deckt alle Fälle ab:

While len(@find_string) > 0, required buffer size =

round(len(@input)/len(@find_string),0) * len(@new_string)

+ (len(@input) % len(@find_string))

Abschneiden beim Verwenden von QUOTENAME(@variable, ']')

Ein Abschneiden ist möglich, wenn der Name eines SQL Server sicherungsfähigen Elements an Anweisungen übergeben wird, die das Format QUOTENAME(@variable, ']') verwenden. Im folgenden Beispiel wird dies veranschaulicht.

CREATE PROCEDURE sp_MyProc

@schemaname sysname,

@tablename sysname,

AS

-- Declare a variable as sysname. The variable will be 128 characters.

-- But @objectname actually must allow for 2*258+1 characters.

DECLARE @objectname sysname

SET @objectname = QUOTENAME(@schemaname)+'.'+ QUOTENAME(@tablename)

-- Do some operations.

GO

Wenn Sie Werte vom Typ sysname verketten, sollten Sie temporäre Variablen verwenden, die groß genug sind, um die maximale Anzahl von 128 Zeichen pro Wert aufzunehmen. Falls möglich, sollten Sie QUOTENAME() direkt innerhalb von dynamischem Transact-SQL aufrufen. Andernfalls können Sie die erforderliche Puffergröße so berechnen, wie es im vorherigen Abschnitt beschrieben wird.