WordPress érdekesség, avagy miért nem jó ötlet fröccsöntött rendszereket használni

A minap a segítségem kérték, mert egy szerver spammelt, letiltották és azonosítani kellett a baj forrását. Kiderült, hogy feltörtek rajta egy WordPress blogot, és ezzel elég nagy bajt okoztak, ugyanis a WordPress PHP-ja hozzáfért egyéb könyvtárakhoz is, amiben más PHP oldalak futottak.

  1. Első alkalommal valószínűleg egy kép plugin hibát kihasználva base64-ben kódolva feljutott pár PHP fájl. Általában a szokásos dolgokra voltak kihegyezve:
    • teljes adatbázis leképezés, amihez a WordPress felhasználója hozzáfér
    • teljes könyvtárstruktúra leképezés, amihez a WordPress-t futtató PHP hozzáfér
  2. Következő lépésben elrejtett véletlenszerű könyvtárakban PHP fájlokat, amik több funkciót láttak el:
    • önmagukat terjesztették, mint file proxy
    • spam leveleket küldtek
    • webproxy-t hoztak létre
    • egyéb vicces dolgokat “telepítettek” egykattintásos szolgáltatókon hostolt címekről
  3. A legviccesebb lépése pedig az volt, hogy létező (lehetőleg index.php) PHP fájlok első sorába is elhelyezte magát a 11900. oszloptól! A fájlokba belenézve semmi érdekes nincs. De a grep mégis kidobja, hogy ott van benne az eval, sőt magát a kódot is. Nano-ban vagy mceditben az eval-hoz is ugrik, de belenézve nem látni.wordpress_hack2
    find . -name "*.php" -exec grep -H "eval" {} \; | more

    wordpress_hack

 

Update #1:

Közben találtam még egy finomságot:

wordpress_hack3

 

Mi a tanulság? Ha már fröccsöntött motorokat (WordPress, Joomla, Drupal, …) használunk, frissítsünk, amilyen gyakran csak lehet. Igen, ez is WordPress 🙂

Közel 4 óra volt kibelezni a különféle vicces dolgokat művelő PHP-kat a mappákból és még biztosan találni ezt-azt 🙁

Ui.: Most komolyan, mit nem lehet azon érteni, hogy NE HASZNÁLJ eval()-t, soha, semmiben. De főleg abban nem, ami felhasználó által bevihető inputból származik.

Update #2:

Egy kis Joomla hack. Az 1. sor: assets(), a 2. base64_decode() és már ott is az eval :(
Egy kis Joomla hack. Az 1. sor: assets(), a 2. base64_decode() és már ott is az eval 🙁

Kis olvasnivaló: https://www.exploit-db.com/exploits/17602/ és https://forums.digitalpoint.com/threads/wp-hack.2734636/ és https://jordankueh.com/2014/wordpress-remote-code-execution-base64_decode/ és https://jordankueh.com/2014/wordpress-remote-code-execution-base64_decode/ és http://msurowiecki.pl/index.php/inne/106-wordpress-under-attack-april-2013-tips-to-remove-spyware

2016.03.26. 17:38 - legyes

PHP és MySQL adattípus túlcsordulások

Azt ügye tudjuk, hogy a PHP-ben automatikus típuskonverzió van, amire oda kellene figyelni. A PHP-ban a számok ábrázolására 2 típus áll rendelkezésre (+boolean):
– (signed) integer
– (signed) float

Ezek minden esetben előjellel vannak ellátva, így az integer típusban maximum 2^63 érték tárolható egy 64 bites rendszeren, 64 bites PHP-val. Viszont! A MySQL-ben rendelkezésre áll az előjel nélküli változat, az unsigned bigint, ami ugyanezen körülmények között 2^64 tud ábrázolni, előjel nélkül. Így marad egy nagy hézag a kettő között.  Mivel a PHP-ban automatikus típuskonverzió van, így az (unsigned big)int átalakul a PHP-ben float típussá, amiből mi semmit sem veszünk észre. A float típussal ügye csak pontosságot vesztettünk.

De mi van akkor, ha a PHP által adott választ egy szigorúbb nyelvvel akarjuk feldolgozni – vagy mert int-et várunk -, erőltetjük az int típust:

echo (int) ((int) PHP_INT_MAX+1); // -9223372036854775808

Akkor az bizony a PHP-ban is túlcsordul, ahogy azt a MySQL-ben is tenné ha signed bigint lenne és ebből mi semmit sem veszünk észre. Ugyanez, automatikus típuskonverzióval:
echo(PHP_INT_MAX+1); // 9.2233720368548E+18

Hogy ez miért okozhat gondot?

Elterjedt szokás, hogy biztonsági okok miatt lehetőleg csak ID-ket (azonosító számokat) küldünk szerver oldalra, mivel ezeket jóval könnyebb szűrni, mint a stringeket. Az adatbázisban pedig az azonosítók legtöbb esetben integer típusok, leginkább unsigned bigint-ek, autoincrement tulajdonsággal. Később pedig a szerver oldali vizsgálatnál – mivel számot várunk és az autotincrement alapból 1-ről indul – lustaságból ezt alkalmazzuk és úgy gondoljuk, hogy ezzel macera nélkül (prepared statements) meg is oldottuk az SQL injection elleni védelmet:


$id = abs($_GET['id']); // barmi nem szamra 0-t ad, ami az alap autoincerement 1-es kezdosorszama miatt nem letezo sorra mutat az adatbazisban
$results = $pdo->query('UPDATE users SET money=money+1000 WHERE id='.$id);

Ok, de eddig még sohasem okozott gondot a túlcsordulás, miért kellene rá figyelnem? 

Hát például ezért:
echo (int) PHP_INT_MAX; // 9223372036854775807
echo (int) ((PHP_INT_MAX+1000000)-1000000); // 9223372036854774784

Természetesen ez oda-vissza érvényes, azaz figyelni kell arra, hogy a PHP integer típusa több egész szám tárolására képes, mint a MySQL bizonyos integer típusai. A MySQL persze dob warningot, de ha külön nem kérdezzük meg róla, akkor jó eséllyel nem fogjuk észrevenni PHP-ben.

 

Sok forráskódban láttam már a fent említett lustaságból alkalmazott szám-konverziós védelmet. A baj az, hogy úgy hiszik elegendő csupán (int) -el erőltetni a típust. Csak hogy az negatív számokat is elfogad, így a túlcsordítással máris pozitív számmá változtathatjuk, ami viszont már tutira létezik az adatbázisban a tipikus autoincrementes megoldásoknál. Kiszámolni pedig könnyű a megfelelő mértékű túlcsordítást okozó számot, hogy a kért ID-t kapjuk eredményül. 

2015.07.19. 15:10 - legyes