Możliwość odgadnięcia kolejnej wartości rand
wiąże się z możliwością ustalenia, z czym srand
została wywołana. W szczególności wysiew srand
z ustaloną liczbą daje przewidywalną wydajność ! Z interaktywnego monitu PHP:
[charles@charles-workstation ~]$ php -a
Interactive shell
php > srand(1024);
php > echo rand(1, 100);
97
php > echo rand(1, 100);
97
php > echo rand(1, 100);
39
php > echo rand(1, 100);
77
php > echo rand(1, 100);
93
php > srand(1024);
php > echo rand(1, 100);
97
php > echo rand(1, 100);
97
php > echo rand(1, 100);
39
php > echo rand(1, 100);
77
php > echo rand(1, 100);
93
php >
To nie tylko fuks. Większość wersji PHP * na większości platform ** generuje sekwencję 97, 97, 39, 77, 93, gdy srand
jest z 1024.
Żeby było jasne, nie jest to problem z PHP, to jest problem z rand
samą implementacją . Ten sam problem występuje w innych językach, które używają tej samej (lub podobnej) implementacji, w tym Perl.
Sztuczka polega na tym, że każda rozsądna wersja PHP będzie zawierała wstępnie srand
„nieznaną” wartość. Och, ale tak naprawdę nie jest to nieznane. Od ext/standard/php_rand.h
:
#define GENERATE_SEED() (((long) (time(0) * getpid())) ^ ((long) (1000000.0 * php_combined_lcg(TSRMLS_C))))
Jest to więc matematyka z time()
PID i wynikiem php_combined_lcg
, który jest zdefiniowany w ext/standard/lcg.c
. Nie zamierzam tu c & p, bo oczy mi się błyszczą i postanowiłem przestać polować.
Trochę Googling pokazuje, że inne obszary PHP nie mają najlepszych właściwości generowania losowości i wzywa do php_combined_lcg
wyróżnienia się tutaj, szczególnie ten fragment analizy:
Ta funkcja ( gettimeofday
) nie tylko przekazuje nam dokładny znacznik czasu serwera na srebrnym talerzu, ale także dodaje dane wyjściowe LCG, jeśli poprosimy o „więcej entropii” (z PHP uniqid
).
Tak żeuniqid
. Wydaje się, że wartość php_combined_lcg
jest tym, co widzimy, gdy patrzymy na wynikowe cyfry szesnastkowe po wywołaniu uniqid
z drugim argumentem ustawionym na wartość prawdziwą.
Gdzie teraz byliśmy?
O tak. srand
.
Tak więc, jeśli kod, z którego próbujesz przewidzieć losowe wartości , nie wywołuje srand
, musisz określić wartość podaną przez php_combined_lcg
, którą możesz (pośrednio?) Uzyskać poprzez wywołanie uniqid
. Mając tę wartość w ręku, możliwe jest brutalne wymuszenie reszty wartości - time()
PID i pewnej matematyki. Powiązany problem bezpieczeństwa dotyczy przerywania sesji, ale ta sama technika działałaby tutaj. Ponownie z artykułu:
Oto podsumowanie kroków ataku opisanych powyżej:
- poczekaj, aż serwer się zrestartuje
- pobierz wartość uniqid
- brutalna siła nasion RNG z tego
- odpytuje status online, aby poczekać na pojawienie się celu
- przeplataj ankiety stanu z ankietami uniqid, aby śledzić aktualny czas serwera i wartość RNG
- identyfikator sesji brute force przeciwko serwerowi przy użyciu czasu i przedziału wartości RNG ustalonego podczas odpytywania
Wystarczy wymienić ostatni krok zgodnie z wymaganiami.
(Ten problem bezpieczeństwa został zgłoszony we wcześniejszej wersji PHP (5.3.2) niż obecnie (5.3.6), więc możliwe jest, że zachowanie uniqid
i / lub php_combined_lcg
uległo zmianie, więc ta konkretna technika może już nie być wykonalna. YMMV.)
Z drugiej strony, jeśli kod, który próbujesz wytworzyć, wywołuje srand
ręcznie , to chyba że używają czegoś wielokrotnie lepszego niż wynik php_combined_lcg
, prawdopodobnie łatwiej będzie ci odgadnąć wartość i zainicjować lokalne generator z odpowiednią liczbą. Większość osób, które dzwoniłyby ręcznie, srand
również nie zdawały sobie sprawy z tego, jak okropny jest to pomysł, a zatem prawdopodobnie nie zastosują lepszych wartości.
Warto zauważyć, że mt_rand
ten sam problem dotyczy również tego samego problemu. Wysiew mt_srand
o znanej wartości da również przewidywalne wyniki. Oparcie się na entropii openssl_random_pseudo_bytes
jest prawdopodobnie bezpieczniejszym zakładem.
tl; dr: Aby uzyskać najlepsze wyniki, nie uruchamiaj generatora liczb losowych PHP, a na miłość boską, nie narażaj uniqid
użytkowników. Wykonanie jednego lub obu z nich może sprawić, że twoje losowe liczby będą bardziej zgadywalne.
Aktualizacja dla PHP 7:
PHP 7.0 wprowadza random_bytes
i random_int
jako podstawowe funkcje. Korzystają z implementacji CSPRNG systemu bazowego, dzięki czemu są wolni od problemów, jakie ma zalążkowy generator liczb losowych. Są skutecznie podobne openssl_random_pseudo_bytes
, ale nie wymagają instalowania rozszerzenia. Polifill jest dostępny dla PHP5 .
*: Poprawka bezpieczeństwa Suhosin zmienia zachowanie rand
i powoduje mt_rand
, że zawsze są one ponownie uruchamiane przy każdym wywołaniu. Suhosin jest dostarczany przez stronę trzecią. Niektóre dystrybucje Linuksa domyślnie włączają go do swoich oficjalnych pakietów PHP, podczas gdy inne udostępniają go jako opcję, a inne całkowicie go ignorują.
**: W zależności od platformy i używanych wywołań biblioteki, zostaną wygenerowane inne sekwencje niż tutaj udokumentowane, ale wyniki powinny być powtarzalne, chyba że zostanie użyta łatka Suhosin.