Previsione dell'output di PHP's rand ()

19

Ho letto in numerose fonti che l'output di PHP's rand () è prevedibile come un PRNG, e per lo più lo accetto come fatto semplicemente perché l'ho visto in così tanti posti.

Mi interessa un proof-of-concept: come andrei a prevedere l'output di rand ()? Dalla lettura di questo articolo capisco che il numero casuale è un numero restituito da una lista che inizia con un puntatore (il seme) - ma non riesco a immaginare come sia prevedibile.

Qualcuno potrebbe ragionevolmente capire che cosa è stato generato casualmente tramite rand () in un dato momento nel giro di poche migliaia di ipotesi? o addirittura 10.000 ipotesi? Come?

Questo sta succedendo perché ho visto una libreria auth che utilizza rand () per produrre un token per gli utenti che hanno perso le password e ho pensato che si trattasse di un potenziale buco di sicurezza. Da allora ho sostituito il metodo con l'hashing di una miscela di openssl_random_pseudo_bytes() , la password con hash originale e il microtime. Dopo aver fatto ciò, mi sono reso conto che se fossi stato all'esterno, non avrei idea di come indovinare il token, pur sapendo che era un md5 di rand ().

    
posta Erik 13.05.2011 - 23:36
fonte

4 risposte

26

La possibilità di indovinare il valore successivo da rand è legata alla capacità di determinare con che cosa è stato chiamato srand . In particolare, seeding srand con un numero predeterminato determina un output prevedibile ! Dal prompt interattivo di 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 > 

Questo non è solo un colpo di fortuna. La maggior parte delle versioni PHP * sulla maggior parte delle piattaforme ** genererà la sequenza 97, 97, 39, 77, 93 quando srand 'd con 1024.

Per essere chiari, questo non è un problema con PHP, questo è un problema con l'implementazione di rand stesso. Lo stesso problema si presenta in altre lingue che utilizzano la stessa implementazione (o simile), incluso Perl.

Il trucco è che qualsiasi versione sana di PHP avrà una% di co_de pre-seeded con un valore "sconosciuto". Oh, ma non è veramente sconosciuto. Da srand :

#define GENERATE_SEED() (((long) (time(0) * getpid())) ^ ((long) (1000000.0 * php_combined_lcg(TSRMLS_C))))

Quindi, è un po 'di matematica con ext/standard/php_rand.h , il PID e il risultato di time() , che è definito in php_combined_lcg . Non ho intenzione di c & p qui, come, beh, i miei occhi si sono vetrificati e ho deciso di interrompere la caccia.

Un po 'di Google mostra che altre aree di PHP non hanno le migliori proprietà di generazione della casualità , e le chiamate a ext/standard/lcg.c si distinguono qui, specialmente questo bit di analisi:

Not only does this function (gettimeofday) hand us back a precise server timestamp on a silver platter, it also adds in LCG output if we request "more entropy" (from PHP's uniqid).

that php_combined_lcg . Sembra che il valore di uniqid sia quello che vediamo quando guardiamo le cifre esadecimali risultanti dopo aver chiamato php_combined_lcg con il secondo argomento impostato su un valore true.

Ora, dove eravamo?

Oh si. uniqid .

Quindi, se il codice che stai cercando di prevedere i valori casuali di non chiama srand , dovrai determinare il valore fornito da srand , che tu può ottenere (indirettamente?) attraverso una chiamata a php_combined_lcg . Con quel valore in mano, è fattibile a brute-forzare il resto del valore - uniqid , il PID e qualche matematica. Il problema legato alla sicurezza riguarda la rottura delle sessioni, ma la stessa tecnica funzionerebbe qui. Di nuovo, dall'articolo:

Here's a summary of the attack steps outlined above:
  • wait for the server to reboot
  • fetch a uniqid value
  • brute force the RNG seed from this
  • poll the online status to wait for target to appear
  • interleave status polls with uniqid polls to keep track of current server time and RNG value
  • brute force session ID against server using the time and RNG value interval established in polling

Sostituisci solo l'ultimo passaggio come richiesto.

(Questo problema di sicurezza è stato segnalato in una versione precedente di PHP (5.3.2) rispetto a quella corrente (5.3.6), quindi è possibile che il comportamento di time() e / o uniqid sia cambiato, quindi questo La tecnica specifica potrebbe non essere più utilizzabile. YMMV.)

D'altra parte, se il codice che stai provando per il prodotto chiama php_combined_lcg manualmente , allora a meno che non stiano usando qualcosa di molte volte migliore del risultato di srand , tu " Probabilmente avremo un più facile tempo a indovinare il valore e seminare il generatore locale con il numero giusto. La maggior parte delle persone che chiamerebbero manualmente php_combined_lcg non capirebbe quanto sia orribile un'idea, e quindi non è probabile che utilizzi valori migliori.

Vale la pena notare che srand è anche afflitto dallo stesso problema. Seminare mt_rand con un valore noto produrrà anche risultati prevedibili. Basare la tua entropia su mt_srand è probabilmente una scommessa più sicura.

tl; dr: per ottenere i migliori risultati, non seminare il generatore di numeri casuali PHP e, per l'amor del cielo, non esporre openssl_random_pseudo_bytes agli utenti. L'esecuzione di uno o entrambi può causare l'indecisione dei numeri casuali.

Aggiornamento per PHP 7:

PHP 7.0 introduce uniqid e random_bytes come funzioni principali. Usano l'implementazione CSPRNG del sistema sottostante, rendendoli liberi dai problemi che ha un generatore di numeri casuali seminato. Sono effettivamente simili a random_int , solo senza bisogno di un'estensione da installare. Un polyfill è disponibile per PHP5 .

*: la patch di sicurezza Suhosin modifica il comportamento di openssl_random_pseudo_bytes e rand in modo tale che -seed con ogni chiamata. Suhosin è fornito da una terza parte. Alcune distribuzioni Linux lo includono nei loro pacchetti ufficiali di PHP per impostazione predefinita, mentre altri lo rendono un'opzione, mentre altri lo ignorano del tutto.

**: A seconda della piattaforma e delle chiamate della libreria sottostante utilizzate, verranno generate sequenze diverse rispetto a quelle documentate qui, ma i risultati dovrebbero essere ancora ripetibili a meno che non venga utilizzata la patch Suhosin.

    
risposta data 14.05.2011 - 00:58
fonte
8

Per illustrare visivamente in che modo la funzione rand() non è casuale, ecco un'immagine in cui tutti i pixel sono fatti di valori "casuali" di rosso, verde e blu:

Normalmente non dovrebbe esserci alcun motivo nelle immagini.

Ho provato a chiamare srand() con valori diversi, non cambia la prevedibilità di questa funzione.

Si noti che entrambi non sono crittograficamente sicuri e producono risultati prevedibili.

    
risposta data 04.05.2015 - 09:56
fonte
7

the output of PHP's rand() is predictable as its a PRNG

È un generatore di congruenza lineare . Ciò significa che hai una funzione efficace: NEW_NUMBER = (A * OLD_NUMBER + B) MOD C . Se esegui il grafico NEW_NUMBER rispetto a OLD_NUMBER inizierai a vedere linee diagonali. Alcune delle note su documentazione RAND di PHP forniscono esempi su come farlo.

This is coming up because I saw a auth library which uses rand() to produce a token for users who have lost passwords, and I assumed this was a potential security hole.

Su una macchina Windows, il valore massimo di RAND è 2 ^ 15. Ciò fornisce all'aggressore solo 32.768 possibilità di controllo.

Could someone reasonably figure out what random # was generated via rand() at a given moment in time within a few thousand guesses? or even 10,000 guesses? How?

Mentre questo articolo non è esattamente quello che stai cercando, mostra come alcuni ricercatori hanno adottato un'implementazione di un generatore di numeri casuali e lo hanno usato per fare soldi su Texas Holdem. Ce ne sono 52! possibili mazzi mescolati, ma l'implementazione usava un generatore di numeri casuali a 32 bit (che è il numero massimo di mt_getrandmax su una macchina Windows) e lo seminò con il tempo in millisecondi da mezzanotte. Ciò ha ridotto il numero di possibili mazzi mescolati da circa 2 ^ 226 a circa 2 ^ 27, rendendo possibile la ricerca in tempo reale e sapere quale mazzo è stato distribuito.

After doing this I realized that if I were on the outside looking in, I'd have no idea how to guess the token even knowing it was a md5 of rand().

Consiglierei di usare qualcosa nella famiglia SHA-2 in quanto i federali considerano md5 non funzionante. Alcune persone usano google per decifrare gli hash MD5 perché sono così comuni. Basta fare un hash e poi lanciare l'hash in una ricerca su google - in pratica google è diventato una tavola dell'arcobaleno .

    
risposta data 14.05.2011 - 01:42
fonte
1

È davvero più accurato dire che, dato un numero generato casualmente, il prossimo è relativamente prevedibile. Ci sono solo tanti numeri che può essere. Ma questo non significa che tu possa indovinarlo, più che potresti scrivere un programma che lo fa, abbastanza rapidamente.

    
risposta data 14.05.2011 - 00:02
fonte

Leggi altre domande sui tag