Delen via


SQL-injectie

van toepassing op:SQL ServerAzure SQL DatabaseAzure SQL Managed InstanceAzure Synapse AnalyticsAnalytics Platform System (PDW)SQL-database in Microsoft Fabric

SQL-injectie is een aanval waarbij schadelijke code wordt ingevoegd in tekenreeksen die later worden doorgegeven aan een exemplaar van de SQL Server Database Engine voor parseren en uitvoeren. Elke procedure waarmee SQL-instructies worden gemaakt, moet worden gecontroleerd op beveiligingsproblemen met injectie, omdat de database-engine alle syntactisch geldige query's uitvoert die worden ontvangen. Zelfs geparameteriseerde gegevens kunnen worden gemanipuleerd door een ervaren en vastberaden aanvaller.

Hoe SQL-injectie werkt

De primaire vorm van SQL-injectie bestaat uit het direct invoegen van code in gebruikersinvoervariabelen die worden samengevoegd met SQL-opdrachten en worden uitgevoerd. Een minder directe aanval injecteert schadelijke code in tekenreeksen die zijn bestemd voor opslag in een tabel of als metagegevens. Wanneer de opgeslagen tekenreeksen vervolgens worden samengevoegd in een dynamische SQL-opdracht, wordt de schadelijke code uitgevoerd.

Het injectieproces werkt door een tekenreeks voortijdig te beëindigen en een nieuwe opdracht toe te voegen. Omdat aan de ingevoegde opdracht mogelijk extra tekenreeksen zijn toegevoegd voordat deze wordt uitgevoerd, beëindigt de malefactor de geïnjecteerde tekenreeks met een opmerkingsmarkering --. Volgende tekst wordt tijdens de uitvoering genegeerd.

In het volgende script ziet u een eenvoudige SQL-injectie. Met het script wordt een SQL-query gemaakt door vastgelegde tekenreeksen samen te voegen met een tekenreeks die door de gebruiker is ingevoerd:

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

De gebruiker wordt gevraagd de naam van een plaats in te voeren. Zodra Redmond wordt ingevoerd, ziet de query die door het script is samengesteld er ongeveer als volgt uit:

SELECT * FROM OrdersTable WHERE ShipCity = 'Redmond';

Stel echter dat de gebruiker de volgende tekst invoert:

Redmond';drop table OrdersTable--

In dit geval stelt het script de volgende query samen:

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

De puntkomma (;) geeft het einde van de ene query en het begin van een andere aan. Het dubbele afbreekstreepje (--) geeft aan dat de rest van de huidige regel een opmerking is en moet worden genegeerd. Als de gewijzigde code syntactisch juist is, wordt deze uitgevoerd door de server. Wanneer de database-engine deze instructie verwerkt, worden eerst alle records in OrdersTable geselecteerd waar ShipCityRedmond is. De database-engine verwijdert vervolgens OrdersTable.

Zolang de geïnjecteerde SQL-code syntactisch correct is, kan manipulatie niet programmatisch worden gedetecteerd. Daarom moet u alle gebruikersinvoer valideren en zorgvuldig de code controleren waarmee samengestelde SQL-opdrachten worden uitgevoerd op de server die u gebruikt. Aanbevolen procedures voor coderen worden beschreven in de volgende secties in dit artikel.

Alle invoer valideren

Valideer gebruikersinvoer altijd door het testtype, de lengte, de notatie en het bereik te testen. Wanneer u voorzorgsmaatregelen voor schadelijke invoer implementeert, moet u rekening houden met de architectuur- en implementatiescenario's van uw toepassing. Houd er rekening mee dat programma's die zijn ontworpen om te worden uitgevoerd in een beveiligde omgeving, kunnen worden gekopieerd naar een onbeveiligde omgeving. De volgende suggesties moeten worden beschouwd als best practices:

  • Maak geen veronderstellingen over de grootte, het type of de inhoud van de gegevens die door uw toepassing worden ontvangen. U moet bijvoorbeeld de volgende evaluatie uitvoeren:

    • Hoe gedraagt uw toepassing zich als een ongewenste of kwaadwillende gebruiker een videobestand van 2 GB invoert waar uw toepassing een postcode verwacht?

    • Hoe gedraagt uw toepassing zich als een DROP TABLE instructie is ingesloten in een tekstveld?

  • Test de grootte en het gegevenstype van invoer en dwing de juiste limieten af. Dit kan helpen bij het voorkomen van opzettelijke bufferoverschrijdingen.

  • Test de inhoud van tekenreeksvariabelen en accepteer alleen verwachte waarden. Negeer vermeldingen die binaire gegevens, escapereeksen en opmerkingstekens bevatten. Dit kan helpen bij het voorkomen van scriptinjectie en kan beschermen tegen bepaalde bufferoverschrijdingsexplosies.

  • Wanneer u met XML-documenten werkt, valideert u alle gegevens op basis van het bijbehorende schema terwijl deze worden ingevoerd.

  • Bouw nooit Transact-SQL instructies rechtstreeks vanuit gebruikersinvoer.

  • Gebruik opgeslagen procedures om gebruikersinvoer te valideren.

  • In omgevingen met meerdere lagen moeten alle gegevens worden gevalideerd voordat ze worden toegelaten tot de vertrouwde zone. Gegevens die niet voldoen aan het validatieproces, moeten worden geweigerd en er moet een fout worden geretourneerd naar de vorige laag.

  • Implementeer meerdere validatielagen. Voorzorgsmaatregelen die u neemt tegen toevallig kwaadwillende gebruikers, kunnen ineffectief zijn tegen bepaalde aanvallers. Een betere procedure is het valideren van invoer in de gebruikersinterface en op alle volgende punten waar een vertrouwensgrens wordt overschreden.

    Gegevensvalidatie in een toepassing aan de clientzijde kan bijvoorbeeld eenvoudige scriptinjectie voorkomen. Als de volgende laag echter ervan uitgaat dat de invoer al is gevalideerd, kan elke kwaadwillende gebruiker die een client kan omzeilen onbeperkte toegang tot een systeem hebben.

  • Voeg nooit gebruikersinvoer samen die niet is gevalideerd. Tekenreekssamenvoeging is het primaire toegangspunt voor scriptinjectie.

  • Accepteer niet de volgende tekenreeksen in velden waaruit bestandsnamen kunnen worden samengesteld: AUX, CLOCK$, COM1, COM8, CON, CONFIG$, LPT1, LPT8, NUL en PRN.

Indien mogelijk negeert u invoer die de volgende tekens bevat.

Invoerteken Betekenis in Transact-SQL
; Scheidingsteken voor query
' Tekenreeksscheidingsteken.
-- Scheidingsteken voor opmerkingen met één regel. Tekst die -- volgt tot het einde van die regel wordt niet geëvalueerd door de server.
/*** ... ***/ Scheidingstekens voor opmerkingen. Tekst tussen /* en */ wordt niet geëvalueerd door de server.
xp_ Wordt gebruikt aan het begin van de naam van door de catalogus uitgebreide opgeslagen procedures, zoals xp_cmdshell.

Typeveilige SQL-parameters gebruiken

De Parameters verzameling in de database-engine biedt typecontrole en lengtevalidatie. Als u de Parameters verzameling gebruikt, wordt invoer beschouwd als een letterlijke waarde in plaats van als uitvoerbare code. Een ander voordeel van het gebruik van de Parameters verzameling is dat u type- en lengtecontroles kunt afdwingen. Waarden buiten het bereik activeren een uitzondering. Het volgende codefragment wordt weergegeven met behulp van de Parameters verzameling:

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 dit voorbeeld wordt de @au_id parameter behandeld als een letterlijke waarde in plaats van als uitvoerbare code. Deze waarde wordt gecontroleerd op type en lengte. Als de waarde @au_id niet voldoet aan de opgegeven type- en lengtebeperkingen, wordt er een uitzondering gegenereerd.

Geparameteriseerde invoer gebruiken met opgeslagen procedures

Opgeslagen procedures zijn mogelijk vatbaar voor SQL-injectie als ze niet-gefilterde invoer gebruiken. De volgende code is bijvoorbeeld kwetsbaar:

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

Als u opgeslagen procedures gebruikt, moet u parameters gebruiken als invoer.

De verzameling Parameters gebruiken met dynamische SQL

Als u geen opgeslagen procedures kunt gebruiken, kunt u nog steeds parameters gebruiken, zoals wordt weergegeven in het volgende codevoorbeeld.

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;

Filterinvoer

Het filteren van invoer kan ook handig zijn bij het beveiligen tegen SQL-injectie door escape-tekens te verwijderen. Vanwege het grote aantal tekens dat problemen kan veroorzaken, is filteren echter geen betrouwbare verdediging. In het volgende voorbeeld wordt gezocht naar het tekenreeksscheidingsteken.

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

LIKE-clausules

Als u een LIKE clausule gebruikt, moeten jokertekens nog steeds worden geëscaped.

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

Code voor SQL-injectie controleren

Controleer alle code die EXECUTE, EXEC of sp_executesql aanroept. U kunt query's gebruiken die vergelijkbaar zijn met de volgende om u te helpen bij het identificeren van procedures die deze instructies bevatten. Met deze query wordt gecontroleerd op 1, 2, 3 of 4 spaties na de woorden EXECUTE of EXEC.

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%';

Parameters verpakken met QUOTENAME() en REPLACE()

Controleer in elke geselecteerde opgeslagen procedure of alle variabelen die worden gebruikt in dynamische Transact-SQL correct worden verwerkt. Gegevens die afkomstig zijn van de invoerparameters van de opgeslagen procedure of die uit een tabel worden gelezen, moeten worden verpakt in QUOTENAME() of REPLACE(). Houd er rekening mee dat de waarde van @variable die wordt doorgegeven QUOTENAME() , sysname is en een maximale lengte van 128 tekens heeft.

@variable Aanbevolen wrapper
Naam van een beveiligbaar QUOTENAME(@variable)
Tekenreeks van <= 128 tekens QUOTENAME(@variable, '''')
Tekenreeks van > 128 tekens REPLACE(@variable,'''', '''''')

Wanneer u deze techniek gebruikt, kan een SET instructie als volgt worden gewijzigd:

-- 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'''';

Injectie ingeschakeld door afkapping van gegevens

Dynamische Transact-SQL die aan een variabele is toegewezen, wordt ingekort als deze groter is dan de buffer die voor die variabele is gereserveerd. Een aanvaller die afkapping van instructies kan afdwingen door onverwacht lange tekenreeksen door te geven aan een opgeslagen procedure, kan het resultaat bewerken. De volgende voorbeeld van een opgeslagen procedure is bijvoorbeeld kwetsbaar voor injectie door afkapping.

In dit voorbeeld hebben we een @command buffer met een maximale lengte van 200 tekens. We hebben in totaal 154 tekens nodig om het wachtwoord in te stellen van 'sa': 26 voor de UPDATE instructie, 16 voor de WHERE component, 4 voor 'sa'en 2 voor aanhalingstekens tussen QUOTENAME(@loginname): 200 - 26 - 16 - 4 - 2 = 154. Maar omdat @new deze variabele is gedeclareerd als sysname, mag deze variabele slechts 128 tekens bevatten. We kunnen dit oplossen door enkele aanhalingstekens in @new door te geven.

CREATE PROCEDURE sp_MySetPassword
    @loginname SYSNAME,
    @old SYSNAME,
    @new SYSNAME
AS
-- Declare variable.
DECLARE @command VARCHAR(200)

-- Construct the dynamic Transact-SQL.
SET @command = 'UPDATE Users SET password=' + QUOTENAME(@new, '''')
    + ' WHERE username=' + QUOTENAME(@loginname, '''')
    + ' AND password=' + QUOTENAME(@old, '''')

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

Als een aanvaller 154 tekens doorgeeft aan een buffer van 128 tekens, kunnen ze een nieuw wachtwoord sa instellen zonder het oude wachtwoord te kennen.

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

Daarom moet u een grote buffer gebruiken voor een opdrachtvariabele of de dynamische Transact-SQL in de EXECUTE instructie rechtstreeks uitvoeren.

Afkapping wanneer QUOTENAME(@variable, ''') en REPLACE() worden gebruikt

Tekenreeksen die worden geretourneerd door QUOTENAME() en REPLACE() worden zonder melding afgekapt als ze de toegewezen ruimte overschrijden. De opgeslagen procedure die in het volgende voorbeeld wordt gemaakt, laat zien wat er kan gebeuren.

In dit voorbeeld worden de gegevens die zijn opgeslagen in tijdelijke variabelen afgekapt, omdat de buffergrootte van @loginen @oldpassword@newpassword slechts 128 tekens zijn, maar QUOTENAME() maximaal 258 tekens kunnen retourneren. Als @new 128 tekens bevat, kan @newpassword123... n zijn, waarbij n het 127e teken is. Omdat de tekenreeks die door QUOTENAME() wordt geretourneerd wordt afgekapt, kan deze er als de volgende verklaring uitzien:

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

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);

SET @login = QUOTENAME(@loginname, '''');
SET @oldpassword = QUOTENAME(@old, '''');
SET @newpassword = QUOTENAME(@new, '''');

-- Construct the dynamic Transact-SQL.
SET @command = 'UPDATE Users set password = ' + @newpassword
    + ' WHERE username = ' + @login
    + ' AND password = ' + @oldpassword;

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

Daarom stelt de volgende instructie de wachtwoorden van alle gebruikers in op de waarde die is doorgegeven in de vorige code.

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

U kunt tekst afkappen door de toegewezen bufferruimte te overschrijden bij het gebruik van REPLACE(). De opgeslagen procedure die in het volgende voorbeeld wordt gemaakt, laat zien wat er kan gebeuren.

In dit voorbeeld worden gegevens afgekapt omdat de buffers die zijn toegewezen voor @loginen @newpassword@oldpassword slechts 128 tekens bevatten, maar QUOTENAME() maximaal 258 tekens kunnen retourneren. Als @new 128 tekens bevat, kan @newpassword'123...n' zijn, waarbij n het 127e teken is. Omdat de tekenreeks die door QUOTENAME() wordt geretourneerd, wordt afgekapt, kan deze er als volgt uitzien:

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

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);

SET @login = REPLACE(@loginname, '''', '''''');
SET @oldpassword = REPLACE(@old, '''', '''''');
SET @newpassword = REPLACE(@new, '''', '''''');

-- Construct the dynamic Transact-SQL.
SET @command = 'UPDATE Users SET password = '''
    + @newpassword + ''' WHERE username = '''
    + @login + ''' AND password = ''' + @oldpassword + '''';

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

Net als bij QUOTENAME(), kan afkorten van tekenreeksen door REPLACE() worden vermeden door tijdelijke variabelen te declareren die voldoende groot zijn voor alle gevallen. Indien mogelijk moet u QUOTENAME() of REPLACE() rechtstreeks aanroepen binnen de dynamische Transact-SQL. Anders kunt u de vereiste buffergrootte als volgt berekenen. Voor @outbuffer = QUOTENAME(@input), de grootte van @outbuffer moet zijn 2 * (len(@input) + 1). Wanneer u REPLACE() en dubbele aanhalingstekens gebruikt, zoals in het vorige voorbeeld, is een buffer van 2 * len(@input) voldoende.

In de volgende berekening worden alle gevallen behandeld:

WHILE LEN(@find_string) > 0, required buffer size =
    ROUND(LEN(@input) / LEN(@find_string), 0)
        * LEN(@new_string) + (LEN(@input) % LEN(@find_string))

Afkapping wanneer QUOTENAME(@variable; ']') wordt gebruikt

Afkapping kan optreden wanneer de naam van een database engine beveiligbaar wordt doorgegeven aan instructies die gebruikmaken van de vorm QUOTENAME(@variable, ']'). In het volgende voorbeeld wordt dit scenario gedemonstreerd.

In dit voorbeeld @objectname moet 2 * 258 + 1 tekens zijn toegestaan.

CREATE PROCEDURE sp_MyProc
    @schemaname SYSNAME,
    @tablename SYSNAME
AS
-- Declare a variable as sysname. The variable will be 128 characters.
DECLARE @objectname SYSNAME;

SET @objectname = QUOTENAME(@schemaname) + '.' + QUOTENAME(@tablename);
    -- Do some operations.
GO

Wanneer u waarden van het type sysname samenvoegt, moet u tijdelijke variabelen gebruiken die groot genoeg zijn om het maximum van 128 tekens per waarde te bevatten. Roep indien mogelijk QUOTENAME() rechtstreeks in de dynamische Transact-SQL aan. Anders kunt u de vereiste buffergrootte berekenen, zoals uitgelegd in de vorige sectie.