MSDN Magazin > Home > Ausgaben > 2006 > November >  SQL Security: Neue SQL-Abschneidungsangriffe un...
SQL Security
Neue SQL-Abschneidungsangriffe und wie man sie vermeidet
Bala Neerumalla

Themen in diesem Artikel:
  • Begrenzen von Bezeichnern und Zeichenfolgen
  • Nützliche T-SQL-Funktionen
  • Abschneidungs- und Änderungsangriffe
  • Suchen nach Fehlern und Risiken
In diesem Artikel werden folgende Technologien verwendet:
SQL Server
SQL Injection-Angriffe haben aufgrund ihrer Fähigkeit, Firewalls und Systeme zur Aufdeckung von Angriffen zu durchdringen, wodurch die Datenebenen des Benutzers beeinträchtigt werden, viel Aufmerksamkeit erregt. Wenn man sich das grundlegende Codemuster ansieht, ähnelt dieses Problem allen anderen Injection-Problemen, bei denen nicht vertrauenswürdige Daten bei der Erstellung einer Anweisung verwendet werden. Dabei ist es unerheblich, ob es sich um eine Injection der ersten oder zweiten Ordnung handelt. Die meisten Entwickler vermeiden diese Schwachstellen in Web-Front-Ends mittlerweile, indem sie parametrisierte SQL-Abfragen in Verbindung mit gespeicherten Prozeduren am Back-End verwenden, aber es gibt einige Fälle, in denen Entwickler noch immer dynamisch konstruierten SQL-Code verwenden, etwa bei der Erstellung von Anweisungen in der Data Definition Language (DDL) auf der Grundlage von Benutzereingaben oder für Anwendungen, die in C/C++ geschrieben werden.
In diesem Artikel werden einige neue Ideen vorgestellt, die zu einer Änderung von SQL-Anweisungen oder zur Einfügung von SQL-Code führen können, selbst wenn der Code durch Begrenzungszeichen geschützt ist. Zuerst werden einige bewährte Methoden zur Erstellung von begrenzten Bezeichnern und SQL-Literalen vorgestellt und anschließend einige neue Möglichkeiten, wie Angreifer SQL-Code einfügen können. Dies wird Ihnen helfen, Ihre Anwendungen besser zu schützen.

Begrenzen von Bezeichnern und Zeichenfolgen
Bei SQL Server™ gibt es zwei Arten von Zeichenfolgevariablen: SQL-Bezeichner, die SQL-Objekte wie Tabellen, Ansichten und gespeicherte Prozeduren eindeutig identifizieren, und Zeichenfolgeliterale, die Daten darstellen. Die Begrenzung von SQL-Bezeichnern unterscheidet sich von der Begrenzung von Datenzeichenfolgen. Hier werden bewährte Methoden zur Erstellung von dynamischem SQL-Code vorgestellt, bei dem diese Datenvariablen verwendet werden müssen.
Begrenzte Bezeichner müssen verwendet werden, wenn Schlüsselwörter für die SQL-Objektnamen verwendet werden oder wenn Sonderzeichen in den Objektnamen enthalten sind. Angenommen, Sie möchten eine Anmeldung mit der Bezeichnung „my_dbreader“ ablegen. Dazu führen Sie folgende Anweisung aus:
DROP LOGIN my_dbreader
Was passiert, wenn Sie eine Anmeldung ablegen möchten, die „DROP“ (ebenfalls ein Schlüsselwort) als ihren Namen verwendet? Wenn Sie die folgende SQL-Anweisung verwenden, gibt SQL Server einen falschen Syntaxfehler zurück.
DROP LOGIN DROP
Was geschieht, wenn Sie beispielsweise eine Anmeldung mit der Bezeichnung „my][dbreader“ ablegen möchten? Dies führt ebenfalls zu einem Syntaxfehler.
Da der Anmeldename in beiden Fällen entweder ein Schlüsselwort ist oder Sonderzeichen enthält, müssen einige Anfangs- und Endmarkierungen bereitgestellt werden, damit SQL Server den Objektnamen in der SQL-Anweisung identifizieren kann.
Als Begrenzer für SQL-Bezeichner können Sie entweder doppelte Anführungszeichen oder eckige Klammern verwenden. Doppelte Anführungszeichen können jedoch nur dann verwendet werden, wenn die QUOTED_IDENTIFIER-Einstellung eingeschaltet ist, bei der es sich um eine verbindungsbasierte Einstellung handelt. Aus Gründen der Einfachheit hat es sich bewährt, immer eckige Klammern zu verwenden.
Um den Anmeldenamen „DROP“ erfolgreich abzulegen, können Sie eckige Klammern zur Erstellung Ihrer SQL-Anweisung verwenden.
DROP LOGIN [DROP]
Doch wie sieht es mit der folgenden Anweisung aus?
DROP LOGIN [my][dbreader]
Da der Anmeldename „my][dbreader“ die begrenzenden Zeichen enthält, geht SQL in diesem besonderen Fall davon aus, dass [my] der Anmeldename ist, da das Wort in eckigen Klammern steht. Da [dbreader] auf den Anmeldenamen folgt, ergibt dies keine richtige SQL-Anweisung und verursacht einen Syntaxfehler. Dieses Problem lässt sich beheben, indem Sie auf die rechte eckige Klammer eine weitere folgen lassen. Wenn Sie nun die folgende Anweisung ausführen, legt SQL Server erfolgreich die Anmeldung „my][dbreader“ ab:
DROP LOGIN [my]][dbreader]
Der Schutzmechanismus besteht einfach in der Verdoppelung der rechten eckigen Klammern. Bezüglich anderer Zeichen einschließlich der linken eckigen Klammern muss nichts unternommen werden.
Die Erstellung begrenzter Literale ähnelt der Erstellung begrenzter SQL-Bezeichner. Der Hauptunterschied besteht lediglich in dem zu verwendenden Begrenzungszeichen. Im Folgenden sehen Sie einige Beispiele, bevor für die Bildung eines begrenzten Zeichenfolgeliterals ähnliche Regeln festgelegt werden.
Angenommen, Sie möchten den Anmeldenamen „dbreader“ mit „P@$$w0rd“ als Kennwort erstellen. Dazu könnten Sie folgende SQL-Anweisung verwenden:
CREATE LOGIN [dbreader] WITH PASSWORD = 'P@$$w0rd'
In dieser Anweisung sind die Zeichenfolgedaten „P@$$w0rd“ durch einfache Anführungszeichen begrenzt, sodass SQL weiß, wo die Zeichenfolge beginnt und endet. Doch was geschieht, wenn die Datenzeichenfolge einfache Anführungszeichen enthält? SQL Server löst einen Fehler aus, und die Anweisung ist ungültig:
CREATE LOGIN [dbreader] WITH PASSWORD = 'P@$$'w0rd'
In der Zeichenfolge müssen alle einfachen Anführungszeichen geschützt werden, wenn eine gültige SQL-Anweisung erstellt werden soll:
CREATE LOGIN [dbreader] WITH PASSWORD = 'P@$$''w0rd'
Bei Ausführung dieser Anweisung erstellt SQL Server den Anmeldenamen „dbreader“ mit „P@$$'w0rd“ als Kennwort.
Als Begrenzer können Sie auch doppelte Anführungszeichen verwenden, doch wie bereits erwähnt, hängt der Erfolg dieses Ansatzes ganz davon ab, ob die Einstellung QUOTED_IDENTIFIER aktiviert ist. Daher ist es viel besser, immer einfache Anführungszeichen als Begrenzer für Zeichenfolgeliterale zu verwenden.

T-SQL-Funktionen
Die Regeln für die Behandlung von Bezeichnern und Zeichenfolgen sind also relativ einfach, und sie können manuell begrenzt werden, wenn Sie die Zeichenfolgen vorher kennen. Doch wie sieht es bei der Erstellung dynamischer T-SQL-Anweisungen auf der Grundlage von Benutzereingaben aus? Dazu bedarf es der Automatisierung. Zwei T-SQL-Funktionen, die Ihnen bei der Erstellung begrenzter Zeichenfolgen helfen können, sind QUOTENAME und REPLACE.
QUOTENAME gibt eine Unicode-Zeichenfolge mit hinzugefügten Begrenzern zurück, damit die Eingabezeichenfolge zu einem gültigen Bezeichner wird. Für die QUOTENAME-Funktion wird folgende Syntax verwendet:
QUOTENAME ( 'string' [ , 'delimiter' ] ) 
Sie übergeben an QUOTENAME eine zu begrenzende Zeichenfolge sowie eine aus einem Zeichen bestehende Zeichenfolge als Begrenzer. Bei dem Begrenzer kann es sich um eine eckige Klammer oder ein einfaches oder doppeltes Anführungszeichen handeln.
Diese Funktion dient hauptsächlich der Erstellung begrenzter SQL-Bezeichner. Aus diesem Grund wird nur der Typ „sysname“ akzeptiert, der in SQL Server „nvarchar(128)“ entspricht. Diese Funktion kann auch zur Erstellung begrenzter SQL-Zeichenfolgeliterale verwendet werden, doch aufgrund der Längeneinschränkung des Arguments funktioniert dies nur bei Zeichenfolgen, die aus 128 Zeichen oder weniger bestehen (hier kann die REPLACE-Funktion nützlich sein). Abbildung 1 zeigt, wie QUOTENAME von „sp_addlogin“ zur Erstellung von begrenzten Anmeldenamen und Kennwortzeichenfolgen verwendet wird. Da sowohl @loginname als auch @passwd ein sysname-Datentyp ist, kann die QUOTENAME-Funktion zur Erstellung eines begrenzten SQL-Bezeichners und eines begrenzten Literals verwendet werden. Selbst wenn jemand @loginname = 'my[]dbreader' und @passwd = 'P@$$''w0rd' übergibt, bietet sich keine Gelegenheit zur SQL Injection, da QUOTENAME die begrenzenden Zeichen richtig schützt.
create login [my[]]dbreader] with password = 'P@$$''w0rd'
create procedure sys.sp_addlogin
    @loginame       sysname
   ,@passwd         sysname = Null
   ,@defdb          sysname = ‘master’      
   ,@deflanguage    sysname = Null
   ,@sid            varbinary(16) = Null
   ,@encryptopt     varchar(20) = Null
AS
    -- SETUP RUNTIME OPTIONS / DECLARE VARIABLES --
    -- some code ----
    set @exec_stmt = ‘create login ‘ + quotename(@loginame, ‘[‘)

    if @passwd is null
        select @passwd = ‘‘

    if (@encryptopt is null)
        set @exec_stmt = @exec_stmt + ‘ with password = ‘ +
            quotename(@passwd, ‘‘‘‘)
    else
    -- some code
GO
Die REPLACE-Funktion ersetzt alle Vorkommnisse einer gegebenen Zeichenfolge durch eine angegebene Ersatzzeichenfolge. Im Gegensatz zu QUOTENAME gibt es keine Längeneinschränkungen bezüglich der Argumente, die akzeptiert werden.
REPLACE ( 'string1' , 'string2' , 'string3' )
REPLACE hat drei Zeichenfolgen: string1 ist der zu bearbeitende Ausdruck, string2 ist das in string1 zu ersetzende Element und string3 ist das anstelle von string2 zu verwendende Element. Diese Zeichenfolgeausdrücke können aus Zeichen oder binären Daten bestehen.
Zur Erstellung begrenzter SQL-Literale können Sie REPLACE zur Verdopplung des Vorkommens von einfachen Anführungszeichen verwenden, doch die Begrenzer müssen manuell hinzugefügt werden (einfache Anführungszeichen am Anfang und Ende). Abbildung 2 zeigt, wie sp_attach_single_file_db mithilfe dieser Funktion einen geschützten physischen Namen für eine Datei erstellt. „@physname“ ist „nvarchar(260)“, sodass Sie QUOTENAME nicht zur Erstellung des begrenzten Literals verwenden können, was der Grund zur Verwendung von REPLACE ist. Selbst wenn eine Zeichenfolge mit einfachem Anführungszeichen übergeben wird, kann nicht aus der SQL-Anweisung ausgebrochen und beliebiger SQL-Code eingefügt werden.
create procedure sys.sp_attach_single_file_db
    @dbname sysname,
    @physname nvarchar(260)
as
    declare @execstring nvarchar (4000)
    -- some code --
    select @execstring = ‘CREATE DATABASE ‘
        + quotename( @dbname , ‘[‘)
        + ‘ ON (FILENAME =‘
        + ‘‘‘‘
        + REPLACE(@physname,N’’’’,N’’’’’’)
        + ‘‘‘‘
        + ‘ ) FOR ATTACH’
    EXEC (@execstring)
    -- some code --
GO

SQL Injection-Schwachstellen
Sie sehen jetzt eine gespeicherte Prozedur, die das Kennwort eines Benutzerkontos nach Überprüfung des aktuellen Kennworts ändert (siehe Abbildung 3).
CREATE PROCEDURE sp_setPassword
    @username varchar(25),
    @old varchar(25),
    @new varchar(25)
AS

DECLARE @command varchar(100)

SET @command=
    ‘update Users set password=‘‘‘ + @new + 
    ‘‘‘ where username=‘‘‘ + @username + 
    ‘‘‘ AND password=‘‘‘ + @old + ‘‘‘‘

EXEC (@command)
GO   
Ein schneller Blick auf die gespeicherte Prozedur zeigt, dass keine Parameter von einfachen Anführungszeichen geschützt werden, sodass eine Schwachstelle für SQL Injection-Angriffe besteht. Ein Angreifer kann ein paar spezifische Argumente übergeben und die SQL-Anweisung wie folgt ändern:
update Users set password='NewP@ssw0rd' 
where username='admin' --' and password='dummy'
Das Ergebnis stellt das Kennwort für das Administratorkonto (oder jedes beliebige Konto) ein, ohne dass das eigentliche Kennwort erforderlich ist. In T-SQL lässt sich dies durch die Verwendung der REPLACE- oder QUOTENAME-Funktion beheben. Abbildung 4 zeigt den mit der REPLACE-Funktion korrigierten Code.
CREATE PROCEDURE sp_setPassword
    @username varchar(25),
    @old varchar(25),
    @new varchar(25)
AS

-- Declare variables.
DECLARE @command varchar(100)

-- Construct the dynamic SQL
SET @command= 
    ‘update Users set password=‘‘‘ + REPLACE(@new, ‘‘‘‘, ‘‘‘‘‘‘) + ‘‘‘‘ + 
    ‘ where username=‘‘‘ + REPLACE(@username, ‘‘‘‘, ‘‘‘‘‘‘) + ‘‘‘‘ + 
    ‘ AND password = ‘‘‘ + REPLACE(@old, ‘‘‘‘, ‘‘‘‘‘‘) + ‘‘‘‘

-- Execute the command.
EXEC (@command)
GO
Wie Sie sehen, verdoppelt REPLACE alle einfachen Anführungszeichen, die in den Parametern vorhanden sind. Wenn nun ein Angreifer dieselben Argumente übergibt, sieht die Anweisung so aus:
update Users set password='NewP@ssw0rd' 
where username='admin''--' and password='dummy'
Sie ist gegenüber typischen SQL Injection-Problemen nicht anfällig.

Änderung durch Abschneiden
Wenn Sie sich die zuvor gezeigte gespeicherte Prozedur genau ansehen, werden Sie feststellen, dass die Variable „@command“ nur 100 Zeichen enthalten kann, doch eine REPLACE-Funktion kann zu jeder aus 25 Zeichen bestehenden Variablen 50 Zeichen zurückgeben, wenn alle Zeichen einfache Anführungszeichen sind. SQL Server 2000 SP4 und SQL Server 2005 SP1 können die Daten automatisch abschneiden, wenn die Variable nicht über ausreichend große Puffer verfügt. Dies bietet Angreifern die Gelegenheit, die Befehlszeichenfolge abzuschneiden.
Wenn jemand wie in diesem Beispiel den Befehl direkt hinter dem Ausdruck username='username' abschneiden kann, ist es möglich, das Kennwort eines bekannten Benutzerkontos zu ändern, ohne das aktuelle Kennwort des Opfers zu kennen.
Angenommen, ein Angreifer weiß, dass ein Benutzer mit dem Namen Administrator in einer Webanwendung vorhanden ist (dabei könnte es sich um ein beliebiges Benutzerkonto handeln). Der Angreifer muss ein neues, 41 Zeichen langes Kennwort angeben, damit der Befehl lang genug ist, um richtig abgeschnitten zu werden (41 Zeichen, weil von den 100 Zeichen des Befehls 27 Zeichen von der Aktualisierungsanweisung, 17 von der WHERE-Klausel, 13 von "administrator" verwendet werden und weil 2 einfache Anführungszeichen das neue Kennwort umgeben).
Der Angreifer kann nur 25 Zeichen für das neue Kennwort übergeben. Dies kann er jedoch umgehen, indem er einfache Anführungszeichen übergibt, die durch die REPLACE-Funktion verdoppelt werden. Durch die Übergabe von 18 einfachen Anführungszeichen, 1 Großbuchstaben, 1 Symbol, 2 Kleinbuchstaben und 1 Ziffer kann der Angreifer also den Befehl direkt hinter dem Ausdruck where username='administrator' abschneiden. Wenn der Angreifer ''''''''''''''''''!Abb1 für den Parameter @new und administrator als username-Parameter übergibt, sieht der @command so aus:
update Users set password=
'''''''''''''''''''''''''''''''''''''!Abb1' where username='administrator'
In Abbildung 5 wird die Funktion QUOTENAME anstelle von REPLACE verwendet. Der einzige Unterschied zwischen dem vorherigen und diesem Beispiel besteht darin, dass der Entwickler im vorherigen Beispiel die aus einfachen Anführungszeichen bestehenden Begrenzer für den Benutzernamen, das neue Kennwort und das alte Kennwort hinzugefügt hat; im aktuellen Beispiel werden sie von der QUOTENAME-Funktion hinzugefügt. Da sich die vom Benutzer bereitgestellten Daten nicht geändert haben, kann dies von derselben Angreiferzeichenfolge ausgenutzt werden, die für das vorherige Beispiel verwendet wurde. Abbildung 6 ist eine gekürzte Version einer C/C++-Funktion, die in einer Anwendungsserver-Anwendung geschrieben wurde, um dieselbe Funktionalität zu erzielen. Sie ist gegenüber demselben Angriff anfällig.
DWORD ChangePassword(char* psUserName, char* psOld, char* psNew)
{
    char* psEscapedUserName = NULL;
    char* psEscapedOldPW = NULL;
    char* psEscapedNewPW = NULL;
    char szSQLCommand[100];
    HRESULT hr=0;
    
    // Input Validation
    ...

    // Calculate and allocate the new buffer with length 
    // userdatalen*2 + 1
    // Escape all single quotes with double quotes
    ...

    //Construct the query
    hr = StringCchPrintf(szSQLCommand, sizeof(szSQLCommand)/sizeof(char),
        "Update Users set password=‘%s’ where username=‘%s’"
        "AND password=‘%s’,
        psEscapedNewPW, psEscapedUserName, psEscapedOldPW);
    
    if (S_OK != hr)
    {
        // handle error cases
    }

    // Execute and return
}

CREATE PROCEDURE sp_setPassword
    @username varchar(25),
    @old varchar(25),
    @new varchar(25)
AS
-- Declare variables.
DECLARE @command varchar(100)

-- In the following statement, we will need 43 characters 
-- to set an administrator password without knowing its current password.
-- 100 - 26 - 16 - 15 = 43 (26 for update stmt, 16 for where clause, 
-- 15 for ‘administrator’). But @new only takes 25 characters, which we 
-- can get around by using single quotes. So one can pass the following 
-- parametes and set admin password. @new = 18 single quotes, 1 Capital 
-- letter, 1 symbol, 2 small case letters, 1 digit
-- @username = administrator
-- @command becomes 
-- update Users set password=‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘!Abb1’ 
-- where username=‘administrator’
SET @command= ‘update Users set password=‘ + QUOTENAME(@new,’’’’) + 
‘ where username=‘ + QUOTENAME(@username,’’’’) + ‘ AND password = ‘ + QUOTENAME(@old,’’’’)

-- Execute the command.
EXEC (@command)
GO


SQL Injection durch Abschneiden
Abbildung 7 zeigt eine weitere Variante desselben Codes, der jedoch durch die Verwendung separater Variablen festgelegt wurde. Wie Sie sehen, speichert dieser Code die geschützten Zeichenfolgen in separaten Variablen, und @command verfügt über ausreichend Puffer, um die gesamte Zeichenfolge aufzunehmen. @escaped_username, @escaped_oldpw und @escaped_newpw werden als varchar(25) deklariert, sie müssten jedoch in dem Fall, in dem alle Zeichen in @username, @old und @new 25 einfache Anführungszeichen sind, 50 Zeichen aufnehmen. Dies bietet die Gelegenheit, die geschützte Zeichenfolge abzuschneiden.
CREATE PROCEDURE sp_setPassword
    @username varchar(25),
    @old varchar(25),
    @new varchar(25)
AS
-- Declare variables.
DECLARE @escaped_username varchar(25)
DECLARE @escaped_oldpw varchar(25)
DECLARE @escaped_newpw varchar(25)
DECLARE @command varchar(250)

SET @escaped_username = REPLACE(@username, ‘‘‘‘, ‘‘‘‘‘‘)
SET @escaped_oldpw = REPLACE(@old, ‘‘‘‘, ‘‘‘‘‘‘)
SET @escaped_newpw = REPLACE(@new, ‘‘‘‘, ‘‘‘‘‘‘)

SET @command = 
    ‘update Users set password=‘‘‘ + @escaped_newpw + ‘‘‘‘ + 
    ‘ where username=‘‘‘ + @escaped_username + ‘‘‘‘ + 
    ‘ AND password = ‘‘‘ + @escaped_oldpw + ‘‘‘‘
EXEC (@command)
GO
Ein Angreifer kann 123...n' als neues Kennwort übergeben, wobei n das 24. Zeichen ist, @escaped_newpw ebenfalls zu 123...n' machen (das zweite einfache Anführungszeichen, das von der REPLACE-Funktion zurückgegeben wurde, wird abgeschnitten) und die endgültige Abfrage so gestalten, dass sie wie die folgende aussieht, die er durch das Einführen von Code über das Feld „Benutzername“ ausnutzen kann:
update users set password='123...n'' 
where username='<SQL Injection here using Username>
Dieses Codemuster ist gefährlicher, da es die Gelegenheit bietet, SQL-Code einzufügen, statt nur vorhandenen SQL-Code anzuschneiden.
Abbildung 8 zeigt ein weiteres Beispiel derselben Variante, bei der die Funktion QUOTENAME anstelle von REPLACE verwendet wird. Da QUOTENAME die Begrenzer hinzufügt, ist die Nutzlast anders, dennoch ist der Code gegenüber SQL Injection-Angriffen anfällig.
ALTER PROCEDURE sp_setPassword
    @username varchar(25),
    @old varchar(25),
    @new varchar(25)
AS
-- Declare variables.
DECLARE @quoted_username varchar(25)
DECLARE @quoted_oldpw varchar(25)
DECLARE @quoted_newpw varchar(25)
DECLARE @command varchar(250)

-- In the following statements, all the variables can only hold 
-- 25 characters, but quotename() will return 52 characters when all 
-- the characters are single quotes.
SET @quoted_username = QUOTENAME(@username, ‘‘‘‘)
SET @quoted_oldpw = QUOTENAME(@old, ‘‘‘‘)
SET @quoted_newpw = QUOTENAME(@new, ‘‘‘‘)

-- By passing the new password as 123...n where n is 24th character, 
-- @quoted_newpw becomes ‘123..n
-- Observe carefully that there is no trailing single quote as it gets 
-- truncated.
-- So the final query becomes something like this
-- update users set password=‘123...n where username=‘ <SQL Injection 
-- here using Username>
SET @command= ‘update Users set password=‘ + @quoted_newpw + 
              ‘ where username=‘ + @quoted_username + 
              ‘ AND password = ‘ + @quoted_oldpw
EXEC (@command)
GO
In diesem Fall speichert der Code die begrenzten Zeichenfolgen in separaten Variablen, und @command verfügt über ausreichend Puffer, um die ganze Befehlszeichenfolge aufzunehmen. Wie beim vorhergehenden Beispiel liegt das Problem bei den Variablen mit Anführungszeichen: @quoted_username, @quoted_oldpw und @quoted_newpw. Sie werden als varchar(25) deklariert, erfordern jedoch 52 Zeichen in dem Fall, in dem alle Zeichen in @username, @old und @new 25 einfache Anführungszeichen sind. (QUOTENAME fügt zudem den Anfangs- und Endbegrenzer hinzu.) Dies bietet dem Angreifer Gelegenheit, die begrenzte Zeichenzeichenfolge abzuschneiden.
Ein Angreifer kann 123...n als neues Kennwort übergeben (wobei n das 24. Zeichen ist), @escaped_newpw ebenfalls in '123...n ändern (das einleitende einfache Anführungszeichen wird von QUOTENAME hinzugefügt) und die endgültige Abfrage wie folgt gestalten, sodass er sie durch das Einfügen von Code über das Feld „Benutzername“ ausnutzen kann:
update users set 
password='123...n where 
username=' <SQL Injection here using Username>
Abbildung 9 ist eine gekürzte Version dieses Codes in C/C++, mit dem sich dieselbe Funktionalität erzielen lässt. Der Code ist genauso anfällig.
DWORD ChangePassword(char* psUserName, char* psOld, char* psNew)
{
    char szEscapedUserName[26];
    char szEscapedOldPW[26];
    char szEscapedNewPW[26];
    char szSQLCommand[250];
    
    // Input Validation

    // Escape User supplied data
    Replace(psUserName, "’", "’’", szEscapedUserName, 
            sizeof(szEscapedUserName));
    Replace(psPassword, "’", "’’", szEscapedOldPW, 
            sizeof(szEscapedOldPW));
    Replace(psPassword, "’", "’’", szEscapedNewPW, 
            sizeof(szEscapedNewPW));
    
    // Construct the query
    StringCchPrintf(szSQLCommand, sizeof(szSQLCommand)/sizeof(char),
        "Update Users set password=‘%s’ where username=‘%s’"
        "AND password=‘%s’,
        szEscapedNewPW, szEscapedUserName,szEscapedOldPW);

    // Execute and return
}
Obwohl ich T-SQL-Code zu Demonstrationszwecken verwendet habe, muss man in der Praxis keine dynamischen SQL Data Manipulation Language-Anweisungen verwenden, sodass die meisten Anwendungen, die DML-Code enthalten, gegenüber diesen Problemen nicht anfällig sind.
Im Folgenden sehen Sie ein weiteres Beispiel, das eine dynamische DDL-Anweisung auf der Grundlage von Benutzereingaben erstellt. Abbildung 10 veranschaulicht dies. Wie bei den vorherigen Beispielen gibt es bei den folgenden Anweisungen Abschneidungsprobleme:
set @escaped_oldpw = quotename(@old, '''')
set @escaped_newpw = quotename(@new, '''')
Diese können ausgenutzt werden, indem @new = '123...' übergeben wird, wobei das 127. Zeichen (keine einfachen Anführungszeichen) mit @old = '; SQL Injection' beginnt, sodass die SQL-Anweisung dann so aussieht:
alter login [loginname] 
with password = '123... old_password = '; SQL
Injection
create procedure sys.sp_password
    @old sysname = NULL,        -- the old (current) password
    @new sysname,               -- the new password
    @loginame sysname = NULL    -- user to change password on
as
    -- SETUP RUNTIME OPTIONS / DECLARE VARIABLES --
    set nocount on
    declare @exec_stmt nvarchar(4000)
    declare @escaped_oldpw sysname
    declare @escaped_newpw sysname

    set @escaped_oldpw = quotename(@old, ‘‘‘‘)
    set @escaped_newpw = quotename(@new, ‘‘‘‘)

    set @exec_stmt = ‘alter login ‘ + quotename(@loginame) +
        ‘ with password = ‘ + @escaped_newpw + ‘ old_password = ‘ + 
        @escaped_old

    exec (@exec_stmt)

    if @@error <> 0
        return (1)

    -- RETURN SUCCESS --
    return  (0)    -- sp_password
Obwohl bei gespeicherten Prozeduren diese Probleme eher auftreten, resultieren nicht alle in Sicherheitsrisiken. Im Folgenden erfahren Sie, welche Prozeduren sorgfältig geprüft werden müssen.
In SQL Server werden alle gespeicherten Prozeduren standardmäßig im Kontext des Aufrufenden ausgeführt. Selbst wenn eine Prozedur also SQL Injection-Probleme hat, kann ein bösartiger lokaler Benutzer, der bezüglich der Prozedur ausführungsberechtigt ist, seine Rechte nicht erhöhen, und der eingefügte Code wird in seinem Kontext ausgeführt. Wenn jedoch ein internes Wartungsskript vorhanden ist, das als Besitzer oder als anderer bestimmter Benutzer (unter Verwendung der EXECUTE AS-Funktion) ausgeführt wird, kann der Aufrufende den Code in einem anderen Benutzerkontext ausführen und seine Rechte erhöhen, sodass sie den Rechten dieses Benutzers entsprechen.
Bei allen Abschneidungsproblemen handelt es sich definitiv um Fehler, obwohl diese nicht unbedingt Sicherheitsrisiken darstellen. Man sollte sie jedoch beheben, da man nie wissen kann, wer sie finden und in Zukunft verwenden wird.
Es gibt weitere Schritte, mit denen Injection-Schwachstellen in Ihrem SQL-Code verringert werden können. Zuerst sollten Sie die Verwendung von dynamischem SQL-Code für DML-Anweisungen in gespeicherten Prozeduren vermeiden. Ist dies nicht möglich, verwenden Sie „sp_executesql“. Zweitens sollten Sie, wie die Beispiele in diesem Artikel es veranschaulichen, Pufferlängen richtig berechnen. Schließlich sollten im C/C++-Code die Rückgabewerte von Zeichenfolgen geprüft werden, um festzustellen, ob die Zeichenfolge abgeschnitten wurde, was entsprechend zu einem Fehler führen würde. In der Randleiste "Techniken zum Aufdecken von Schwachstellen" finden Sie eine Zusammenfassung der Schritte, die Sie unternehmen können.

Erkennen von Injection durch Abschneiden
Um SQL Injection durch Abschneidungsprobleme mit automatisierten Tools zu erkennen, ist eine gute Kenntnis aller Codemuster erforderlich, die zu Abschneidungsmöglichkeiten führen. Sie können mit verschiedenen Zeichenfolgedaten verschiedene bestimmte Codemuster ins Visier nehmen. Gehen Sie bei den folgenden Szenarios davon aus, dass n die Länge des Eingabepuffers ist.
Um Begrenzungsprobleme bei der QUOTENAME-Funktion zu erkennen, wird von der Annahme ausgegangen, dass QUOTENAME (oder eine ähnliche Funktion für C/C++-Anwendungen) zur Erstellung eines begrenzten Bezeichners oder Literals verwendet wird und dass die Puffergröße der begrenzten Zeichenfolge weniger als 2 * n + 2 beträgt. Um diese Probleme zu suchen, wenn die Pufferlänge der begrenzten Zeichenfolge gleich n ist, übergeben Sie lange Zeichenfolgen mit nicht begrenzenden Zeichen. Der nachgestellte Begrenzer wird abgeschnitten, und es bietet sich eine Injection-Möglichkeit, bei der eine andere Eingabevariable verwendet wird.
Um diese Probleme zu suchen, wenn die begrenzte Pufferlänge ungerade ist, übergeben Sie lange Zeichenfolgen, die aus einfachen Anführungszeichen (oder rechten eckigen Klammern oder doppelten Anführungszeichen) bestehen. Da QUOTENAME alle vorhandenen Begrenzer verdoppelt und ein einleitendes Begrenzungszeichen hinzufügt, wird der nachgestellte Begrenzer abgeschnitten, da der Puffer für geschützte Zeichenfolgen nur eine ungerade Anzahl von Zeichen enthalten kann.
Um diese Probleme zu suchen, wenn die begrenzte Pufferlänge eine gerade Zahl ist, übergeben Sie Zeichenfolgen wie 1', 1'', 1''', 1'''' und so weiter, wobei die Anzahl der einfachen Anführungszeichen (oder rechten eckigen Klammern) bei jeder Iteration inkrementiert wird. Da QUOTENAME alle vorhandenen einfachen Anführungszeichen verdoppelt, enthält die Rückgabezeichenfolge eine gerade Zahl an einfachen Anführungszeichen sowie – neben dem einleitenden Begrenzer und 1 – eine gerade Anzahl von Zeichen. Aufgrund dessen wird der nachgestellte Begrenzer abgeschnitten.
Sie können diese Probleme auch erkennen, wenn REPLACE (oder eine ähnliche Funktion in C/C++-Anwendungen) zur Erstellung der geschützten Zeichenfolge verwendet wird und wenn die Puffergröße für geschützte Zeichenfolgen weniger als 2 * n beträgt. Um diese Probleme zu suchen, wenn die Pufferlänge der geschützten Zeichenfolgen gleich n ist, übergeben Sie Zeichenfolgen wie 1', 12', 123' und 123...n', wobei die Eingabezeichenfolge bei jeder Iteration inkrementiert wird. Wenn Sie in diesem Fall die richtige Länge erreichen, verdoppelt die REPLACE-Funktion das letzte einfache Anführungszeichen. Da die geschützte Zeichenfolgevariable nicht genug Pufferspeicher hat, wird das letzte einfache Anführungszeichen abgeschnitten und als übergeben gespeichert, was Gelegenheit zum Ausbrechen aus der SQL-Anweisung bietet.
Um Probleme mit der REPLACE-Funktion zu suchen, wenn die geschützte Pufferlänge ungerade ist, übergeben Sie inkrementelle Längen von Zeichenfolgen mit einfachen Anführungszeichen wie ', '', ''' und ''''...'/B> (oder übergeben einfach lange, aus einfachen Anführungszeichen bestehende Zeichenfolgen). In diesem Fall verdoppelt REPLACE alle vorkommenden einfachen Anführungszeichen. Da die Puffergröße jedoch ungerade ist, wird das letzte einfache Anführungszeichen abgeschnitten, was die Gelegenheit zum Ausbrechen aus der Anweisung bietet.
Um diese Probleme zu suchen, wenn die geschützte Pufferlänge eine gerade Zahl ist, übergeben Sie Zeichenfolgen wie 1', 1'', 1''', 1'''' und so weiter, wobei die Anzahl der einfachen Anführungszeichen (oder rechten eckigen Klammern) bei jeder Iteration inkrementiert wird. Der Rückgabewert ohne die einleitende 1 enthält eine gerade Anzahl an Zeichen, sodass der gesamte Rückgabewert eine ungerade Zeichenzahl haben wird. Da die Pufferlänge gerade ist, wird das nachgestellte einfache Anführungszeichen abgeschnitten, was die Gelegenheit bietet, aus der SQL-Anweisung auszubrechen.
Techniken zum Erkennen von Schwachstellen
Verwenden von Codeüberprüfungen Hier finden Sie einige Techniken zum Erkennen von Problemen in Ihren SQL-Anweisungen, wenn Sie Codeüberprüfungen durchführen.

Erkennen von SQL Injections erster oder zweiter Ordnung
  • Suchen Sie die APIs, die zur Ausführung von SQL-Anweisungen dienen.
  • Prüfen Sie, ob bei den in der dynamischen SQL-Anweisung verwendeten Daten eine Datenüberprüfung durchgeführt wird.
  • Wenn keine Datenüberprüfung stattfindet, prüfen Sie, ob die Daten durch begrenzende Zeichen (einfache Anführungszeichen für Zeichenfolgeliterale und rechte eckige Klammern für SQL-Bezeichner) geschützt werden.

Erkennen von SQL-Änderungen durch Abschneidungsprobleme
  • Prüfen Sie die Pufferlängen, die zum Speichern der endgültigen dynamischen SQL-Anweisung dienen.
  • Berechnen Sie die maximale Puffergröße, die zur Aufnahme der SQL-Anweisung erforderlich ist, wenn die Eingabe maximale Größe hat, und prüfen Sie, ob der Puffer zur Aufnahme der SQL-Anweisung groß genug ist.
  • Achten Sie besonders auf die Rückgabewerte der QUOTENAME- oder REPLACE-Funktion. Wenn die Länge der Dateneingabe n Zeichen beträgt, wird von diesen Funktionen 2 * n + 2 oder 2 * n zurückgegeben, wenn es sich bei allen Eingabezeichen um Begrenzungszeichen handelt.
  • Bei C/C++-Anwendungen prüfen Sie, ob die Rückgabewerte aus APIs wie beispielsweise „StringCchPrintf“, die zur Erstellung der SQL-Anweisung dienen, auf Fehler wie unzureichenden Puffer überprüft werden.

Erkennen von SQL Injection durch Abschneidungsprobleme
  • Prüfen Sie die Pufferlängen, die zum Speichern der begrenzten Zeichenfolgen oder geschützten Zeichenfolgen dienen.
  • Wenn n die Länge der Eingabezeichenfolge ist, brauchen Sie 2 * n + 2 zum Speichern des Rückgabewerts aus der QUOTENAME-Funktion und 2 * n zum Speichern des Rückgabewerts aus der REPLACE-Funktion.
  • Bei C/C++-Anwendungen prüfen Sie, ob die Rückgabewerte von entsprechenden Ersatzfunktionen auf Fehler wie unzureichenden Puffer geprüft werden.

Verwenden von Black-Box-Methoden Wenn Sie über automatisierte Tools oder Smart-Fuzzer verfügen, gibt es einige Techniken zum Erkennen von Problemen in Ihren SQL-Anweisungen.

Erkennen von SQL Injection-Problemen
  • Senden Sie einfache Anführungszeichen als Eingabedaten, um die Fälle zu suchen, in denen die Benutzereingabe nicht bereinigt und als Zeichenfolgeliterale in einer dynamischen SQL-Anweisung verwendet wird.
  • Verwenden Sie rechte eckige Klammern (das Zeichen ]) als Dateneingabe, um nach Fällen zu suchen, wo die Benutzereingabe als Teil eines SQL-Bezeichners ohne Eingabebereinigung verwendet wird.

Erkennen von Abschneidungsproblemen
  • Senden Sie lange Zeichenfolgen so, wie Sie Zeichenfolgen senden würden, um Pufferüberläufe festzustellen.

Erkennen von SQL-Änderungen durch Abschneidungsprobleme
  • Senden Sie lange Zeichenfolgen, die aus einfachen Anführungszeichen (oder rechten eckigen Klammern oder doppelten Anführungszeichen) bestehen. Dadurch haben die Rückgabewerte der REPLACE- und QUOTENAME-Funktion maximale Größe und könnten die zur Aufnahme der SQL-Anweisung verwendete Befehlsvariable abschneiden.


Bala Neerumalla arbeitet als Entwickler von Sicherheitssoftware bei Microsoft. Er hat sich auf die Suche nach Sicherheitsrisiken bei Anwendungen spezialisiert.

Page view tracker