Turbo Intruder w testowaniu Race Condition

Czy wiesz, że będąc wystarczająco szybkim, możesz wykorzystać lukę, której przy zachowaniu „standardowej” prędkości wysyłania żądań http do serwera nie udałoby się zidentyfikować? Czasami przy szukaniu błędów bezpieczeństwa trzeba zmierzyć się w wyścigu z kodem programisty i serwerem.

Race Condition jest typem błędu wynikającym między innymi ze starego podejścia do programowania kiedy to wielowątkowość ani idea thread safe nie była zbyt popularna. Współcześnie co prawda coraz częściej można spotkać programowanie wielowątkowe wraz z podziałem na mikroserwisy, ale idea thread safe nie zawsze zostaje poprawnie zaimplementowana. Rezultatem tego są, podatności gdzie wiele wątków korzysta z tych samych danych, a wynik operacji zależny jest od kolejności, w jakiej wątki uzyskają do nich dostęp.

Przedstawię to na najczęściej spotykanym przeze mnie scenariuszu. Związany on jest z wirtualnymi kartami podarunkowymi i wygląda następująco:

  1. Sklep oferuje karty podarunkowe o wartości 1337 zł w postaci jednorazowego, unikalnego kodu. Jeden z nich to 1337. Można, go zrealizować wpisując na koncie użytkownika i dodając wirtualne środki na zakupy.
  2. Użytkownik wpisuje kod 1337 na swoim koncie (wysyła request dodania kodu).
  3. Serwer sprawdza w bazie czy kod 1337 istnieje.
  4. Serwer po potwierdzeniu, że kod 1337 istnieje, dodaje środki do konta użytkownika.
  5. Serwer usuwa kod 1337 z bazy.

Wszystko byłoby w porządku gdyby klient był tylko jeden, albo sklep obsługiwałaby użytkowników pojedynczo. Wielowątkowość prowadzi do tego, że pomiędzy punktem 4 i 5 powstać może luka czasowa gdzie jeden z wątków nie zdąży usunąć z bazy użytego kodu, a inny zdąży w tym czasie potwierdzić, że nadal jest niewykorzystany. W wyniku tego daną kartą podarunkową będzie można doładować konto wirtualnymi środkami kilkukrotnie jak w poniższym schemacie:

  1. Sklep oferuje karty podarunkowe o wartości 1337 zł w postaci jednorazowego, unikalnego kodu. Jeden z nich to 1337. Można, go zrealizować wpisując na koncie użytkownika i dodając wirtualne środki na zakupy.
  2. Użytkownik wpisuje kod 1337 na swoim koncie (wysyła request dodania kodu).
  3. Użytkownik wpisuje kod 1337 na swoim koncie (wysyła request dodania kodu).
  4. Użytkownik wpisuje kod 1337 na swoim koncie (wysyła request dodania kodu).
  5. Serwer sprawdza w bazie czy kod 1337 istnieje.
  6. Serwer, po potwierdzeniu, że kod 1337 istnieje, dodaje środki do konta użytkownika.
  7. Serwer sprawdza w bazie czy kod 1337 istnieje.
  8. Serwer, po potwierdzeniu, że kod 1337 istnieje, dodaje środki do konta użytkownika.
  9. Serwer usuwa kod 1337 z bazy danych.
  10. Serwer sprawdza w bazie czy kod 1337 istnieje.
  11. Serwer odpowiada, że kod 1337 jest nieprawidłowy (został usunięty z bazy).

Jak już wiemy bierzemy udział w wyścigu i musimy być szybcy w wysyłaniu requestów. Pomoże nam w tym wtyczka do Burpa – Turbo Intruder.

Można ją znaleźć w Bapp Storze lub na githubie – https://github.com/PortSwigger/turbo-intruder.

Po jej zainstalowaniu w Burpie wybieramy request odpowiedzialny za dodanie kodu do konta i poprzez menu kontekstowe wysyłamy go do Turbo Intrudera:

Naszym oczom ukaże się okno z wybranym requestem i edytorem, w którym musimy za pomocą języka python zdefiniować instrukcje. W rozpatrywanym przypadku możemy skorzystać z predefiniowanego skryptu examples/race.py. Musimy jedynie gdzieś dodać znacznik „%s” w żądaniu http oznaczający zmienną, którego de facto nie wykorzystujemy w tym przypadku:

Jeżeli wygramy wyścig (i kod odpowiedzialny za jego dodanie jest podatny) to uda nam się dodać środki do konta kilkukrotnie.

Spójrzmy teraz na przykładowy skrypt, który możemy wykorzystać np. do szukania ukrytych zasobów na serwerze:

def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                           concurrentConnections=5,
                           requestsPerConnection=100,
                           pipeline=False
                           )

    for word in open('/usr/share/dict/words'):
        engine.queue(target.req, word.rstrip())

def handleResponse(req, interesting):
    if req.status != 404:
        table.add(req)

Parametry, które będą nas głównie interesować pod kątem optymalizacji to:

concurrentConnections – oznacza ilość jednoczesnych połączeń

requestsPerConnection – oznacza ilość żądań na połączenie

pipeline – służy do wysyłania kilku żądań bez czekania na odpowiedź przed wysłaniem kolejnego

for word in open(’/usr/share/dict/words’) – ścieżka do słownika

if req.status – służy do filtrowania requestów, po interesującym nas kodzie statusu

Przy optymalizacji zależy nam na uzyskaniu jak największej ilości RPS (requestów na sekundę) przy jednoczesnym parametrze „Retries” bliskim 0 (nie obsłużonych, requestów przez serwer). Warto pamiętać również o odchudzeniu zapytań pod kątem ich wielkości i usunięciu wszystkich niepotrzebnych nagłówków. Wszak mniej danych do przesłania tym szybciej zostaną one przesłane.

Scenariuszy wykorzystania Turbo Intrudera w atakach jest wiele i może się on przydać także do szybkiego brutforcowania kodów OTP. Jeżeli chciałbyś przećwiczyć jego wykorzystania w scenariuszu race condition z uploadem plików możesz to zrobić w ćwiczeniu przygotowanym przez Portswiggera. Przez źle napisany kod serwer najpierw uploaduje tam plik, skanuje antywirusem i sprawdza rozszerzenie. Tworzy to okienko czasowe, które można wykorzystać do zdalnego wykonania kodu na serwerze – https://portswigger.net/web-security/file-upload/lab-file-upload-web-shell-upload-via-race-condition

Chcesz wiedzieć więcej?

Zapisz się i bądź informowany o nowych postach (zero spamu!).
Dodatkowo otrzymasz, moją prywatną listę 15 najbardziej przydatnych narzędzi (wraz z krótkim opisem), których używam przy testach penetracyjnych.

Nigdy nie podam, nie wymienię ani nie sprzedam Twojego adresu e-mail. W każdej chwili możesz zrezygnować z subskrypcji.

Tagi , , , , , , , , , , , .Dodaj do zakładek Link.

Podziel się swoją opinią na temat artykułu