Prevedere l'output di rand () di PHP


21

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

Sono interessato a una prova di concetto: come farei per prevedere l'output di rand ()? Dalla lettura di questo articolo ho capito che il numero casuale è un numero restituito da un elenco che inizia da un puntatore (il seme), ma non riesco a immaginare come sia prevedibile.

Qualcuno potrebbe ragionevolmente capire quale # casuale è stato generato tramite rand () in un dato momento nel tempo entro poche migliaia di ipotesi? o addirittura 10.000 ipotesi? Come?

Questo sta arrivando perché ho visto una libreria di autenticazione che utilizza rand () per produrre un token per gli utenti che hanno perso le password e ho pensato che fosse una potenziale falla nella sicurezza. Da allora ho sostituito il metodo con l'hashing di una miscela di openssl_random_pseudo_bytes(), la password hash originale e microtime. Dopo aver fatto questo mi sono reso conto che se fossi stato all'esterno a guardarmi dentro, non avrei idea di come indovinare il token anche sapendo che era un md5 di rand ().


"ma non riesco a immaginare come sia prevedibile"? Devi prima leggere " en.wikipedia.org/wiki/Linear_congruential_generator in modo da poter iniziare a immaginare come sia prevedibile. Quindi puoi rivedere la tua domanda per eliminare lo stupore e passare alle questioni più pratiche di reverse engineering del PHP sorgente della funzione rand per vedere come funziona
S.Lott

"Ho pensato che fosse una potenziale falla di sicurezza"? Solo se Evil Hacker è in grado di ottenere una password casuale di un utente, utilizzare una tabella arcobaleno per annullare l'hash MD5 per recuperare il valore originale (pre-hash) e quindi garantire che abbiano effettuato la richiesta di password successiva. Teoricamente possibile, suppongo. Ma solo se avessero un tavolo arcobaleno funzionante per un numero casuale.
S.Lott

@ S.Lott - non è una questione di password. Il sistema ti consente di reimpostare la password e ti invia un token che viene utilizzato in un URL. Il token viene generato tramite MD5 (rand ()). Se riesci a prevedere l'output di rand () potresti cambiare la password di chiunque, senza avere l'hash per l'originale o conoscere l'originale.
Erik,

@Erik. Giusto. Sostituisci "password casuale" con "token casuale" se questo aiuta. Il token può essere abusato solo se qualcuno può svolgere l'hash MD5 per recuperare il numero casuale E assicurarsi che otterrà il prossimo numero casuale. Prevedere il prossimo rand è solo una piccola parte. Annullare MD5 è la parte difficile.
S.Lott

1
Si noti che MD5 (rand ()) ha solo la stessa sicurezza di rand (). È pratico creare una tabella di ricerca di MD5 (rand ()) -> rand () per l'insieme di numeri molto limitato. Con il dominio limitato di rand () potresti provare una semplice forza bruta a meno che non ci sia un meccanismo in atto che impedisce ripetuti tentativi.
MZB

Risposte:


28

La capacità di indovinare il valore successivo da randè legata alla capacità di determinare ciò che è srandstato chiamato. In particolare, il seeding srandcon un numero predeterminato produce 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 di PHP * sulla maggior parte delle piattaforme ** genererà la sequenza 97, 97, 39, 77, 93 quando srandcon 1024.

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

Il trucco è che qualsiasi versione sana di PHP avrà pre-seeding srandcon un valore "sconosciuto". Oh, ma non è davvero sconosciuto. Da ext/standard/php_rand.h:

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

Quindi, è un po 'di matematica con time(), il PID e il risultato di php_combined_lcg, che è definito in ext/standard/lcg.c. Non ho intenzione di c & p qui, poiché, beh, i miei occhi si sono illuminati e ho deciso di smettere di cacciare.

Un po 'di googling mostra che altre aree di PHP non hanno le migliori proprietà di generazione di casualità e chiama a php_combined_lcgdistinguersi qui, in particolare questo po' di analisi:

Questa funzione ( gettimeofday) non solo ci restituisce un preciso timestamp del server su un piatto d'argento, ma aggiunge anche l'output LCG se richiediamo "più entropia" (da PHP uniqid).

quellouniqid . Sembra che il valore di php_combined_lcgsia ciò che vediamo quando osserviamo le cifre esadecimali risultanti dopo aver chiamato uniqidcon il secondo argomento impostato su un valore vero.

Ora dove eravamo rimasti?

Oh si. srand.

Quindi, se il codice dal quale stai tentando di prevedere valori casuali non chiama srand, dovrai determinare il valore fornito da php_combined_lcg, che puoi ottenere (indirettamente?) Tramite una chiamata a uniqid. Con quel valore in mano, è possibile forzare il resto del valore time(), il PID e un po 'di matematica. Il problema di sicurezza collegato riguarda l'interruzione delle sessioni, ma la stessa tecnica funzionerebbe qui. Ancora una volta, dall'articolo:

Ecco un riepilogo dei passaggi di attacco descritti sopra:
  • attendere il riavvio del server
  • recupera un valore uniqid
  • forza bruta il seme RNG da questo
  • eseguire il polling dello stato online per attendere la visualizzazione del target
  • interleave dei sondaggi di stato con i sondaggi uniqid per tenere traccia dell'ora corrente del server e del valore RNG
  • ID sessione di forza bruta contro il server utilizzando l'intervallo di tempo e valore RNG stabilito nel polling

Sostituisci l'ultimo passaggio come richiesto.

(Questo problema di sicurezza era stato segnalato in una versione precedente di PHP (5.3.2) di quella attuale (5.3.6), quindi è possibile che il comportamento uniqide / o php_combined_lcgsia cambiato, quindi questa specifica tecnica potrebbe non essere più utilizzabile. YMMV.)

D'altra parte, se il codice che stai provando a produrre manualmente chiamasrand , a meno che non stiano usando qualcosa molte volte meglio del risultato php_combined_lcg, probabilmente ti divertirai molto più facilmente a indovinare il valore e seminare il tuo locale generatore con il numero giusto. La maggior parte delle persone che chiamerebbero srandanche manualmente non si renderebbero conto di quanto sia orribile un'idea, e quindi è improbabile che utilizzino valori migliori.

Vale la pena notare che mt_randè anche affetto dallo stesso problema. Anche il seeding mt_srandcon un valore noto produrrà risultati prevedibili. Basare la tua entropia openssl_random_pseudo_bytesè probabilmente una scommessa più sicura.

tl; dr: per ottenere i migliori risultati, non eseguire il seeding del generatore di numeri casuali PHP e, per l'amor del cielo, non esporre uniqidagli utenti. Fare uno di questi o entrambi questi può rendere più indovinabili i numeri casuali.


Aggiornamento per PHP 7:

PHP 7.0 introduce random_bytese random_intcome funzioni principali. Usano l'implementazione CSPRNG del sistema sottostante, rendendoli liberi dai problemi che ha un generatore di numeri casuali con seeding. Sono effettivamente simili a openssl_random_pseudo_bytes, solo senza la necessità di installare un'estensione. È disponibile un polyfill per PHP5 .


*: La patch di sicurezza di Suhosin modifica il comportamento rande in mt_randmodo tale da ripetere il seeding ad ogni chiamata. Suhosin è fornito da una terza parte. Alcune distribuzioni Linux lo includono nei loro pacchetti PHP ufficiali per impostazione predefinita, mentre altri lo rendono un'opzione e altri lo ignorano del tutto.

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


Grazie Charles - tra la tua risposta e la lettura del link sul generatore di congruenza lineare di Tangurena sento di averne una migliore comprensione. Già "sapevo" che usare rand () in questo modo era una cattiva idea, ma so di sapere il perché .
Erik,

Wow, oggetti di scena per una risposta ben definita, grazie!
David Hobs,

10

Per illustrare visivamente quanto non casuale sia la rand()funzione, ecco un'immagine in cui tutti i pixel sono costituiti da valori "casuali" di rosso, verde e blu:

Valori RGB casuali

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.


7

l'output del rand () di PHP è prevedibile come un PRNG

È un generatore di congruenza lineare . Ciò significa che si ha una funzione che è effettivamente: NEW_NUMBER = (A * OLD_NUMBER + B) MOD C. Se traccia il grafico NEW_NUMBER vs OLD_NUMBER, inizierai a vedere linee diagonali. Alcune delle note sulla documentazione RAND di PHP forniscono esempi su come farlo.

Questo sta arrivando perché ho visto una libreria di autenticazione che utilizza rand () per produrre un token per gli utenti che hanno perso le password e ho pensato che fosse una potenziale falla nella sicurezza.

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

Qualcuno potrebbe ragionevolmente capire quale # casuale è stato generato tramite rand () in un dato momento nel tempo entro poche migliaia di ipotesi? o addirittura 10.000 ipotesi? Come?

Sebbene questo articolo non sia esattamente quello che stai cercando, mostra come alcuni ricercatori hanno adottato un'implementazione esistente di un generatore di numeri casuali e lo hanno utilizzato per fare soldi su Texas Holdem. Ce ne sono 52! possibili mazzi mischiati, ma l'implementazione ha usato un generatore di numeri casuali a 32 bit (che è il numero massimo di mt_getrandmax su una macchina Windows) e lo ha seminato con il tempo in millisecondi dalla 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.

Dopo aver fatto questo mi sono reso conto che se fossi stato all'esterno a guardarmi dentro, non avrei idea di come indovinare il token anche sapendo che era un md5 di rand ().

Consiglierei di usare qualcosa nella famiglia SHA-2 dato che i federali considerano md5 rotto. Alcune persone usano google per decrittografare gli hash md5 perché sono così comuni. Basta eseguire l'hashing di qualcosa, quindi lanciare l'hash in una ricerca su Google - fondamentalmente Google è diventato un gigantesco tavolo arcobaleno .


1

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


1
Penso che il prossimo numero sia del tutto deterministico. Non "relativamente" ma assolutamente. Il problema con i generatori di numeri pseudo-casuali è che una sequenza passerà test statistici. Due numeri adiacenti, sebbene totalmente deterministici, avranno proprietà statistiche comuni in comune con numeri casuali effettivi.
S.Lott

1
Il prossimo numero è del tutto deterministico. Questo è ciò che significa "pseudo" nel generatore di numeri pseudo-casuali. D'altra parte, le informazioni necessarie per determinare che il prossimo numero è quasi impossibile da acquisire in pratica.
Rein Henrichs,

@ S.Lott - Avevo l'impressione che un numero potesse apparire più volte nelle 2 ^ 32 possibili uscite e che ogni volta che apparisse potesse essere seguito da un numero diverso. Ma dato un seme di X, restituendo un risultato di Y, il risultato successivo sarà sempre lo stesso. Quindi, in pratica, potrebbero esserci una manciata di numeri che seguono Y. Tuttavia, potrei sbagliarmi; è da molto tempo che non guardo davvero ai PRNG.
pdr,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.