A vendégposzt alapesetben ugye az a poszt, amit nem a blog szokásos szerzője, hanem egy vendégszerző ír. Na most ebben az esetben nem a szerző a vendég, hanem a téma, a most következő dolgok ugyanis nem az Égvemaradt.hu-val, hanem egy másik weboldallal estek meg. Azt az oldalt nem én készítem, hanem egy kedves barátom, akit az egyszerűség kedvéért nevezzünk most Liának. Az oldalt pedig – szintén az egyszerűség kedvéért – nevezzük lia.hu-nak.
Történetünk egy szép, kellemesen meleg februári napon vette kezdetét, amikor Lia rám írt, hogy “van egy kis gond”, ugyanis minden jel arra utal, hogy feltörték a WordPress-alapú honlapját, a lia.hu-t. No, ennek a fele sem tréfa, gyorsan rá is néztem az oldalra, de nem láttam semmi gyanúsat.
Kis kitérő: azt érdemes rólam tudni, hogy egy ideje már a kiberbiztonságból élek. Ez a fő- és a mellékállásom is, magánszemélyként is érdeklődöm is a téma iránt, és ha valaki más azzal fordult volna hozzám, hogy egy barátja segítséget kért tőle valami hekkertámadással kapcsolatban, akkor csípőből el tudtam volna mondani neki a nyomozás elkezdéséhez szükséges óvintézkedéseket: virtuális gép, tűzfal, antivírus, vegyvédelmi öltözet, azt’ hajrá. Nna, ehhez képest sikerült nekem azonnal megnyitni a lia.hu-t, a saját gépemről, a saját böngészőmből.

Nemhiába mondják, hogy a suszter gyerekének lyukas a cipője… Mondjuk rendszeresen frissített böngészőm, tűzfalam meg antivírusom legalább van.
Az oldal első blikkre tényleg rendben volt, de akármelyik linkre kattintottam, csak ugyanarra a statikus főoldalra kerültem vissza. Gyorsan megnyomtam a hackergombot, de ott sem láttam semmi érdekeset, úgyhogy inkább másik irányból közelítettem meg a problémát.
Ha nagyon hivatalos akarok lenni, akkor azt mondhatom, hogy eddig ún. black box tesztelést végeztem, azaz a rendszerbe nem belepiszkálva (a dobozt nem kinyitva) vizsgáltam annak működését, viselkedésanalízist folytattam. Viszont én már kisgyerekkorom óta sokkal inkább a white box megközelítést érzem magaménak (mondjuk így azt, hogy gyakran szétszedtem dolgokat, amiket aztán néha nem tudtam összerakni), úgyhogy inkább kinyitottam azt a bizonyos dobozt, és gyorsan be-FTP-ztem a tárhelyre, hogy fájlszinten nézhessek szét.
A malware anatómiája
Az első, ami feltűnt, az volt, hogy minden egyes mappában ott figyelt egy-egy .htaccess fájl, bitre azonos mérettel és közel másodpercre azonos létrehozási dátummal. Ez volt az a dátum, ami aztán belém is égett a felderítés heteiben: 2023-12-16 04:30:00.
Ezen a szinten azt érdemes tudni a .htaccess fájlokról, hogy ezek határozzák meg az Apache webszerver viselkedését az adott mappában. A legtöbbször arra használják őket, hogy elrejtsék a böngésző címsávjából a ronda fájlkiterjesztéseket (.php, .html, .asp), letiltsák vagy autentikációhoz kössék egy mappa elérhetőségét (403 Forbidden), stb. A lényeg, hogy aki manipulálni tudja a .htaccesst, az kedvére alakíthatja a webszerver viselkedését.
Ez természetesen nem egy eredendően gonosz dolog, maga a WordPress is nagymértékben támaszkodik ezekre a mechanizmusokra. Ezeket a fájlokat viszont valaki módosította, mert a következőt tartalmazták:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^(index|wp\-admin|wp\-include|wp\-comment|wp\-loader|wp\-corn\-sample|wp\-logln|output|about|admin|randkeyword|readurl|wp\-ver).php$ - [L]
RewriteRule ^.*\.[pP][hH].* index.php [L]
RewriteRule ^.*\.[sS][uU][sS][pP][eE][cC][tT][eE][dD] index.php [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php [L]
</IfModule>
Nem olvasom folyékonyan a .htaccess fájlokat, de ezen ránézésre látszik, hogy valami nem stimmel. Az index és a wp-admin ismerős címek, a wp-include és a többi wp- kezdetű is lehet legitim URL, de például a wp-corn-nál (kukorica?! Vagy talán a cron elgépelve?) meg a wp-logInnél (nagy i betű!) azért felszaladt a szemöldököm. Ami viszont teljesen egyértelműen elárulja, hogy ez a fájl tényleg rosszindulatú, azok az ezutáni kulcsszavak: admin (wp nélkül), randkeyword, readurl – ezeknek semmi keresnivalójuk nincs egy normális WordPress .htaccessében.
A következő sorokról is ordít a hekkelés: ha a böngésző a fentieken kívül bármilyen más php-t kér, vagy ha a kérés tartalmazza a suspected szót (kis- és nagybetűk nem számítanak), akkor a kért erőforrás helyett a statikus kezdőoldalt kapja.
És hogy mi értelme ennek az egésznek? Egyelőre még nem tudtam, de az egyértelmű volt, hogy nagyon erősen egyetlen belépési pont, az index.php felé terelte a látogatót. De vajon miért, mi volt az index.php-ben?
Feltárul az index.php
Egy klasszikus, izgulós filmben itt most belépnénk az ősi, évtizedek vagy talán évszázadok óta háborítatlan index.php porlepte, súlyos nyikordulással résnyire nyíló kétszárnyú ajtaján, hogy a fáklyánk remegő fényében megpillanthassuk, mi rejtőzik odabent.

Egy PHP-alapú oldal üzemeltetése azért nem ennyire izgalmas történet (illetve lehet az, csak másképp), az index.php a konvenciók szerint egyszerűen az adott mappa alapértelmezett fájlja. Tehát ha a cicak/cirmi.jpg-t vagy cicak/kormi.gif-et kérted, akkor a kért képfájlt kapod, de ha csak annyit írsz be a böngésző címsorába, hogy cicak vagy cicak/, akkor a cicak/index.php-t. Ennyi.
Ezért aztán az index.php-kban általában valamilyen normális HTML (-t generáló PHP-kód) szokott lenni, esetleg egy átirányítás valamelyik másik oldalra. De semmiképpen nem ez:
<?php
$BKAPIPVO = "\x3d\x2d\x30\x3d\x39\x2c\x3b\x3e\x33\x3d\x27\x26"^'ZWESZCVNAXTU';
$MPCPRAGP = "\x28\x35\x20\x22\x77\x66\x14\x3e\x21\x36\x23\x2e\x29"^'JTSGARKZDULJL';
eval($BKAPIPVO($MPCPRAGP('eJx1[...]0D0=')));
$MGRIXLLT = ZNZLMWXS('KXGUELMNFGRNs7dV0NZXUjYzNbfVBgA=');
$BFBGW = ZNZLMWXS('JWIDPXOPBVKM9UwN82r5lcz1rDQVQQA');
$SVKMXQKKABF = 'Mhwo[...]Xb3o=';
eval($MGRIXLLT($BFBGW('eJxl[...]FQ4=')));
Ez nem egy hibásan letöltött fájl, nem klingonul íródott, és nem is a te böngésződnek vannak karakterkódolási problémái. Amit itt látunk, az a szép magyar szóval obfuszkációnak (angolul obfuscation) nevezett dolog eredménye.
Az egér eszköztára
A világon a legtöbb dolog nem fekete vagy fehér. A kiberbiztonságban végképp nem, ezért még a legoptimistább szakember sem gondolja azt, hogy létezne 100%-osan biztonságos rendszer. A támadók mindig találni fognak egy újabb sérülékenységet – de a védőknek meg az a feladatuk, hogy ezt észrevegyék, és befoltozzák a lyukakat, amelyeket a támadók eddig kihasználtak (jó esetben előre, rosszabb esetben utólag). Ezért a támadók és a védők egy állandó macska-egér játékot űznek egymással, amelyben mindkét félnek megvannak az eszközei arra, hogy az ellenfelek munkáját megnehezítsék.
Az obfuszkáció a támadók egyik eszköze, amellyel igyekeznek elrejteni egy rosszindulatú fájl valódi célját.* Hiába találtam meg ugyanis védőként a keresett fájlt (az index.php-t), és hiába tudom olvasni a PHP nyelvet, ezekből a kriksz-krakszokból semmit nem értek. Ha kezdő lennék, lehet, hogy fel is adnám ezen a ponton, és akkor a támadók elérték (sőt, túlteljesítették) volna a céljukat, hiszen meggátoltak volna engem a malware elemzésében, megértésében. Így csupán lelassítottak – mondjuk azt eléggé sikeresen.
*: Az obfuszkáció ezen kívül másban is segíthet a támadóknak: lassíthatja és megnehezítheti a malware elemzését, illetve elrejtheti a malware jellegzetes (és emiatt antivírusok által felismerhető) részeit. Persze ezáltal más jellegzetességeket generál, amit az antivírusok szintén felismerhetnek, de ebbe most ne menjünk bele… Mondtam már, hogy ez egy állandó macska-egér játék? 🙂
Apró kitérő: amikor azt írom, hogy malware, akkor nem az angoltudásommal akarok felvágni. Magyarra talán rosszindulatú szoftvernek vagy kártevőnek lehetne leginkább fordítani ezt a kifejezést, de előbbi túl hosszú, utóbbi pedig a számítástechnikán kívül is értelmezhető. Egyszer-egyszer azon kapom magam, hogy a magyarban szintén használt vírus vagy féreg szóval próbálnám elkerülni a szóismétlést, de a fogalmak között vannak apró eltérések, ezért a pontosság kedvéért igyekszem a legtágabb fogalomnál, a malware-nél maradni, még ha a magyar szövegben idegenként is hat ez az újra és újra felbukkanó angol szó. Cserébe megígérem, hogy a magyar nyelvtani szabályoknak megfelelően ragozom. 🙂
Az obfuszkáció tipikusan a malware elkészítésének utolsó lépése. Tehát a gonoszok megírják az ember által is tökéletesen olvasható PHP-kódot, majd a végén megnyomják a billentyűzetük közepén található, nagy, Obfuscate feliratú gombot, és voilà, a kód máris olvashatatlanná válik.
A támadók természetesen megpróbálják annyira elfedni az eredeti kódot, amennyire csak lehet. De ennek van egy határa: a végrehajtó gépnek muszáj megértenie, hogy mit kell tennie. Tehát csak annyira változtatható meg a kód, hogy még végrehajtható maradjon. Fordított (compiled) nyelvek esetében a lefordított bináris fájlok általában eléggé elfedik az eredeti kódot, és bár a gép tökéletesen érti őket, emberként nehéz megérteni a kód logikáját. A PHP viszont nem fordított, hanem interpretált nyelv, azaz nem a programkódból fordított binárist adjuk oda a gépnek végrehajtásra, hanem magát a programkódot. Emiatt az obfuszkált kódnak is érvényes PHP-kódnak kell maradnia, ha pedig valami érvényes PHP-kód, akkor azt – még ha nyögvenyelősen is, de – el tudom olvasni! Sőt, akár soronként (utasításonként) is le tudom futtatni, és figyelhetem, ahogy a kriksz-krakszok a szemem láttára alakulnak vissza a malware eredeti kódjává!
Nosza, feltűrtem hát az ingujjam, és nekiálltam végigkövetni a parancsokat.

Mi van igazából ide írva?
Kezdetnek nézzünk valami egyértelműt:
<?php
Ez annyit mond a PHP-értelmező motornak, hogy “Jól figyelj, ami innentől következik, az PHP-kód lesz, végre kell hajtanod!“. Ez még nem volt obfuszkálva, könnyű volt. Lássuk a következő utasítást:
$BKAPIPVO = "\x3d\x2d\x30\x3d\x39\x2c\x3b\x3e\x33\x3d\x27\x26"^'ZWESZCVNAXTU';
Ez már durvábbnak néz ki, de szét tudjuk szedni két részre, az egyenlőségjel jobb és bal oldalára. Mivel ez az egyenlőségjel a programozásban nem egy matematikai szimbólum, hanem az értékadás operátora, először a jobb oldalt kell megfejtenünk.
A jobb oldalon egy XOR (kizáró vagy) műveletet találunk (ennek a jele a ^), amely két karakterláncot (programozónyelven ún. stringet, szimpla és dupla idézőjelek között) dolgoz össze egymással. A második string egyértelmű, ZWESZCVNAXTU. Bármennyire is úgy tűnik, ez nem egy lengyel város neve, hanem egy véletlenszerű karaktersorozat.

Az első string egy kicsit ijesztő lehet a sok \x-szel, de azok igazából csak annyit jelentenek, hogy “a megadott ASCII kódú karakter“. Ezek lehetnének akár mindenféle barátságtalan, nem nyomtatható karakterek is, de itt most szerencsénk van, a XOR bal oldalát így is írhattuk volna: =-0=9,;>3=’&. (Figyelem, ezek nem további műveletek, amelyeket a PHP-motor végrehajt, hanem csupán egymás után írt karakterek.)
A XOR művelet fogja ezt a két stringet, és előállít belőlük egy harmadikat oly módon, hogy ahol a két stringben azonos bit áll, oda 0-t ír, ahol eltérő, oda pedig 1-et. Ennek az eredménye pedig az lesz, hogy gzuncompress. De hiszen ez egy értelmes parancs! Hogy mit csinál, az egyelőre nem számít, a lényeg, hogy úgy tűnik, jó irányba haladunk! Hurrá!
Ezt a gzuncompress-t végül értékül adjuk egy PHP-változónak, amit $BKAPIPVO-nak nevezünk el (és amely név ugyanolyan véletlenszerű karakterhalmaz, mint a ZWESZCVNAXTU). Tehát ahol a továbbiakban $BKAPIPVO-t olvasunk, oda igazából gzuncompress-t kell képzelnünk. Hiszen a PHP-értelmező motor is ezt teszi.
A következő sorban ugyanezt a módszert követve a base64_decode parancsot rejtjük be a $MPCPRAGP változóba, ennek a végigkövetése legyen házi feladat. 🙂 A nagy trükk ugyanis az azt követő sorban bújik meg:
eval($BKAPIPVO($MPCPRAGP('eJx1[...]FQ4=')));
(Az eredeti fájlban a […]-ok helyett természetesen hosszú-hosszú karaktersorozatok szerepelnek, amelyeket egyrészt átláthatósági, másrészt biztonsági okból nem teszek közzé.)
eval is evil
Az eval egy különleges parancs. Sok nyelvben megtalálható, és nagyjából mindegyikben ugyanazt tanítják róla (már amennyiben tanítják egyáltalán): ne használd! Nem véletlen az “eval is evil” kifejezésre érkező rengeteg Google-találat, bár azért egy kicsit alaposabban megnézve őket kiderül, hogy sok helyen azért árnyalják ezt a sarkos kijelentést.
De mi is ez az eval, mire jó, és miért gonosz?
Az eval (az angol evaluate, kiértékel szóból) egy olyan parancs, amelynek odaadhatunk egy stringet (szöveget), amit ő aztán programkódként értelmez és végrehajt. Tehát ez a programkód:
<?php
echo 'Helló!';
végső soron ugyanazt fogja csinálni (kiírja, hogy Helló!), mint ez:
<?php
eval("echo 'Helló!';");
Nem szeretnék belemenni annak a fejtegetésébe, hogy miért jó ez a parancs egyáltalán (én sem tudom pontosan, csak néhány ötletem van), a lényeg az, hogy az eval egy elérhető eszköz a PHP-ban.
És hogy miért mondják róla, hogy gonosz? Elsősorban azért, mert egy nagyon nagy hatalmú eszköz, és mint minden nagy hatalmú eszközzel, nagyon könnyű vele biztonsági rést nyitni a programon. Ezt amúgy a hivatalos dokumentáció is kiemeli színes-keretes szöveggel mind PHP-ban, mind pl. JavaScriptben.

Ettől függetlenül viszont a rosszindulatú programok előszeretettel használják az evalt az obfuszkációra. Tételezzük fel, hogy a malware valami nagyon gonosz dolgot akar csinálni, amihez egy ún. rendszerhívásra van szüksége:
<?php
system("rm -rf /");
A PHP a system paranccsal tud kérést küldeni az operációs rendszer felé (ez a rendszerhívás), maga a kérés, az rm -rf / pedig arra utasítja az operációs rendszert, hogy mindent töröljön a merevlemezről.
Tegyük fel, hogy van egy olyan végtelenül egyszerű antivírusunk, ami futtatás előtt átnézi a PHP-fájlokat, és ha megtalálja bennük a system szót (azaz egy potenciális rendszerhívást), akkor nem engedi őket futni. Ez az antivírus ezáltal látszólag tökéletesen véd (mondjuk egy csomó fals pozitívot is fog hozni, azaz ártalmatlan programokat sem fog futni hagyni, de a példa kedvéért ezt most engedjük el) – de mit is írtam egy kicsivel feljebb a tökéletes védelemről?

A támadó többféleképpen is kijátszhatja ezt a primitív védelmet. Igaz, hogy nem írhatja le a kódba a system szót, de az evalt használva csupán annyi lesz a dolga, hogy valamilyen módon előállítsa ezt a karaktersorozatot, az pedig végtelen módon megtehető. Például:
<?php
$cmd = 'rm -rf /'; // így nem kell mindenhol kiírnom
eval('s' . 'ystem($cmd);'); // sima összefűzés
eval("\x73ystem($cmd);"); // lásd feljebb
eval(('000000' ^ 'CICDU]') . '($cmd);'); // lásd feljebb
eval(strrev('metsys') . '($cmd);'); // string megfordítása
// ...
A fenti példákban sehol nem írtam le magát a system szót, mégis minden sor végrehajtja a rendszerhívást. Szemfüles olvasóknak most lehet egy olyan észrevétele, hogy igazából eval nélkül is megoldható lenne ugyanez:
<?php
$x = 's' . 'ystem';
$x('rm -rf /');
Ez így van, eval nélkül is megy a dolog, de az evallal nem csak egy-egy függvény, hanem a program teljes szerkezete is elrejthető. De azért ha ez feltűnt, akkor grat! 🙂

Na de akkor végül mi volt igazából odaírva?
Most, hogy már minden építőkockát megismertünk, nézzük csak meg még egyszer az index.php-ban talált első evalt!
eval($BKAPIPVO($MPCPRAGP('eJx1[...]FQ4=')));
A fenti behelyettesítésekkel ebből a következő lesz:
eval(gzuncompress(base64_decode('eJx1[...]FQ4=')));
A gzuncompress és a base64_decode számunkra most lényegtelen, stringeket manipuláló függvények, de az eJx1 kezdetű, látszólag értelmetlen karakterláncból előállítják azt az érvényes PHP-kódot, amit aztán az eval végrehajt. Ez pedig nem más, mint:
//H~@cYTiX!0Ki_pq
function ZNZLMWXS($data) {
##Eyz;aylT4
$char = substr($data, 0, 1);
/*O8MKzM*/
$length = ord($char)-64;
//On&nW%g,WJdN1TUX_hfb
$key = substr($data, 1, $length);
##8H`;_+,w
$data = substr($data, $length+1);
//:~}xm&-XF&=x
$data = gzinflate(base64_decode($data));
/*W<4O+*6JxPx&)*/
$out_data = "";
##jzk]E)H8(
for ($i = 0; $i < strlen($data);) {
##B5<#GMozjSc
for ($j = 0; $j < strlen($key) && $i < strlen($data); $j++, $i++) {
##|15(>dgYYG}\tH8p_w
$out_data .= chr(ord($data[$i]) ^ ord($key[$j]));
//m*BER
}
}
return $out_data;
##G#|(1o_
}
Ha kigyomláljuk belőle a kizárólag zavarás céljából odagenerált kommenteket, akkor maradnak a vastaggal szedett sorok. Ezek egy ZNZLMWXS nevű függvényt definiálnak, amely egy bemenő stringet valamilyen algoritmus szerint egy másik stringgé alakít át.
Vájtfülűeknek annyi még azért érdekes lehet benne, hogy ez egy XOR-titkosítás, szóval már nem sima kódolásról beszélünk, mint a base64-kódolásnál vagy a gzippel való tömörítésnél, hanem igazi titkosításról, ami a kulcsa nélkül feltörhetetlen lenne (mert ún. one time pad-et használ). Viszont mivel itt maga a kulcs is megtalálható a fájlban (a bejövő adat első n karaktere), folytathatjuk a visszafejtést.

Létrejött tehát menet közben (!) a ZNZLMWXS nevű dekódoló függvény, amit a következő sorokban rögtön el is kezdünk használni:
$MGRIXLLT = ZNZLMWXS('KXGUELMNFGRNs7dV0NZXUjYzNbfVBgA=');
$BFBGW = ZNZLMWXS('JWIDPXOPBVKM9UwN82r5lcz1rDQVQQA');
Ez a két sor elkészít két újabb véletlen nevű változót: az $MGRIXLLT-t, amelynek az értéke gzuncompress lesz, és a $BFBGW-t, amely pedig – ki nem találnátok – base64_decode. Tehát ahol a kód további részében ezek a változónevek szerepelnek, oda igazából ezeket a függvényeket kell odaképzelni.
A következő, utolsó előtti sor egy $SVKMXQKKABF nevű, szörnyen hosszú karakterlánc, egy konstans, nem csinál semmit. Látszólag értelmetlen, de könnyű kitalálni, hogy épp ebben rejtőzik a lényeg, a malware igazi kódja, eddig ugyanis csak bizonyos szabályok szerint generált, az obfuszkálásra (és annak visszafejtésére) használt kódot láttunk. Az utolsó sor is követi az eddigi mintát:
eval($MGRIXLLT($BFBGW('eJxl[...]FQ4=')));
Azaz:
eval(gzuncompress(base64_decode('eJxl[...]FQ4=')));
Ha elvégezzük a base64 dekódolást és a gzuncompress-t, akkor a következőt kapjuk:
@ini_set('display_errors',0);
error_reporting(0);
eval($MGRIXLLT($BFBGW('eJxt[...]Otg==')));
eval($MGRIXLLT(str_rot13($BFBGW('a5wV[...]7A=='))));
Itt az első két sor csak arra szolgál, hogy ha bármi nem úgy sikerülne a malware-nek, ahogy azt tervezte, akkor se bukjon le a hibaüzenetek miatt. A többinek pedig kísértetiesen ismerős a szerkezete… Stringmanipuláció, majd eval. Újra stringmanipuláció, újra eval.
(Az str_rot13 is egy sima stringmanipuláló függvény, azt még csak nem is álcázták. Fekete öves geocacherek pedig fejben végre tudják hajtani. :))
Az első eval megint csak egy XOR-kódoló függvényt definiál (ennek UWLAEJADXTEZ lesz a neve), a második viszont ezt a PHP-kódot adja:
eval($MGRIXLLT(strrev($BFBGW('1PSM0[...]nHg='))));
Az pedig ezt:
eval($MGRIXLLT($BFBGW('eJwV[...]Bw==')));
Az meg ezt:
eval($MGRIXLLT(str_rot13($BFBGW('a5wV[...]ug=='))));
Az meg ezt:
eval($MGRIXLLT($BFBGW('eJwV[...]RUw=')));
És így tovább.
Mi van???
Ha már te sem tudod követni az újabb és újabb evalok végeláthatatlan sorát, és/vagy jojózik a szemed a sok értelmetlen, hosszú karakterlánctól, nem vagy egyedül. És nem is benned van a hiba, ugyanis amit épp csinálunk, az nem embernek való feladat. Épp ez az obfuszkáció lényege: a kód visszafejthető, de annyira nehezen, annyira fárasztóan és annyira sok hibalehetőséggel, hogy gépesített segítség nélkül tulajdonképpen lehetetlen. Ahhoz tudnám hasonlítani, mint amikor nagyon ráérő barátok kismillió réteg papírba csomagolják az ajándékot (és persze vannak ennek extrémebb változatai is), csak itt a végén nem ajándékot kapsz, hanem ha nem vigyázol, vírust.

Az utolsó pár kódrészletnél feltűnhetett, hogy annak a bizonyos szörnyen hosszú karakterláncnak, az $SVKMXQKKABF-nek semmi nyoma. Ez így is van, de a megérzéseim azt súgták, hogy majd valamikor a sokadik réteg alatt fel fog még bukkanni.
Ezt bebizonyítani viszont kézzel lehetetlennek tűnik. Ha jól emlékszem, 10 vagy 20 rétegnyi evalt bontottam fel, mielőtt eldöntöttem, hogy írok egy saját eszközt, ami automatikusan végrehajtja nekem a fenti lépéseket anélkül, hogy futtatná magát a malware-t. Neki is fogtam a feladatnak, és hamarosan elkészült az első PHP-deobfuscatorom. Erről az eszközről fog szólni a bejegyzés második része, ha elkészülök vele (márhogy a blogposzttal), ideteszem a linket.
További szálak
Hosszú időn keresztül másolgattam ide-oda káprázó szemmel az eval-hagyma lehántott rétegeit. Ehhez amúgy már bölcsen egy online PHP futtatókörnyezetet használtam, nem a saját gépemet – hiába, na, lassan, de tanulok. Közben viszont néha tartottam rövidebb-hosszabb szüneteket is, és ilyenkor egy kicsit más irányból néztem rá a malware-re: a white box-megközelítés helyett visszatértem a black boxhoz, a technikai részletek vizsgálata helyett inkább hátraléptem kettőt, hogy a nagyobb képet láthassam.
Liát ugyanis – teljesen érthetően – kevésbé nyűgözte le az obfuszkáció folyamata és visszafejtése, őt sokkal inkább azok a kérdések foglalkoztatták, hogy:
- Hogyan lehet visszaállítani a lia.hu eredeti állapotát?
- Miért törték fel egyáltalán?
- Kik tették és hogyan?
Nekem pedig barátjaként az elsődleges feladatom az volt, hogy ezekre a kérdésekre választ adjak, ne pedig kíváncsi kisgyerekként az obfuszkációval bíbelődjek.
Az eredeti állapot visszaállítása
Az első kérdésre adott alapértelmezett válasz egy korábbi biztonsági mentésből való visszaállítás szokott lenni. Sajnos ebben az esetben ez nem volt járható út, mert a tárhelyszolgáltató csak x napnyi teljes weboldalmentést tárolt, a feltörés óta azonban már több idő telt el, mire Lia észrevette, hogy baj van.
Egyébként ha sikerült is volna visszaállítani az oldalt egy korábbi, még helyesen működő állapotba, folyamatosan ott lebegett volna a fejünk fölött a lehetőség, hogy ugyanazt a biztonsági rést kihasználva (hiszen egyszer már láttuk, hogy ez lehetséges) másodjára is feltörhetik a honlapot.
Szóval a teljes visszaállítás nem működött, de azért nem volt még minden veszve. Mivel a lia.hu-n egy WordPress futott, az egyes aloldalak tartalmai nem külön-külön fájlokban voltak tárolva, hanem egy adatbázisban, ahogy a WordPress ezt csinálja. Nagy szerencsénkre ez az adatbázis nem sérült meg a malware-től, szóval a leginkább pótolhatatlan tartalom, a Lia által írt szövegek megmaradtak. A WordPresst ugyan nem használhattam az exportáláshoz (hiszen az admin felülete sem volt elérhető), viszont a tárhelyszolgáltató által biztosított phpMyAdmin felületen keresztül le tudtam menteni a teljes tartalmat, így az már biztos helyre került. Lia letörölhette az első izzadságcseppet a homlokáról.
Ezután megbeszéltük, hogy FTP-n keresztül leszedek annyi tartalmat, amennyit csak tudok (el nem hinnétek, hány apró fájlból áll egy rendesen megpluginozott WordPress! És bezippelni sem tudtam a tartalmat!), aztán veszünk egy nagy levegőt, és nyomunk egy gyalut a teljes tárhelyre.

Ezzel kapcsolatban annyi érdekesség volt még, hogy nem volt elég felperzselni a web root mappát (erős egyszerűsítéssel az ebben található fájlok érhetőek el a világ számára), találtam fertőzött fájlokat ezen kívül is, mindenféle ideiglenes és rendszermappákban. És csak remélni tudom, hogy megfelelően működött a tárhelyszolgáltató sandboxingja, és a host rendszer (meg a többi, ugyanezen a fizikai szerveren hostolt weboldal) fájlrendszerét nem érte el a malware. Felettébb makacs kis rohadék volt ez!
Miután eltűntek az utolsó rosszindulatú bitek is (kellett némi idő, mire elhittük, hogy tényleg vége), tiszta lappal lehetett újrakezdeni a lia.hu újraépítését. Ehhez természetesen kikeresgéltük az adatbázismentésből a fontos szöveges tartalmakat, de mielőtt átadtam volna Liának, ezerszer leellenőriztem, hogy semmilyen fertőzött vagy veszélyes elem ne maradhasson bennük (arra az esetre, ha mégiscsak belemászott volna a malware az adatbázisba is). A HTML tageket is kidobáltam, így a formázás elveszett, de ekkorra már a tetőfokára hágott a paranoia, úgyhogy az is nagy szó volt, hogy nem kézzel másoltattam le szegény Liával egy papírra a képernyőről az adatbázisban talált szövegeket amolyan tökéletes airgapként. 😀
A malware célja
Az eddigiekben több ezer szót írtam a malware technikai jellegzetességeiről, de (a kevésbé technikai emberek számára) talán a legfontosabb kérdést még nem érintettem: Vajon mi volt ennek az egésznek az értelme, a célja?
A honlapot ismerve szinte biztosra vehető, hogy a támadás nem célzott volt, nem konkrétan a lia.hu ellen irányult. A WordPress motortól eltekintve ugyanis teljesen statikus volt, nem volt rajta semmilyen rejtett információ, bejelentkezési lehetőség, értékes személyes adat. Az interakció egyetlen formája egy kapcsolatfelvételi űrlap volt, de közvetlenül fölötte ott volt az e-mail cím is, amire az űrlap a levelet küldte.
Az, hogy eddig még nem írtam a támadás céljáról, két okra vezethető vissza. Részben arra, hogy a cél elsőre nem is volt számomra nyilvánvaló, részben pedig arra, hogy nagyon elszomorodtam, ahogy szép lassan megláttam, mire használták fel a hackerek a korábban szebb napokat látott domaint. Egy végtelenül egyszerű, már ránézésre is messziről kerülendő, átverős online webshopnak kinéző, szánalmas oldalra vitték a látogatókat, ahol ottjártamkor épp serpenyőt meg air fryert “vehettem volna”. A megjelenő árucikkek amúgy véletlenszerűen töltötődtek le egy központi szerverről valamilyen kulcsszavak alapján, innen-onnan összelopkodott fotókkal, rémesen magyarra fordított szövegekkel, stb.
A készítők arra amúgy odafigyeltek, hogy csak azoknak tolják az arcába a lopott holmit, akik valamilyen keresőből (Google-ről, Bingről, Yahoo-ról vagy Yandexről) érkeztek. Aki célirányosan a lia.hu-t írta be a böngésző címsorába, mint ahogy én is tettem, amikor Lia szólt a támadásról, az nem látta a “webshopot”, csak az eredeti weboldal statikus képét. Ennek minden bizonnyal az volt a célja, hogy minél később bukjon le malware – egy weboldal tulajdonosa, adminisztrátora ugyanis általában közvetlenül látogatja meg az oldalát, nem keresőből érkezve.
Ki(k) állhat(nak) a támadás mögött?
Erre a kérdésre (természetesen) kezdetben nem tudtam válaszolni. Kerestem ugyan nyomokat a szerver access logjaiban, de igazából ebbe nem fektettem sok energiát, hiszen úgyis csak legfeljebb IP-címeket meg user agenteket nézegethettem volna, ezeket pedig egy magára valamit is adó támadó elrejti.
Néhány forrásfájl visszafejtése után is csak annyit tudtam mondani, hogy maga a malware (legalábbis egy része) kínai szövegeket tartalmaz, de megtaláltam egy indonéziai hackercsapat aláírását is. A siralmas “webshop” orosz szerverekkel kommunikált, úgyhogy egészen biztosan kijelenthető, hogy a malware technikai részét több modulból legózták össze, az alkotóelemek pedig szerte a világon készülhettek.

Maga a támadás is simán lehetett többszereplős, a mai kiberbűnözés ugyanis egyre szervezettebb. Külön csapatok írják a malware-eket, mások derítik fel a potenciális áldozatokat, megint mások törik fel és fertőzik meg a honlapokat. Ezután pedig ismét mások üzemeltetik ezeket a feltört honlapokat, nyilván a megrendelő céljának megfelelően. Adathalászat, bankkártyaadatok megszerzése, kriptovaluta bányászása, más rendszerek megtámadása… a bűnözők céljai végtelenül sokfélék lehetnek egy-egy feltört honlappal.
Ebben a konkrét esetben arra tippelnék, hogy a “webshopon” keresztül bankkártyaadatokat próbáltak megszerezni, vagy esetleg a gyanútlan látogatók böngészőjét próbálták megfertőzni valamilyen vírussal.
Hogyan jutottak be? Hogyan védekezhetünk ezután?
Az előzőhöz hasonlóan sajnos erre az első kérdésre sem tudok válaszolni. Ez elég nagy baj, mert az előző kérdéssel ellentétben ez a válasz elég fontos lenne – ha ugyanis nem tudjuk, hogy milyen biztonsági rést használtak ki a támadók, akkor be sem tudjuk azt foltozni. Maradnak hát sajnos a közhelyek: mindig legyen friss a WordPress, ne használjunk gyanús bővítményeket és/vagy témákat (na jó, de minek kellene gyanút keltenie egy laikusban?), és amiket használunk, azokat is frissítsük rendszeresen, automatikusan (hogy inkább az ún. ellátásilánc-támadásoknak eshessünk áldozatul, à la xz, mondhatná valaki szarkasztikusan).
A WordPress admin fiókhoz erős és máshol nem használt jelszót válasszunk, és lehetőség szerint kapcsoljuk be a kétfaktoros hitelesítést, a tárhelyszolgáltató adminisztrációs felületéhez szintúgy. Ezeket a jó tanácsokat egy találomra kiválasztott, kiberbiztonság témájú cikkből is megkaphatja bárki, de a lia.hu esetében sajnos nem tudok ennél többet, érdekesebbet mondani.
Ezek az intézkedések egy sikeres támadás kockázatát (riskjét) csökkentik, de emellett lehetséges annak hatását (impactját) is korlátozni, azaz elfogadni, hogy a honlapot fel fogják törni, és arra koncentrálni, hogy amikor ez megtörténik (nem ha, hanem amikor!), akkor minél kevésbé fájjon. A lia.hu szerencsére ebből a szempontból elég jó helyzetben van: mint feljebb írtam, természetéből adódóan nem kell dinamikusnak lennie, nem kell felhasználókat kezelnie vagy olyan érzékeny adatokat tárolnia, amelyek kiszivárgása esetén Liának komolyabb (akár jogi) problémákkal kellene szembenéznie. Már csak a gyorsabb, fájdalommentesebb helyreállítást kellene megoldani (pl. jól működő biztonsági mentésekkel), és akkor akár már rá is merném mondani, hogy jóvanazúgy!
(Nincs) vége
Hasonlóképpen így 4000 szó (!) után most már erre a blogposztra is rámondom: jóvanazúgy! 🙂 Az elvarratlan szálakkal pedig jövök majd a második részben, remélem, arra is hamarosan sort tudok keríteni!
Hogy mit honnan:
- Nem okos Forrest Gumpot, nem vidám Andy Bernardot és kíváncsi Jetro Willemst a Giphyről
- Az izgulós-titokzatos ajtót és az ingujj feltűrését a tenorról
- Wrzeszczt Adobe Stockról
- Zombis ajtót a Redditről
- Ceruzás kisötöst és reset a Napirajzról
- Enigmát a Wikipédiáról
- Pont olyan kíváncsi kisgyereket, amilyet akartam, nem találtam, ezért megkértem a Binget (ő meg Dall-E-t), hogy rajzoljon nekem egyet (a prompt: I need a photorealistic image about a curious toddler trying to open a small can with “MALWARE” written on it while several houses are on fire in the background. The kid seems to be ignoring the flames.)
- Bárkiket az EM360-ról egy gyors képkeresés után
- Szalacsi Sándort a Coubról