SQL-injekció

A Wikipédiából, a szabad enciklopédiából

Az SQL-injekció egy SQL-adatbázisokkal összefüggő biztonsági hiba kihasználása. A biztonsági hiba egy adatbázis-hozzáférő program programozási hibájából ered. E hibán keresztül egy támadó adatbázisparancsokat illeszthet be és egyéni esettől függően további adatokat olvashat ki az adatbázisból, jogosulatlanul módosíthatja vagy törölheti őket, vagy akár az egész adatbázisszerver irányítását átveheti.

Szükségletek[szerkesztés]

Egy sikeres SQL-injekcióhoz az alábbiak szükségesek:

  • egy támadható SQL-adatbázis, például MySQL, Microsoft SQL Server, Db2
  • egy alkalmazás, melybe bevihetők adatok, például bejelentkezési maszk, termékkeresés vagy webes kapcsolati ív
  • hogy az alkalmazás ezen adatokat az adatbázisba továbbítsa
  • egy hiba az alkalmazásban adattovábbításkor

A hiba abban áll, hogy az alkalmazás a bevitt adatokat nem tiszta adatként továbbítja, hanem ezekből adatbázis-lekérdezést hoz létre, így egy támadó megkísérelheti adatbázis-lekérdezések részeinek célzott irányítását.

Az SQL adatbázis-leíró nyelvben egyes jelek saját jelentéssel rendelkeznek, például:

A dokumentumorientált NoSQL adatbázisokat is érintheti e probléma, például a kapcsos és szögletes zárójelek JSON-objektumot, illetve tömböt jelentenek. E karakterek célzott használata lehetővé teszi a támadás véghezvitelét.

Példa[szerkesztés]

Egy példa egy lekérdezésre, mely egy szerkesztőségi rendszerben adott kategóriából keres:

SELECT id, cím, szöveg FROM cikkek WHERE kategória = 'keresett kategória';

A lekérdezés az alábbi részekből áll:

  • A nagybetűs szavak kulcsszavak az SQL-ben, meghatározott jelentéssel.
  • A kisbetűs szavak táblázat-, sor- vagy oszlopnevek az adatbázisban.
  • Az egyszerű idézőjelek közti szöveg egyszerű szöveg.

A „keresett kategória” itt csak helykitöltő, melynek helyén a keresett kategóriának kell szerepelnie. Míg az egyik említett jelet se tartalmazza, közvetlenül behelyettesíthető a példalekérdezésbe.

Ha a keresett kategória azonban például Rock ’n’ Roll, így aposztrófot tartalmaz, a helykitöltő naiv cseréje az alábbi lekérdezéshez vezet:

SELECT id, cím, szöveg FROM cikkek WHERE kategória = 'Rock 'n' Roll';

Itt jelenik meg az aposztróf kettős jelentése: egyrészt karakterlánc-határoló, másrészt szövegbeli szimbólum. E kettős jelentést az emberek láthatják, de az adatbázis nem tudja megfelelően értelmezni, mivel a lekérdezést kategória = 'Rock '-ként értelmezi, melyet az „n” betű követ, majd aposztrófok közt az ezt követő' Roll' szöveg. Az n szót az adatbázis nem tudja megfelelően kezelni, így a lekérdezésre hibát ír ki.

SQL-injekció akkor áll fenn, ha a támadó a bevitt adatok megfelelő választásával a lekérdezést úgy változtatja meg, hogy bár értelmes a szerkezete, de más a jelentése. Így a látszólag értelmetlen „abcd' OR id < 100 OR kategória = 'b123” az alábbi lekérdezéshez vezet:

SELECT id, cím, szöveg FROM cikkek WHERE kategória = 'abcd' OR id < 100 OR kategória = 'b123';

Ez az adatbázis szempontjából teljesen megfelelő, 3 külön feltételt tartalmaz, melyek egyikének igaznak kell lennie. Az első és a harmadik feltétel a példában azt érik el, hogy az SQL-lekérdezés továbbra is értelmes, és ilyen nevű kategóriák léte az adatbázisban valószínűtlen. Így az egyetlen releváns feltétel az id < 100. Az eredeti, csak adott kategóriájú cikkeket megadó lekérdezésből megfelelő kifejezésválasztással más szerkezetű lekérdezés jött létre, mely a cikkeket azonosítóik alapján keresi, kategóriától függetlenül.

E megváltoztatott SQL-lekérdezéssel egy támadó megláthat neki nem megfelelő adatokat az adatbázisból. Más célzott keresőkifejezésekkel adatok módosíthatók vagy törölhetők esettől függően.

A hiba az SQL-injekcióban a keresési kifejezés egy az egyben történő átvétele az aposztróf és más jelek különleges jelentésének átlátása nélkül.

Folyamat[szerkesztés]

Adatmódosítás[szerkesztés]

Adott egy szerveren egy find.cgi című szkript cikkek megjelenítésére. A szkript paramétere „ID”, mely később a lekérdezés része. Az alábbi táblázat illusztrálja ezt:

  Megfelelő működés
Cím http://webserver/cgi-bin/find.cgi?ID=42
Kapott SQL SELECT author, subject, text FROM cikkek WHERE ID=42;
  SQL-injekció
Cím http://webserver/cgi-bin/find.cgi?ID=42;UPDATE USER SET TYPE="admin" WHERE ID=23
Kapott SQL SELECT author, subject, text FROM cikkek WHERE ID=42;UPDATE USER SET TYPE="admin" WHERE ID=23;

A programnak így egy módosított, a felhasználó-adatbázist módosító SQL-parancs küldése történik.

Adatbázisszerver-módosítás[szerkesztés]

Egy webszerveren weblapkeresésre használatos search.aspx fájl van. A szkript paramétere a keyword, melynek értéke SQL-lekérdezés része. Az alábbi táblázat illusztrálja ezt:

  Megfelelő működés
Cím http://webserver/search.aspx?keyword=sql
Kapott SQL SELECT url, title FROM myindex WHERE keyword LIKE '%sql%'
  SQL-injekció
Cím http://webserver/search.aspx?keyword=sql';GO EXEC cmdshell('shutdown /s') --
Kapott SQL SELECT url, title FROM myindex WHERE keyword LIKE '%sql';GO EXEC cmdshell('shutdown /s') --%'

Itt a tulajdonképpeni lekérdezés mellé további parancs került. A két kötőjel (--) az a lekérdezés maradékát, az aposztrófot teszi megjegyzésbe, így az figyelmen kívül marad. A létrehozott lekérdezés lehetővé teszi egy Windows-folyamat végrehajtását, jelen esetben a szerver kikapcsolását (amíg a folyamat adminisztrátori jogosultsággal rendelkezik). Azonban adatok és más információk is kiszivároghatnak így (például a Microsoft SQL Server esetén).

Adatok kikémlelése[szerkesztés]

Egyes SQL-megvalósításokban elérhető a UNION parancs, lehetővé téve több, közös eredményhalmazt adó SELECT egyidejű végrehajtását. Megfelelően megadott UNION paranccsal bármilyen táblázat és rendszerváltozó kikémlelhető.

  Megfelelő működés
Cím http://webserver/cgi-bin/find.cgi?ID=42
Kapott SQL SELECT author, subject, text FROM cikkek WHERE ID=42;
  SQL-injekció
Cím http://webserver/cgi-bin/find.cgi?ID=42 UNION SELECT login, password, 'x' FROM user
Kapott SQL SELECT author, subject, text FROM cikkek WHERE ID=42 UNION SELECT login, password, 'x' FROM user;

Az x a UNION SELECT esetén szükséges, mivel minden UNION paranccsal összekapcsolt SELECT parancsnak azonos oszlopszámmal kell rendelkeznie. A támadó az oszlopszámot kitalálhatja, ha e mögé az ID=42 order by x-- szöveget teszi. Ha például az oldal esetén megfelelően jelenik meg, de esetén hiba vagy más lap jelenik meg, a táblázat 8 oszlopos.

Hibásan beállított adatbázisszerver és hozzáféréssel rendelkező, éppen az adatbázishoz kapcsolódó felhasználó esetén, akiről szóló adatokkal szemben történik az SQL-injekció, „Rendszeradatbázis.RendszertáblázatTáblázatlistával” vagy hasonló egyszerű SQL-szintaxissal hozzáférhetők a rendszertáblázatok, és kiolvashatók az adatbázisok táblázatai, lehetővé téve fontos információk megszerzését további támadásokhoz és még nagyobb mértékű rendszerhozzáféréshez.

MySQL-adatbázisrendszerek esetén a rendszer-információk az information_schema adatbázisban szerepelnek.[1] Az alábbi példa bemutatja, hogy tudható meg egy 3 kimeneti oszlopú lekérdezésnél a hozzáférhető adatbázisok szerkezete.

  Megfelelő működés
Cím http://webserver/cgi-bin/find.cgi?ID=42
Kapott SQL SELECT author, subject, text FROM cikkek WHERE ID=42;
  SQL-injekció
Cím http://webserver/cgi-bin/find.cgi?ID=42 UNION SELECT 'Adatbázis', 'Táblázat', 'Oszlop'
UNION SELECT table_schema, table_name, column_name FROM information_schema.columns
WHERE NOT table_schema='information_schema';#%20
Kapott SQL SELECT author, subject, text FROM cikkek WHERE ID=42 UNION SELECT 'Adatbázis', 'Táblázat', 'Oszlop'
UNION SELECT table_schema, table_name, column_name FROM information_schema.columns
WHERE NOT table_schema='information_schema';# 
;

Egyes adatbázisrendszerek lehetővé teszik ezenkívül fájlok lekérdezéssel való kiadását. Ezzel és a fenti technikákkal, valamint ha az elérési út ismert, tetszőleges, az adatbázis-folyamat által hozzáférhető fájl kiolvasható.

Egy hasonló cím egy MongoDB NoSQL adatbázisban így nézhet ki:

  Megfelelő működés
Cím http://webserver/cgi-bin/find.cgi?ID=42
Kapott kód db.users.find({userID: 42</i });
  Kódinjekció
Cím http://webserver/cgi-bin/find.cgi?ID['$ne']=
Kapott kód db.users.find({userID: {'$ne': undefined}});

A $ne jelentése: „nem egyenlő”, a lekérdezés minden, nem üres felhasználónevű felhasználót kiad, vagyis minden felhasználót.

Tetszőleges kód futtatása[szerkesztés]

Egy kevésbé ismert változat egyidejűleg a legveszélyesebb. Ha az adatbázisszerver SELECT … INTO OUTFILE, illetve SELECT … INTO DUMPFILE parancsokat támogat, ezek felhasználhatók az adatbázisszerver fájlrendszerén lévő fájlok létrehozására. Elméletileg így lehetséges tetszőleges kód rendszeren való futtatása, ha az operációs rendszer vagy az adatbázisszervver könyvtárszerkezete írható (ha például gyökérfiókként fut).

Időzített támadások[szerkesztés]

Teljesítménymérő funkciókat támogató adatbázisszerver esetén ezek felhasználhatók adatbázis-szerkezetről szóló információk megismeréséhez. Az adatbázis-tartalmak másolása sokkal tovább tart.

Az alábbi példa MySQL-adatbázisszerveren több másodpercig tart, ha a jelenlegi felhasználó root:

SELECT IF(USER() LIKE 'root@%', BENCHMARK(100000,SHA1('test')), 'false');

Ehhezhasonlóak a Boole-támadások, melyek Boole-lekérdezésekkel kísérelnek meg adatbázis-tartalmakat kivonni.

Adminisztrátori jogok megszerzése[szerkesztés]

Egyes adatbázisszervereknél, például a Microsoft SQL Servernél a 2000-es verzióig, a tárolt eljárások, például az Xp_cmdshell automatikusan meghívhatók voltak, és többek között arra is használhatók voltak, hogy az SQL-szerverprogram jogaival futtasson parancsokat. Az újabb verziókban e funkció megszűnt. E lehetőség például operációsrendszer-héj futtatására is alkalmas volt a megtámadott számítógépen.

Sérülékenységek az adatbázisszerverben[szerkesztés]

Néha a szoftverben vannak sérülékenységek. Így például a mysql_real_escape_string() PHP-függvény egy MySQL-szerverben lehetővé tesz SQL-injekció-alapú támadásokat Unicode-jelekkel megfelelően maszkolt bemenetek esetén is. Ez az 5.0.22 verzióban lett javítva (2006. május 24.).

Vak SQL-injekció[szerkesztés]

„Vak” az SQL-injekció, ha egy szerver nem ad vissza leíró hibaüzenetet, melyből kiderül, hogy a lekérdezés sikeres-e. Apró eltérések, például könnyen megkülönböztethető hibaüzenetek vagy jelentősen eltérő válaszidők alapján megtudható, hogy egy lekérdezés sikeres volt-e, vagy hibás volt.

Megakadályozás[szerkesztés]

Az SQL-injekciók elkerüléséért a felhasználó által megadott adatok nem építendők további intézkedés nélkül SQL-parancsba. A bevitt adatok ellenőrzendők a várt értékek tulajdonságaira. Például a német irányítószámok csak számjegyekből állnak.

Előkészített parancsok[szerkesztés]

A legbiztosabb mód az adatok távoltartása az SQL-értelmezőtől.[2] Ekkor nem kell a bevitelt lerövidíteni. Ez az előkészített parancsok paramétereivel működik. Ekkor az adatok egy már lefordított parancs paraméterei. Így nem történik adatértelmezés, megakadályozva az SQL-injekciót. A tárolt eljárások ezzel szemben nem biztosítanak általános védelmet SQL-injekció ellen, különösen ismeretlen SQL-kód esetén.

Azonban az adatbázisszerver-oldalon is történhetnek biztonsági intézkedések. Például a felhasználó, akit egy adatbázisszervernél egy webalkalmazás hitelesít, csak a neki szükséges jogokkal rendelkezzék. Ez hatástalanítja a támadások legalább egy részét.

Ha egy szerverüzemeltető nem irányítja az alkalmazásait, webalkalmazás-tűzfalakkal (WAF) legalább részben megakadályozhatja a gyenge pontok kihasználását. Alkalmazásirányítástól függetlenül egy szerverüzemeltető célzott WAF-használattal növelheti a biztonságot, mivel sok WAF profilaxist is kínál az akadályozás mellett.

Nem nehéz összetett programok SQL-injekciókat lehetetlenné tevő átalakítása. A legtöbb esetben a fő probléma az e támadásokról való hiányos tudás. Itt néhány példa következik, mely e támadásokat megakadályozza.

Az SQL-lekérdezés tetszőleges bevitt adatokkal való bővítésének egyszerű, de hibás módja nagyjából ílyen a legtöbb nyelvben:

#veszélyes kód, SQL-injekciót tesz lehetővé
sql = "SELECT oszlop1 FROM táblázat WHERE oszlop2 = '" + bemenet + "';"
query = db.query(sql)

Az idézőjelek a programozási nyelv részei, az aposztrófok az SQL-éi. Ha a bevitt adatokban is van aposztróf, lehetséges SQL-injekció. Ez ellen elválasztandók az adatoktól a lekérdezések. A következő kódban a :bemenet a bevitt adatok helykitöltője. Ezeket a lekérdezéskor a db.query az adatbázisba helyezi. Az SQL és a bevitt adatok elválasztása megakadályozza az SQL-injekciót.

sql = "SELECT oszlop1 FROM táblázat WHERE oszlop2 = :bemenet;"
query = db.query(sql, bemenet=bemenet)

Python[szerkesztés]

A Pythonban több lehetőség van adatbázissal való kommunikációra. Ezek egyike az SQLAlchemy. Itt az SQL-injekció ellen kerülendők a nyers SQL-parancsok, ha dokumentum- vagy URL-kérés is használható. Az alábbi példa is ezt szemlélteti:

db.engine.execute("SELECT * FROM table")

Ehelyett az SQLAlchemy belső funkciói használandók, például az alábbiak:[3]

session.query(Order).get(order_id)
session.query(Order).filter(Order.status == 'active')

Visual Basic (ADOdb)[szerkesztés]

A Visual Basicben egyszerű Command-objektumok vannak, melyekkel elkerülhetők e problémák.

Ehelyett:

cn.Execute "SELECT oszlop1 FROM táblázat WHERE oszlop2 = '" & spalte2Wert & "'"

ez használandó:

Dim cmd As ADODB.Command, rs as ADODB.Recordset
With cmd
  Set .ActiveConnection = cn
  Set .CommandType = adCmdText
  .CommandText = "SELECT oszlop1 FROM táblázat WHERE oszlop2 = ?"
  .Parameters.Append .CreateParameter("paramSp2", adVarChar, adParamInput, 25, oszlop2ért) '25 a maximális hossz
  Set rs = .Execute
End With

Delphi[szerkesztés]

A BDE óta a lekérdezésekben használhatók paraméterek. Az eltérő könyvtárakban a szintaxis nem mindig azonos, de hasonló.

Ehelyett:

function TDatabase.GetData(ParameterID: Integer): Integer;
var
  Qry: TQuery;
begin
  Result := 0;
  Qry := TQuery.Create;
  try
    Qry.SQL.Text := 'SELECT F_DATA FROM T_BLUBB WHERE ID = ' + IntToStr(ParameterID);
    Qry.Open;
    Result := Qry.FieldByName('F_DATA').AsInteger;
  finally
    Qry.Free;
  end;
end;

ez használandó paraméterekkel:

function TDatabase.GetData(ParameterID: Integer): Integer;
var
  Qry: TQuery;
begin
  Result := 0;
  Qry := TQuery.Create;
  try
    Qry.SQL.Text := 'SELECT F_DATA FROM T_BLUBB WHERE ID = :PI';
    Qry.ParamByName('PI').AsInteger := ParameterID;
    Qry.Open;
    Result := Qry.FieldByName('F_DATA').AsInteger;
  finally
    Qry.Free;
  end;
end;

Microsoft .NET Framework – C# (ADO.NET)[szerkesztés]

A .NET keretrendszerben vannak egyszerű objektumok az ilyen problémák elkerülésére.

Ehekyett:

SqlCommand cmd = new SqlCommand("SELECT oszlop1 FROM táblázat WHERE oszlop2 = '" + oszlop2ért + "';");

ez használandó:

string oszlop2ért = "Érték";
SqlCommand cmd = new SqlCommand("SELECT oszlop1 FROM táblázat WHERE oszlop2 = @oszlop2ért;");
cmd.Parameters.AddWithValue("@spalte2Wert", spalte2Wert);

Java (JDBC)[szerkesztés]

Az SQL-injekciót megakadályozhatja meglévő függvény. A Java erre a PreparedStatement osztályt (JDBC) használja, s az ismeretlen eredetű adatokat külön paraméterekként továbbítja. Az adatok SQL-től való elválasztására a „?” helykitöltő szolgál.

Ehelyett:

Statement stmt = con.createStatement();
ResultSet rset = stmt.executeQuery("SELECT oszlop1 FROM táblázat WHERE oszlop2 = '" + oszlop2ért + "';");

ez használandó:

PreparedStatement pstmt = con.prepareStatement("SELECT oszlop1 FROM táblázat WHERE oszlop2 = ?");
pstmt.setString(1, oszlop2ért);
ResultSet rset = pstmt.executeQuery();

A PreparedStatement írásimunka-többlete ezenkívül teljesítménynyereséggel egyenlíthető ki, ha a program többször használja a PreparedStatement objektumot.

PHP[szerkesztés]

A PHP-ben a PHP Data Objects elérhető adatbázis-hozzáférésekhez.

Prepared Statement nélküli példa:

$dbh->exec("INSERT INTO REGISTRY (name, value) VALUES (".$dbh->quote($name,PDO::PARAM_STR).", ".$dbh->quote($value,PDO::PARAM_INT).")");

Példa Prepared Statementtel:

$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");
$stmt->bindParam(':name', $name);
$stmt->bindParam(':value', $value);

A PHP 5.3-ig létezett „magic_quotes_gpc” beállítás. Ennek bekapcsolása a külső felhasználói adatok automatikus maszkolását okozta. Egyes szkriptek hasonló függvényeket használnak, például addslashes()-t[4] vagy mysql_real_escape_string()-et[5]. Tehát minden megfelelő jel elé „varázsidézőjelek” révén[6] „\” jelet helyez a feloldófüggvény. Ez a bevitel „hamisítása”, és egyszerű " idézőjel helyett \" jelsorozat jön ki. A platformfüggetlenség végett azonban alkalmazásfejlesztéskor e beállítás kerülendő, és minden bemenet kézzel ellenőrzendő és alakítandó át, mivel nem lehet abból kiindulni, hogy minden rendszer azonos beállításokat használ vagy az lehetséges. Továbbá az addSlashes() nem használandó adatbázis-bemenetekhez, mivel kevésbé biztonságos a mysql_real_escape_string() függvénynél.[7]

A PHP 5.3 után a mysql_real_escape_string()-et felváltotta a MySQLi. A 7.0 verziótól a mysql_real_escape_string() nem elérhető, a függvény neve mysqli_real_escape_string() lett.

Alapvetően a biztonsághoz a legmegfelelőbbek itt is az előkészített parancsok.

Perl[szerkesztés]

Az adatbázis-független DBI adatbázismodullal, mely alapvetően a Perlben így használatos:

Ehelyett:

$arrayref = $databasehandle->selectall_arrayref("SELECT oszlop1 FROM táblázat WHERE oszlop2 = $oszlop2ért");

ez használandó:

$arrayref = $databasehandle->selectall_arrayref('SELECT oszlop1 FROM táblázat WHERE oszlop2 = ?',{},$oszlop2ért);

A Perl DBI modul ezenkívül támogatja a Java-példához hasonló „prepare”-szintaxist.

$statementhandle = $databasehandle->prepare("SELECT oszlop1 FROM táblázat WHERE oszlop2 = ?");
$returnvalue = $statementhandle->execute($oszlop2ért);

Ezenkívül az adatbázis-kezelő is biztonságosan maszkolhatja a bemeneti értékeket, így az adatbázis-megjelenítő ügyel az adatbázis jellemző különleges karaktereire, és ez nem igényel további ismereteket.

$arrayref = $databasehandle->selectall_arrayref("SELECT oszlop1 FROM táblázat WHERE oszlop2 =". $databasehandle->quote($spalte2Wert));

A teljes szkriptben a -T paraméterrel aktiválható úgynevezett „taint mode”-ban a Perl erős heurisztikus módszereket használ, hogy csak biztonságos hozzáféréseket engedélyez. A felhasználó által megadott paramétereket tartalmazó karakterláncokat az adatok explicit ellenőrzéséig „nem biztonságosnak” kezeli, és nem használhatók nem biztonságos parancsokban.

ColdFusion Markup Language[szerkesztés]

A ColdFusionben lehetőség van <cfqueryparam> címkére, mely a szükséges ellenőrzéseket elvégzi:[8]

   SELECT * FROM courses WHERE Course_ID = <cfqueryparam value = "#Course_ID#" CFSQLType = "CF_SQL_INTEGER">

MS-SQL[szerkesztés]

Paraméteres parancsokkal az adatbázis védhető SQL-injekcióktól:

SELECT COUNT(*) FROM Users WHERE UserName=? AND UserPasswordHash=?

Jegyzetek[szerkesztés]

  1. Die Datenbank INFORMATION_SCHEMA. MySQL 5.1 Referenzhandbuch, Kapitel 20
  2. SQL Injection Prevention Cheat Sheet. (Hozzáférés: 2019. október 24.)
  3. 10 Reasons to love SQLAlchemy. (Hozzáférés: 2016. december 20.)
  4. addslashes PHP Manual
  5. mysql_real_escape_string PHP Manual
  6. Magic Quotes Archiválva 2021. január 3-i dátummal a Wayback Machine-ben PHP Manual
  7. Chris Shiflett: addslashes() Versus mysql_real_escape_string() (angolul)
  8. Sablon:Cite webWebarchiv

Fordítás[szerkesztés]

Ez a szócikk részben vagy egészben a SQL-Injection című német Wikipédia-szócikk ezen változatának fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.

További információk[szerkesztés]