Spis treści

Wprowadzenie

Wraz z wydaniem nowej wersji 3.9.23 Joomla! 24 listopada 2020 roku, opublikowanych zostało kilka podatności w zabezpieczeniach występujących w poprzednich wersjach tego systemu. Problem, o którym piszę w artykule, był znany zespołowi Joomla! już 13 października, jednak ta informacja, zgodnie z zasadami bezpieczeństwa przyjętymi w projekcie, pozostawała niejawna.

Zbieżność daty ogłoszenia dziur w CMS z dniem wydania jego uaktualnienia nie jest przypadkowa. Joomla! to projekt open-source, a jego kod źródłowy jest publiczny i można go podejrzeć między innymi w serwisie GitHub. Łatwo jest się więc domyślić, że wydanie poprawki łatającej daną podatność jest doskonałym prezentem dla hakerów. Mogą oni podejrzeć zmiany w kodzie, które zaszły względem poprzedniej wersji i wytypować fragment kodu, w którym wprowadzono zabezpieczenie przed potencjalnym atakiem. Ta wiedza jest przydatna w procesie inżynierii wstecznej (ang. reverse engineering), który pozwala na dokładną analizę działania oprogramowania, a w szczególności tego fragmentu, który był podatny na ataki.

W tym artykule przeprowadzę wspomniany proces w celu wskazania błędu w kodzie źródłowym, który pozwala na włamanie się do systemu CMS. Zademonstruję tym samym sposób, w jaki można wykorzystać dziurę do uzyskania informacji, które nie powinny trafić w niepowołane ręce. Nie chcę jednak, żeby mój artykuł był wykorzystywany jako instrukcja krok-po-kroku do tego, jak włamywać się do aplikacji, w związku z czym proces ataku częściowo ocenzurowałem i niektóre informacje zostały przeze mnie zamazane.

[20201104] - Core - SQL injection in com_users list view (CVE-2020-35613)

Właśnie w ten sposób osoby odpowiedzialne za utrzymanie projektu Joomla! nazwały podatność, którą opisuję w tym artykule. Utrzymują oni stronę, na której publikowane są wszelkie informacje związane z bezpieczeństwem systemu. Można na niej wyczytać, że problem występował już od wersji 3.0.0, czyli naprawdę długo. Nic nie wskazuje jednak, żeby dziura została wykryta wcześniej, niż zrobił to zespół Joomla! i nie była ona wykorzystywana przez hakerów.

Warto zauważyć, że zespół określił powagę podatności jako low w czterostopniowej skali (low, moderate, high, critical). Jak sami twierdzą w swoim dokumencie, ten stopień jest zarezerwowany dla podatności, które są trudne do wykorzystania i które wymagają zaistnienia specyficznych warunków sprzyjających atakowi. I rzeczywiście tak jest w przypadku tego błędu. Jak wykażę w mojej analizie, dziura może być wykorzystana wyłącznie przez osoby, które mają dostęp do panelu administratora CMS oraz wgląd do listy zarejestrowanych użytkowników. Nie oznacza to jednak, że błąd możemy zbagatelizować. Ze względu na swoją naturę, ataki typu SQL injection mają poważne konsekwencje, a w szczególności pozwalają na ujawnienie pełnej bazy danych aplikacji. W wielu przypadkach atak będzie więc naruszeniem prywatności użytkowników danego portalu.

Podatność otrzymała numer CVE-2020-35613 w CVE - publicznej i centralnej bazie znanych podatności w różnych systemach informatycznych.

Inżynieria wsteczna

W opisie podatności czytamy:

Improper filter blacklist configuration leads to a SQL injection vulnerability in the backend user list.

To dla nas przydatna informacja, ponieważ nakierowuje nas na obszar oprogramowania, w którym problem występuje. Nie musimy jednak analizować całego kodu źródłowego Joomla!, co zresztą byłoby bardzo nieefektywne i jest bardzo prawdopodobne, że przeoczylibyśmy miejsce, w którym podatność występuje. Kod źródłowy Joomla! jest przecież dostępny publicznie i codziennie setki tysięcy programistów go czytają. Zapewne wielu z nich jest szczególnie wyczulona na punkcie bezpieczeństwa, a jednak przez tak długi czas dziura pozostawała przez nikogo niezauważona.

W celu namierzenia konkretnego miejsca, w którym problem występował, zaglądam do repozytorium Joomla!. Regułą przyjętą przez zespół Joomla! jest commitowanie wszelkich łatek bezpieczeństwa dopiero w ostatnim commicie przed wydaniem nowej wersji. Zapobiega to wyciekowi informacji o błędzie, zanim użytkownicy uzyskają dostęp do uaktualnienia swojego CMS.

Podatności załatanych w wersji 3.9.23 było kilka, co widać po ilości zmienionego kodu. My zajmiemy się tylko jedną z nich - SQL injection in com_users list view. W celu załatania dziury został zmieniony tylko niewielki fragment:

wynik działania git diff, który pokazuje dodany fragment kodu w nowej wersji Joomla!

Osoby dobrze zaznajomione z architekturą Joomla! już po samym komentarzu będą wiedziały, gdzie szukać dalej. Wspomniany state to prosta struktura stworzona z par kluczy i wartości, która pozwala na przekazywanie niektórych parametrów do zapytań SQL. Istnieje grupa specyficznych kluczy, która jest obsługiwana przez Joomla! w sposób automagiczny. Jedną z nich są filtry - służą one do zawężania kryteriów wyszukiwania na listach w panelu administratora.

W momencie otrzymania zapytania, podjęta jest próba skonstruowania state na podstawie zapytania przeglądarki. Zmienna filter może być przekazywana zarówno w metodzie GET, jak i POST. Łatwo stwierdzić, że jest to dość ryzykowne działanie - pobierane dane wejściowe podane przez użytkownika nie są na tym etapie w żaden sposób zabezpieczone.

Jak widać na poniższym fragmencie kodu, właściwość $filterBlacklist pozwala na pominięcie automatycznego zaimportowania wartości z zapytania przeglądarki. Problem w tym, że do tej pory to zabezpieczenie nie zostało skonfigurowane i dzisiaj już niestety wiemy, jakie to miało konsekwencje.

Fragment kodu źródłowego Joomla! - automatyczne ładowanie filtrów

Zgodnie z powyższą logiką, przekazując do zapytania zmienną filter_excluded=example, będziemy mieli przypisanie state := [ excluded => example ], gdzie wartość example nie jest w żaden sposób oczyszczona ze złośliwych treści.

W jednym z fragmentów pliku administrator/components/com_users/models/users.php widzimy prawidłowe i bezpieczne użycie zmiennej. Jest ona bowiem przekształcona w ten sposób, że ostatecznie do state przekazana zostanie tablica zmiennych typu integer, oczyszczona z wszelkich nieprawidłowych wartości. W zdecydowanej większości przypadków zmienne typu integer możemy traktować jako bezpieczne nawet wtedy, kiedy pochodzą bezpośrednio od użytkownika.

Fragment kodu źródłowego Joomla! - zamienianie tablicy na wartości typu integer

My jednak wiemy, że podatność istnieje. Dlaczego więc to zabezpieczenie okazuje się nie być wystarczające? Dzieje się tak, ponieważ kolejność wykonywania działań jest tu nieprawidłowa. W pierwszej kolejności rzeczywiście wykonuje się ten fragment, w którym zmienna excluded jest oczyszczana, ale niestety jest ona niemal natychmiast nadpisywana przez logikę importowania wartości z zapytania przeglądarki, którą opisałem wcześniej (opisana komentarzem twórców jako Receive & set filters). W rezultacie ten mechanizm bezpieczeństwa nie stanowi żadnej bariery - zmienna excluded ma wartość w dokładnie takiej postaci, jaką podał użytkownik.

W końcu dochodzimy do miejsca, w którym wartość state o kluczu excluded jest przekazywana do zapytania SQL. Jak widzimy, autor kodu zakładając, że będzie ona zawierała wyłącznie zmienne typu integer, nie przeprowadza żadnego filtrowania danych. Niestety skutkuje to powstaniem podatności SQL injection.

fragment kodu źródłowego, który pozwala na przeprowadzenie ataku SQL injection

Wykorzystanie podatności

UWAGA!
Opis wykorzystania podatności ma charakter wyłącznie demonstracyjny jako proof of concept. Wykonanie poniższych kroków bez zgody wszystkich zainteresowanych stron jest nielegalne. Symulowany atak może być przeprowadzony wyłącznie w warunkach testów penetracyjnych. Autor artykułu nie ponosi odpowiedzialności za niewłaściwe wykorzystanie informacji zawartych w tekście.

Tak jak wspomniałem wcześniej, powodzenie ataku możliwe jest tylko w określonych i dość skrajnych warunkach. Atakujący musi mieć bowiem dostęp do panelu administratora, a uprawnienia ACL skonfigurowane w CMS muszą umożliwiać mu przeglądanie listy użytkowników. Praktycznie zawsze będzie to osoba godna zaufania, która nie miałaby powodu atakować strony, a nawet często jedyną osobą z dostępem do panelu administratora będzie sam właściciel, który i tak przecież ma wgląd do wszelkich poufnych danych, w tym pełny dostęp do bazy danych.

Po spełnieniu powyższych warunków atak jest już trywialnie prosty. Wystarczy spreparować złośliwy URL, przekazując w nim odpowiednio przygotowany parametr. Na razie nie będę się skupiał na konkretnej jego wartości - w pierwszej kolejności chcę sprawdzić czy moja analiza była prawidłowa i czy rzeczywiście podane przeze mnie dane wejściowe nie są filtrowanie. Z doświadczenia wiem, że często, jeśli aplikacja jest podatna na atak typu SQL injection, przekazanie apostrofu (') w interesującym nas parametrze spowoduje jakąś anomalię, a najczęściej po prostu pojawi się błąd. I rzeczywiście tak jest w naszym przypadku: wystarczy otworzyć w przeglądarce pewien URL, a naszym oczom ukaże się taki komunikat:

komunikat o błędzie w zapytaniu SQL

Żeby było wyraźniej widać, w jaki sposób tworzone jest zapytanie, zmodyfikuję plik źródłowy, aby wyświetlił pełne polecenie SQL, które ma się wykonać.

fragment kodu Joomla z dodanym poleceniem echo query i exit

Jak widać po wyniku powyższego kodu, apostrof nie został w żaden sposób zmodyfikowany i w takiej postaci został wklejony do zapytania. Już od wyobraźni atakującego zależy jaki fragment przekaże do parametru aby uzyskać dostęp do poufnych informacji.

Automatyzacja SQL injection

Dzięki dostępowi do kodu źródłowego i dokonaniu inżynierii wstecznej potrafimy stworzyć dokładny fragment SQL, dzięki któremu możemy wyciągnąć z bazy danych różne informacje. Możemy przygotowywać starannie dobrane wartości tak długo, aż nie uzyskamy wszystkich interesujących nas danych.

Istnieją jednak różne narzędzia, które umożliwiają automatyzację ataku. Co więcej, metodą brute-force potrafią one wykryć podatność bez wglądu w kod źródłowy atakowanej aplikacji. Jednym z takich narzędzi jest dostępny na licencji open-source projekt sqlmap. Zademonstruję jego działanie na przykładzie analizowanej przez nas podatności. W rezultacie jego działania otrzymamy pełny zrzut bazy danych atakowanej aplikacji.

Nasza podatność jest możliwa do wykorzystania wyłącznie po zalogowaniu się do panelu administratora. sqlmap nie posiada interfejsu, który umożliwiłby nam podanie loginu i hasła do aplikacji, ale możemy do programu przekazać odpowiednie ciasteczka. W przypadku Joomla! jest to wystarczające, ponieważ uwierzytelnianie odbywa się na podstawie ciasteczka sesyjnego. Możemy je podejrzeć z poziomu przeglądarki po zalogowaniu do CMS.

okno modalne Google Chrome pokazujące wykorzystywane ciasteczka

Uruchamiam komendę, przekazując powyższe ciasteczko, a także podając znany przez nas URL. Wartość atakowanego parametru nie ma dla nas znaczenia, ponieważ sqlmap sam będzie podstawiał odpowiednie wartości. Opcjonalnie przekazałem też parametr -p "parametr". Nie jest to jednak wymagane - program sam potrafi ocenić który z parametrów jest podatny na atak.

./sqlmap.py --cookie="394ba4753e9116e2a3dfd93e19f5f032=0a8dd3d5d5cb023fab010d375f40cd2f" -u " http://localhost/administrator/_zredagowano_?parametr=1 " -p "parametr"

Program w trakcie swojego działania będzie zadawał kilka pytań typu Tak/Nie. Ma to na celu optymalizację działania, czyli ograniczenie wysyłanych zapytań do aplikacji (sqlmap działa w dużej mierze za pomocą metody brute-force). Po jakimś czasie uzyskamy pierwsze wnioski z ataku, a na koniec otrzymamy wynik działania programu, w zależności od formatu, o który poprosiliśmy. Możliwości formatowania jest wiele - możemy zażądać jedynie określonej tabeli bazy danych, konkretnych kolumn, a nawet pełny zrzut bazy danych. Ten ostatni format oczywiście zajmie najwięcej czasu, jednak uzyskamy pełną informację.

wynik działania programu sqlmap

tabele bazy danych

tabela users z bazy danych; pokazano kolumny id, username i password, zawierający hash bcrypt

Podsumowanie

Nie sposób stworzyć oprogramowanie odporne na ataki hakerskie, co widać między innymi po tym, z iloma dziurami bezpieczeństwa zmagają się aplikacje takich gigantów technologicznych jak Google, Apple czy Microsoft. Projekt Joomla! był poddany wielu audytom bezpieczeństwa, a mimo tego opisana przeze mnie podatność pozostała niezauważona już od wersji 3.0.0. Nie można też wykluczyć, że podobne błędy będą zdarzały się w przyszłości.

Oczywiście nie wszystkie podatności będą miały tak poważne konsekwencje. Żadnych z nich nie można jednak bagatelizować. Podatność [20201104] - Core - SQL injection in com_users list view wymaga zaistnienia skrajnych warunków, a co za tym idzie, jest bardzo niewielkie prawdopodobieństwo wykorzystania jej do ataku. Gdyby to się jednak hakerom udało, uzyskaliby oni dostęp do ogromnej ilości informacji, które są dla nich nieprzeznaczone. Co więcej, w przypadku źle skonfigurowanego serwera MySQL (np. wiele baz danych do różnych aplikacji przypisanych do jednego użytkownika, niewystarczająco silne hasło, brak łatek bezpieczeństwa), atak na jeden projekt może dać dostęp atakującemu do pozostałych baz danych, a w określonych przypadkach nawet pozwala wykonywać zdalnie komendy w systemie operacyjnym, na którym działa serwer. Ten fakt pokazuje jak daleko idące konsekwencje mają podatności typu SQL injection.

Można się zastanawiać, czy otwarcie kodu źródłowego sprzyja hakerom. Moim zdaniem jest zupełnie odwrotnie - fakt, że każdy (o odpowiedniej wiedzy) może przeanalizować aplikację, sprawia, że jest dużo większe prawdopodobieństwo wczesnego wykrycia podatności, zanim zostanie ona wykorzystana. Tak prawdopodobnie stało się właśnie w przypadku opisanego przeze mnie problemu - nic nie wskazuje na to, żeby dziura była znana wcześniej. Dodatkową zaletą open-source jest transparentność. Po przeprowadzeniu analizy mamy pełną wiedzę na temat podatności, a dzięki temu wiemy, jak się zabezpieczyć nawet w przypadku, w którym nie byłoby możliwości uaktualnienia CMS.

W firmie Empressia zawsze dokonujemy wszelkich starań, żeby strony internetowe naszych klientów były maksymalnie zabezpieczone i korzystały z najnowszych wersji wykorzystywanego oprogramowania. Na bieżąco monitorujemy biuletyny bezpieczeństwa systemów CMS czy frameworków, których używamy do tworzenia aplikacji klientów i reagujemy natychmiast. Często, tak jak w przypadku, który opisałem, dokonujemy procesu inżynierii wstecznej błędu, żeby posiadać maksymalną wiedzę o podatności, a kiedy używamy wtyczek dostarczanych przez mniej znanych twórców, analizujemy ich kod źródłowy pod względem potencjalnych dziur w zabezpieczeniach. Dbanie o poufność danych przetwarzanych przez zamawiane u nas aplikacje jest jedną z wartości, którymi kierujemy się w firmie Empressia. Jeśli masz szczegółowe pytania o to, jak dbamy o bezpieczeństwo tworzonych przez nas stron internetowych, zapraszamy do kontaktu przez formularz dostępny na stronie.