La capacità di indovinare il valore successivo da rand
è legata alla capacità di determinare ciò che è srand
stato chiamato. In particolare, il seeding srand
con 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 srand
con 1024.
Per essere chiari, questo non è un problema con PHP, questo è un problema con l'implementazione di rand
se 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 srand
con 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_lcg
distinguersi 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
).
Sì quellouniqid
. Sembra che il valore di php_combined_lcg
sia ciò che vediamo quando osserviamo le cifre esadecimali risultanti dopo aver chiamato uniqid
con 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 uniqid
e / o php_combined_lcg
sia 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 srand
anche 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_srand
con 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 uniqid
agli utenti. Fare uno di questi o entrambi questi può rendere più indovinabili i numeri casuali.
Aggiornamento per PHP 7:
PHP 7.0 introduce random_bytes
e random_int
come 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 rand
e in mt_rand
modo 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.