Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Vonatkozik a következőkre:SQL Server
Azure SQL Database
Azure SQL Managed Instance
Azure Synapse Analytics
Analitikai Platform System (PDW)
SQL adatbázis a Microsoft Fabric-ben
Az SQL-injektálás olyan támadás, amely során rosszindulatú kódot szúr be a rendszer olyan sztringekbe, amelyeket később az SQL Server Adatbázismotor egy példányának adnak át elemzés és végrehajtás céljából. Az SQL-utasításokat összeállító eljárásokat felül kell vizsgálni az injektálási biztonsági rések miatt, mivel az adatbázismotor végrehajtja az összes kapott szintaktikailag érvényes lekérdezést. Még a paraméteres adatokat is manipulálhatja egy képzett és határozott támadó.
Az SQL-injektálás működése
Az SQL-injektálás elsődleges formája a kód közvetlen beszúrása a felhasználó által bevitt változókba, amelyeket SQL-parancsokkal összefűznek és végrehajtanak. A kevésbé közvetlen támadás rosszindulatú kódot szúr be olyan sztringekbe, amelyek egy táblában való tárolásra vagy metaadatként szolgálnak. Amikor a tárolt sztringek ezután dinamikus SQL-parancsba vannak összefűzve, a rendszer végrehajtja a rosszindulatú kódot.
Az injektálási folyamat úgy működik, hogy idő előtt megszakítja a karakterláncot, és hozzáfűz egy új parancsot. Mivel a beszúrt parancshoz a végrehajtás előtt további sztringek is hozzáfűzhetők, a támadó egy megjegyzésjellel -- befejezi az injektált sztringet. A rendszer a végrehajtáskor figyelmen kívül hagyja a következő szöveget.
Az alábbi szkript egy egyszerű SQL-injektálást mutat be. A szkript úgy hoz létre SQL-lekérdezést, hogy összefűz egy kemény kódú sztringet a felhasználó által megadott sztringgel:
var ShipCity;
ShipCity = Request.form ("ShipCity");
var sql = "select * from OrdersTable where ShipCity = '" + ShipCity + "'";
A rendszer kéri a felhasználót, hogy adja meg a város nevét. Ha beírnak Redmond, a szkript által összeállított lekérdezés az alábbi példához hasonlóan néz ki:
SELECT * FROM OrdersTable WHERE ShipCity = 'Redmond';
Tegyük fel azonban, hogy a felhasználó a következő szöveget írja be:
Redmond';drop table OrdersTable--
Ebben az esetben a szkript a következő lekérdezést gyűjti össze:
SELECT * FROM OrdersTable WHERE ShipCity = 'Redmond';drop table OrdersTable--'
A pontosvessző (;) az egyik lekérdezés végét és a másik kezdetét jelöli. A kettős kötőjel (--) azt jelzi, hogy az aktuális sor többi része megjegyzés, ezért figyelmen kívül kell hagyni. Ha a módosított kód szintaktikailag helyes, azt a kiszolgáló hajtja végre. Amikor az adatbázismotor feldolgozza ezt az utasítást, először kiválasztja az összes rekordot, ahol OrdersTableShipCity van Redmond. Ezután OrdersTable-t eldobja az adatbázismotor.
Mindaddig, amíg az injektált SQL-kód szintaktikailag helyes, a módosítás nem észlelhető programozott módon. Ezért ellenőriznie kell az összes felhasználói bemenetet, és gondosan át kell tekintenie a létrehozott SQL-parancsokat végrehajtó kódot a használt kiszolgálón. Az ajánlott kódolási eljárásokat a cikk következő szakaszai ismertetik.
Az összes bemenet ellenőrzése
Mindig ellenőrizze a felhasználói bemenetet tesztelési típus, hossz, formátum és tartomány alapján. Ha óvintézkedéseket hajt végre a rosszindulatú bemenetek ellen, vegye figyelembe az alkalmazás architektúráját és üzembehelyezési forgatókönyveit. Ne feledje, hogy a biztonságos környezetben való futtatásra tervezett programok nem biztonságos környezetbe másolhatók. Az alábbi javaslatokat ajánlott eljárásnak kell tekinteni:
Ne tegyen feltételezéseket az alkalmazás által fogadott adatok méretéről, típusáról vagy tartalmáról. Például a következő értékelést kell végeznie:
Hogyan viselkedik az alkalmazás, ha egy hibás vagy rosszindulatú felhasználó egy 2 GB-os videofájlt ad meg, amelyben az alkalmazás irányítószámot vár?
Hogyan viselkedik az alkalmazás, ha egy
DROP TABLEutasítás szövegmezőbe van ágyazva?
Tesztelje a bemenet méretét és adattípusát, és kényszerítse ki a megfelelő korlátokat. Ez segíthet megelőzni a puffer szándékos túllépését.
Tesztelje a sztringváltozók tartalmát, és csak a várt értékeket fogadja el. Elutasíthatja a bináris adatokat, a feloldósorozatokat és a megjegyzés karaktereket tartalmazó bejegyzéseket. Ez segíthet megelőzni a szkriptinjektálást, és védelmet nyújthat bizonyos puffertúllépési kihasználtságokkal szemben.
Ha XML-dokumentumokkal dolgozik, a beíráskor ellenőrizze az összes adatot a sémájában.
Soha ne hozzon létre Transact-SQL utasításokat közvetlenül a felhasználói bemenetből.
Tárolt eljárások használata a felhasználói bemenet ellenőrzéséhez.
Többszintű környezetekben minden adatot ellenőrizni kell a megbízható zónába való belépés előtt. Az érvényesítési folyamatot nem átadó adatokat el kell utasítani, és a hibát vissza kell adni az előző szintre.
Több érvényesítési réteg implementálása. Az alkalmilag rosszindulatú felhasználókkal szembeni óvintézkedések hatástalanok lehetnek az elszánt támadókkal szemben. Jobb gyakorlat a felhasználói felületen és az azt követő pontokon lévő bemenetek ellenőrzése, ahol átlépi a megbízhatósági határt.
Az ügyféloldali alkalmazások adatérvényesítése például megakadályozhatja az egyszerű szkriptinjektálást. Ha azonban a következő szint feltételezi, hogy a bemenete már érvényesítve van, minden rosszindulatú felhasználó, aki megkerülheti az ügyfelet, korlátlan hozzáféréssel rendelkezhet a rendszerhez.
Soha ne fűzz össze olyan felhasználói bemenetet, amely nincs érvényesítve. A szkriptinjektálás elsődleges belépési pontja a sztringösszefűzés.
Ne fogadja el a következő sztringeket olyan mezőkben, amelyekből fájlnevek hozhatók létre:
AUX,CLOCK$,COM1keresztülCOM8,CON,CONFIG$,LPT1keresztülLPT8,NULésPRN.
Ha lehetséges, utasítsa el a következő karaktereket tartalmazó bemenetet.
| Bemeneti karakter | Jelentés a Transact-SQL |
|---|---|
; |
Lekérdezéselválasztó. |
' |
Karakteradat-sztringelválasztó. |
-- |
Egysoros megjegyzés elválasztó. A sor végéig követett -- szöveget a kiszolgáló nem értékeli ki. |
/*** ... ***/ |
Megjegyzéshatárolók.
/* A kiszolgáló nem értékeli ki a közötte lévő szöveget */. |
xp_ |
A katalógus által bővített tárolt eljárások nevének elején, például a xp_cmdshell esetében használt. |
Típusbiztos SQL-paraméterek használata
Az Parameters adatbázismotorban található gyűjtemény típusellenőrzést és hossz-ellenőrzést biztosít. Ha a gyűjteményt használja, a bemenetet a Parameters rendszer konstans értékként kezeli a végrehajtható kód helyett. A gyűjtemény használatának másik előnye, Parameters hogy típus- és hosszellenőrzéseket kényszeríthet ki. A tartományon kívüli értékek kivételt váltanak ki. A következő kódrészlet a Parameters gyűjtemény használatát mutatja be:
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;
Ebben a példában a paramétert a @au_id rendszer konstans értékként kezeli a végrehajtható kód helyett. Ez az érték a típus és a hossz szerint van ellenőrizve. Ha az érték @au_id nem felel meg a megadott típus- és hosszkorlátozásoknak, a rendszer kivételt vet ki.
Paraméteres bemenet használata tárolt eljárásokkal
A tárolt eljárások érzékenyek lehetnek az SQL-injektálásra, ha nem szűrt bemenetet használnak. A következő kód például sebezhető:
SqlDataAdapter myCommand =
new SqlDataAdapter("LoginStoredProcedure '" + Login.Text + "'", conn);
Tárolt eljárások használata esetén paramétereket kell használnia bemenetként.
A Paraméterek gyűjtemény használata dinamikus SQL-lel
Ha nem tudja használni a tárolt eljárásokat, akkor is használhat paramétereket, ahogy az a következő kód példájában is látható.
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;
Bemenet szűrése
A bemeneti adatok szűrése az SQL-injektálás elleni védelemben is hasznos lehet a feloldó karakterek eltávolításával. A problémákat okozó karakterek nagy száma miatt azonban a szűrés nem megbízható védelem. Az alábbi példa a karaktersztring-elválasztót keresi.
private string SafeSqlLiteral(string inputSQL)
{
return inputSQL.Replace("'", "''");
}
LIKE záradékok
Ha záradékot LIKE használ, a helyettesítő karaktereket továbbra is meg kell szökni:
s = s.Replace("[", "[[]");
s = s.Replace("%", "[%]");
s = s.Replace("_", "[_]");
Az SQL-injektálás kódjának áttekintése
Tekintse át az összes hívást kezdeményező EXECUTEkódot, EXECvagy sp_executesql. Az alábbihoz hasonló lekérdezésekkel azonosíthatja az utasításokat tartalmazó eljárásokat. Ez a lekérdezés 1, 2, 3 vagy 4 szóközt keres a EXECUTE vagy EXEC szavak után.
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%';
Paraméterek becsomagolása a QUOTENAME() és a REPLACE() függvényekkel
Minden kiválasztott tárolt eljárásban ellenőrizze, hogy a dinamikus Transact-SQL használt összes változó megfelelően van-e kezelve. A tárolt eljárás bemeneti paramétereiből származó vagy egy táblából beolvasott adatokat QUOTENAME() vagy REPLACE() tagekbe kell csomagolni. Ne feledje, hogy a @variable értéke, amelyet QUOTENAME()-nek átadunk, sysname típusú, és legfeljebb 128 karakter hosszúságú lehet.
| @variable | Ajánlott csomagoló |
|---|---|
| Biztonságos személy neve | QUOTENAME(@variable) |
| Karaktersor <= 128 karakter | QUOTENAME(@variable, '''') |
| 128 karakterből álló szöveg > | REPLACE(@variable,'''', '''''') |
Ha ezt a technikát használja, egy SET utasítás az alábbiak szerint módosítható:
-- 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'''';
Adatcsonkítás által engedélyezett bejuttatás
Minden dinamikus Transact-SQL, amelyet egy változóhoz rendelnek, csonkolódik, ha nagyobb, mint az adott változóhoz lefoglalt puffer. Egy támadó, aki váratlanul hosszú karakterláncok tárolt eljárásba való továbbításával kényszerítheti az utasítás csonkolását, módosíthatja az eredményt. Az alábbi példa tárolt eljárás például sebezhető a csonkolás által engedélyezett injektálással szemben.
Ebben a példában egy @command legfeljebb 200 karakter hosszúságú pufferrel rendelkezünk. Összesen 154 karakterre van szükségünk a következő jelszó 'sa'beállításához: 26 az UPDATE utasításhoz, 16 a WHERE záradékhoz, 4 a 'sa', és 2 az idézőjelekhez QUOTENAME(@loginname): 200 - 26 - 16 - 4 - 2 = 154.
@new Mivel azonban a rendszer sysname-ként van deklarálva, ez a változó csak 128 karaktert tartalmazhat. Ezt úgy tudjuk leküzdeni, hogy egy-egy idézőjelet adunk át a következőben @new: .
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
Ha egy támadó 154 karaktert ad át egy 128 karakteres pufferbe, a régi jelszó ismerete nélkül állíthat be új jelszót sa .
EXEC sp_MySetPassword 'sa',
'dummy',
'123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012'''''''''''''''''''''''''''''''''''''''''''''''''''
Ezért egy nagy puffert érdemes használni a parancsváltozóhoz, vagy közvetlenül hajtsa végre a dinamikus Transact-SQL-t az EXECUTE utasításon belül.
Csonkolás a QUOTENAME(@variable, ''') és REPLACE() használatakor
A QUOTENAME() és REPLACE() által visszaadott sztringek csendben csonkulnak, ha túllépik a számukra lefoglalt memóriaterületet. Az alábbi példában létrehozott tárolt eljárás bemutatja, hogy mi történhet.
Ebben a példában az ideiglenes változókban tárolt adatok csonkoltak, mivel a puffer mérete @login, @oldpasswordés @newpassword csak 128 karakter, de QUOTENAME() legfeljebb 258 karaktert adhat vissza. Ha @new 128 karaktert tartalmaz, akkor @newpassword lehet 123... n, ahol a n a 127. karakter. Mivel a visszaadott sztring QUOTENAME() csonkolt, a következő utasításhoz hasonlóan állítható be:
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
Ezért az alábbi utasítás az összes felhasználó jelszavát az előző kódban átadott értékre állítja.
EXEC sp_MyProc '--', 'dummy', '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678'
A karakterlánc csonkolását úgy kényszerítheti, hogy túllépi a lefoglalt pufferterületet a REPLACE() használata során. Az alábbi példában létrehozott tárolt eljárás bemutatja, hogy mi történhet.
Ebben a példában a rendszer csonkolja az adatokat, mert a lefoglalt @login@oldpassword@newpassword pufferek csak 128 karaktert tartalmazhatnak, de QUOTENAME() legfeljebb 258 karaktert adhatnak vissza. Ha @new 128 karaktert tartalmaz, @newpassword akkor '123...n'n a 127. karakter lehet. Mivel a visszaadott sztring QUOTENAME() csonkolt, a következő utasításhoz hasonlóan állítható be:
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
Elkerülhetjük a karakterlánc QUOTENAME() csonkolását REPLACE(), ha olyan ideiglenes változókat deklarálunk, amelyek minden esetben elég nagyok. Amikor csak lehet, közvetlenül a dinamikus Transact-SQL-ben hívja meg a QUOTENAME() vagy REPLACE() elemet. Ellenkező esetben az alábbiak szerint számíthatja ki a szükséges pufferméretet.
@outbuffer = QUOTENAME(@input) esetében @outbuffer méretének 2 * (len(@input) + 1) kell lennie. A REPLACE() és a duplázott idézőjelek használata esetén, mint az előző példában, egy puffer 2 * len(@input) elegendő.
A következő számítás az összes esetet lefedi:
WHILE LEN(@find_string) > 0, required buffer size =
ROUND(LEN(@input) / LEN(@find_string), 0)
* LEN(@new_string) + (LEN(@input) % LEN(@find_string))
Csonkolás a QUOTENAME(@variable, ']') használata esetén
A csonkolás akkor fordulhat elő, ha egy adatbázismotor védhető objektumának nevét adják át azoknak az utasításoknak, amelyek a QUOTENAME(@variable, ']') formátumot használják. Az alábbi példa ezt a forgatókönyvet mutatja be.
Ebben a példában @objectname 2 * 258 + 1 karaktert kell engedélyeznie.
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
A sysname típusú értékek összefűzésekor olyan ideiglenes változókat kell használnia, amely elég nagy ahhoz, hogy az értékenként legfeljebb 128 karaktert tartalmazzon. Ha lehetséges, hívjon QUOTENAME() közvetlenül a dinamikus Transact-SQL-en belül. Ellenkező esetben az előző szakaszban ismertetett módon kiszámíthatja a szükséges pufferméretet.