04-10-2018 / Od 0 do pentestera

Random vs SecureRandom

Każdy serwis, który posiada mechanizm logowania powinien również posiadać opcję przywracania hasła.

Ale jak zrobić to dobrze?

Funkcjonalność resetowanie hasła działa zazwyczaj mniej więcej tak.

Użytkownik podaje na stronie swój adres email powiązany z kontem.

W tym momencie serwer sprawdza czy taki użytkownik istnieje w bazie danych.

Jeśli tak - generuje unikalny ciąg, który jest następnie zapisywany i wysyłany w emailu.

Następnie użytkownik odczytuje email i klika w zawarty tam odnośnik z zapisanym unikalnym kluczem.

Serwer sprawdza czy unikalny ciąg znajduje się w bazie danych i jeśli wszystko się zgadza pozwala na zmianę hasła.

Jak zatem wygenerować ten unikalny ciąg znaków?

Do głowy może nam przyjść użycie funkcji Random1 która pozwala na zwracanie unikalnych ciągów liczb.

Kod takiej funkcji może zatem wyglądać następująco.

package com.company;
import java.util.Random;
public class Main {
    static Random r = new Random();
    static String getToken() {
        return Integer.toString(r.nextInt());
    }
    public static void main(String[] args) {
        System.out.println(getToken());
        System.out.println(getToken());
        System.out.println(getToken());
    }
}

Za każdym razem, gdy ktoś chce zresetować swoje hasło wywoływana jest funkcja getToken(), której wynik jest następnie zapisywany do bazy danych.

Gdzie zatem znajduje się dzisiejszy błąd?

Klasa random jest generatorem liczb pseudolosowych.

Oznacza to że na podstawie niewielkiej ilości informacji - tak zwanego ziarna (z angielskiego seed)

generuje ona deterministycznie kolejne liczby pseudolosowe.

Humorystyczne przedstawienie funkcji random
https://xkcd.com/221/

Ziarno to może być zdefiniowane przez użytkownika - lub też jak w naszym przypadku ustawione automatycznie przez Jave.

Wystarczy więc odgadnąć jakie ziarno zostało użyte aby móc generować kolejne liczby tokena na własnym komputerze.

Do odgadnięcia ziarna użyjemy kodu dostępnego na forum Stack Overflow2.

Odzyskiwanie ziarna

Do swojego działania potrzebuje on 2 kolejnych liczb wygenerowanych przez daną instancje klasy random.

Jak je otrzymać? W przypadku funkcjonalności przypominania hasła jest to bardzo proste.

Wystarczy bowiem dwa razy poprosić o reset konta, do którego posiadamy uprawnienia a za trzecim razem spróbować zresetować konto admina.

Następnie odczytujemy 2 pierwsze wartości z maila, do którego dostęp posiadamy.

Ja zasymuluje tą funkcjonalność używając 3 razy funkcji GetToken.

Wprowadzę teraz 2 pierwsze wartości do klasy odzyskującej ziarno.

Odzyskiwanie ziarna w praktyce

Jak widać po kilku sekundach otrzymaliśmy potencjalną wartość ziarna jak również kolejną liczbę pseudolosową.

W naszym wypadku zgadza się ona z tą, którą otrzymaliśmy po 3 wywołaniu funkcji gettoken.

W rzeczywistości byłby to zatem unikalny identyfikator, którego można by było użyć do resetowania hasła administratora.

Oczywiście zakładamy tutaj, że mamy szczęście i pomiędzy naszymi próbami nikt inny nie wykonał tej samej akcji.

Jak zatem zabezpieczyć się przed tym problemem?

Zawsze używać klasy SecureRandom3 ponieważ wartości zwracanych przez tą klasę nie można przewidzieć.

SecureRandom

Identyczny problem istnieje również z klasą RandomStringUtils

Tam jednak odzyskanie ziarna trwa nieco dłużej.

RandomStringUtils
https://github.com/alex91ar/randomstringutils/blob/master/The%20Java%20soothsayer.pdf

Icon made by Freepik from www.flaticon.com