Co z tego, że Secure?

Dość typowym sposobem realizacji funkcji resetu zapomnianego hasła jest generowanie losowego tokenu, który następnie wysyłany jest na zapisany w systemie adres e-mail. Korzystając z tego tokenu, można ustawić nowe hasło. O tym, że sposób ten nie jest najlepszy można przeczytać tutaj: OWASP Forgot Password Cheat Sheet i posłuchać tu: OWASP Podcast #83. Ale ja dzisiaj o trochę czym innym.

Jeśli sposób resetu hasła opiera się wyłącznie na losowym tokenie, to odgadnięcie tego tokenu daje możliwość zmiany hasła. A teraz zagadka, co jest nie tak z tym fragmentem kodu:

(...) SecureRandom r = new SecureRandom(); r.setSeed(System.currentTimeMillis()); r.nextBytes(buff); (...)

Posłużę się Jythonem:

>>> i = java.lang.System.currentTimeMillis() >>> s = java.security.SecureRandom() >>> s.setSeed(i) >>> s.nextInt() -2061508703 >>> s.nextInt() 421566032 >>> s.nextInt() -1127454972 >>>

I drugi fragment:

>>> r = java.security.SecureRandom() >>> r.setSeed(i) >>> r.nextInt() -2061508703 >>> r.nextInt() 421566032

Co jest nie tak? To, że te generatory zwracają taki sam ciąg wartości, a to dlatego, że zostały zainicjowane tym samym seedem. Nie jest to tajemnica, można stosowną informację znaleźć w dokumentacji:

The returned SecureRandom object has not been seeded. To seed the returned object, call the setSeed method. If setSeed is not called, the first call to nextBytes will force the SecureRandom object to seed itself. This self-seeding will not occur if setSeed was previously called.

Niestety, dalej jest gorzej:

public void setSeed(long seed)

Reseeds this random object, using the eight bytes contained in the given long seed. The given seed supplements, rather than replaces, the existing seed. Thus, repeated calls are guaranteed never to reduce randomness.

Coś tu się nie zgadza? Tak, jedna drobna rzecz – przypadek, kiedy funkcja setSeed jest wołana przed pierwszym “użyciem” generatora. Skutki widoczne na załączonym (wyżej obrazku) – generowany jest powtarzalny ciąg wartości.

A jakie skutki ma to w przypadku resetowania hasła? Wystarczy:

Ilość dostępnych prób jest często nieograniczona, jedynym ograniczeniem jest czas, przez który token jest ważny. W praktyce jednak nie jest to żadne utrudnienie dla atakującego, a więc dysponuje on metodą zmiany hasła dowolnego użytkownika.

Najdziwniejszym wykorzystaniem tego specyficznego zachowania SecureRandom, które widziałem, było (celowo powtarzalne) generowanie kluczy kryptograficznych w oparciu o wyjście SecureRandom inicjowane przez PIN użytkownika.

W całej historii jest tylko jedna pułapka – na różnych platformach zwracane wartości mogą różnić się między sobą. To znaczy ciąg generowany przez SecureRandom zainicjowany w ten sam sposób na Android i, przykładowo, na Windows, będzie (prawdopodobnie) różny.

Wniosek? Słowo Secure w nazwie nie zwalnia od myślenia...

I w temacie: Random number generation: An illustrated primer.

Oryginał tego wpisu dostępny jest pod adresem Co z tego, że Secure?

Autor: Paweł Goleń