Jó lesz az, csak másra

Ez alkalommal egy fordulatos kis nyomozásra invitálom a kedves olvasót, ami kezdetben SQL injection vadászatnak indult, de hamar átcsapott a lét hiábavalóságának megtapasztalásába, a világunkat működtető szabályok megkérdőjelezésébe majd a lehetetlen újraértékelésébe.

Bár az est sztárvendége nem maga az SQL injection lesz, történetünk - mint sok hasonló történet - mégis egy egyszerű query-vel kezdődik.

$query = "SELECT * FROM users WHERE id={intval($id)}";
$user = $db->query($query)->fetch();

A projekt konvencióitól függően ez akár simán át is csusszanhatna egy code review-n, főleg ha az érintettek nem annyira járatosak a PHP-ban. Talán felmerülne a bátortalan kérdés, hogy az az intval hívás ott biztos, hogy működik-e, de a zöld tesztek mindenkit megnyugtatnak. Nincs itt semmi probléma.

Vagy mégis? Talán jobb lesz, ha utánajárunk...

$ php -a
php > $id = 'almafa';
php > var_dump("{intval($id)}");
string(16) "{intval(almafa)}"

Úgy tűnik a PHP-s változó behelyettesítés nem olyan okos, mint más nyelvek hasonló szerkezetei (pl. a Template literals JavaScript-ben, vagy a Python-os Literal String Interpolation).

Az intval nem igazán fut le és ezzel meg is érkeztünk az SQL injection lehetőségéhez. Például ha az $id változó értéke valami olyasmi lenne, hogy 1)} OR 1=1 --, akkor... de már nem is ez az érdekes, igaz? Engem legalábbis ezen a ponton már sokkal jobban foglalkoztatott az, hogy miért voltak zöldek a tesztek?

Az első gondolatom az volt, hogy talán a MySQL-nek is van intval függvénye és az futhat le, de erről hamar meggyőződtem, hogy nem így van.

$ mysql
> SELECT intval("1234");
ERROR 1305 (42000): FUNCTION mysql.intval does not exist

Biztos a kapcsos zárójelekhez lesz valami köze. Nem kevés keresgélés után végül találtam is egy ígéretes nyomot a MySQL dokumentációjában:

{identifier expr} is ODBC escape syntax and is accepted for ODBC compatibility.

A szimatot fogott kopó lelkesedésével vetettem bele magam az "ODBC escape syntax" utáni kutatásba, ahelyett, hogy inkább a dokumentációt olvastam volna egy kicsit tovább.

Az ODBC egy egységesített API, amivel függetleníthetjük a kódunkat attól, hogy konkrétan milyen adatbázis szervert használunk. Ez a kapcsos zárójelek közötti szerkezet is ezt hivatott elősegíteni azzal, hogy a konkrét driver az adatbázis szerver által elfogadott formára hoz bizonyos kifejezéseket.

Mi a PHP kódban viszont nem ODBC driver-t használunk, úgyhogy nem volt semmi, ami az előfeldolgozást elvégezte volna a MySQL szervernek. Így ő maradt az egyetlen gyanúsított, belőle kell kiszednünk, hogy hogyan és miért is futtat le ilyen query-ket.

$ mysql
> SELECT {intval("1234")};
+------+
| 1234 |
+------+
| 1234 |
+------+
1 row in set (0.00 sec)

> SELECT {intval("asdf")};
+------+
| asdf |
+------+
| asdf |
+------+
1 row in set (0.00 sec)

> SELECT {hmmm(1234)};
+------+
| 1234 |
+------+
| 1234 |
+------+
1 row in set (0.00 sec)

> SELECT {erdekes 1234};
+------+
| 1234 |
+------+
| 1234 |
+------+
1 row in set (0.00 sec)

Mint az látszik, a kapcsos zárójelek között működik az intval, ránézésre csinálja is a dolgát... kivéve ha valami szöveget adunk be neki... vagy nem is intval-nak hívjuk... esetleg még a zárójeleket is elhagyjuk.

Ahogy ezt a dokumentáció rákövetkező mondata is megerősíti:

The value is expr.

Tehát bármilyen identifier-t adunk meg, ugyanaz fog kijönni ebből a kifejezésből, mint amit beleküldtünk. Nem csinál vele semmit. Valószínűleg csak azért dolgozza fel, ha esetleg az ODBC driver benne hagyna valami ilyesmit a query-ben, akkor se haljon el.

Ezzel ennek a rejtélynek a végére is értünk. Már csak annyi a dolgunk, hogy elgondolkozzunk egy kicsit azon, hogy hogyan ne szaladjunk bele ebbe a pofonba legközelebb. Jó ötlet lehet például a prepared statement használata intval helyett vagy olyan tesztekkel kiegészíteni a meglévőket, amik az intval szerepére is építenek és szöveges $id-val próbálják feszegetni a rendszer határait.

Mást se szeretnél jobban, mint azonnal értesülni a friss tartalmakról? A legjobb helyen jársz! Feliratkozhatsz az oldal RSS feed-jére vagy követheted a blogot Twitteren.