SQL-injekció
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
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:
- az aposztróf és egyes adatbázisokban az idézőjel karakterláncokat választ el
- a fordított törtvonal (backslash) a következő jel nem speciális jelkénti értelmezését jelenti
- a pontosvessző egy adatbázisparancsot választ el az azt követőtől
- két kötőjel a sor maradékát megjegyzéssé teszi.
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
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
Adatmódosítá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
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
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' |
Kapott SQL | SELECT author, subject, text FROM cikkek WHERE ID=42 UNION SELECT 'Adatbázis', 'Táblázat', 'Oszlop' |
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
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
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
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
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ó
„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
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
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
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)
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
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)
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)
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
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
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
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
Paraméteres parancsokkal az adatbázis védhető SQL-injekcióktól:
SELECT COUNT(*) FROM Users WHERE UserName=? AND UserPasswordHash=?
Jegyzetek
- ↑ Die Datenbank INFORMATION_SCHEMA. MySQL 5.1 Referenzhandbuch, Kapitel 20
- ↑ SQL Injection Prevention Cheat Sheet. (Hozzáférés: 2019. október 24.)
- ↑ 10 Reasons to love SQLAlchemy. (Hozzáférés: 2016. december 20.)
- ↑ addslashes PHP Manual
- ↑ mysql_real_escape_string PHP Manual
- ↑ Magic Quotes Archiválva 2021. január 3-i dátummal a Wayback Machine-ben PHP Manual
- ↑ Chris Shiflett: addslashes() Versus mysql_real_escape_string() (angolul)
- ↑ Sablon:Cite webWebarchiv
Fordítá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
- SQL-Injections – Analyse
- Abusing Poor Programming Techniques in Webserver Scripts via SQL Injection Archiválva 2012. április 16-i dátummal a Wayback Machine-ben (englisch)
- Sicherheit von Webanwendungen: Maßnahmenkatalog und Best Practices (PDF; 805 kB) Bundesamt für Sicherheit in der Informationstechnik (BSI)
- Web Security Threat Classification (PDF, 432 kB)
- Steve Friedl: SQL Injection Attacks by Example.
- Advanced SQL Injection in MySQL (PDF; 935 kB)
- SQLi: Videos zur Durchführung und Gegenmaßnahmen
Informatikai portál • összefoglaló, színes tartalomajánló lap