Php slučajna vrijednost. Nasumični odabir iz niza u PHP-u. U potrazi za entropijom

  • Iz:
  • Registriran: 2014.07.07
  • Postovi: 3,775
  • Samo volim PunBB:
  • 5 godina, 6 mjeseci,
  • Sviđa mi se: 463

Tema: Kako odabrati slučajnu vrijednost iz niza u PHP-u

Pomoću ove funkcije možemo odabrati slučajni element (ili elemente) niza. Da, upravo element ili elementi! To može biti jedan element ili ih može biti nekoliko. Sve ovisi o zadatku s kojim se suočavate.

No, ovdje treba uzeti u obzir da funkcija neće vratiti vrijednost elementa, već njegov ključ (ili ključeve, ako ima više elemenata).

Funkcija kao parametre u zagradama uzima: naziv niza s kojim radimo i broj elemenata koje je potrebno odabrati.

Općenito, sve je jednostavno! A bit će još lakše kad sve to pogledamo na primjerima.

Najprije odaberimo jedan slučajni element iz niza.

2 Odgovori od PunBB

  • Iz: Moskva, Sovkhoznay 3, apt. 98
  • Registriran: 2014.07.07
  • Postovi: 3,775
  • Samo volim PunBB:
  • 5 godina, 6 mjeseci,
  • Sviđa mi se: 463

Zamislimo da negdje na vrhu naše web stranice želimo prikazati neke citate. Naravno, navodnici se moraju promijeniti. Svaki put kada korisnik posjeti vašu stranicu, želite da korisnik vidi novi citat.

Kao što ste vjerojatno pogodili, najlakši način da to implementirate je smjestiti sve dostupne citate i izreke u niz, a zatim odabrati nasumični element iz tog niza i prikazati ga na ekranu.

Što više citata imate u nizu, manja je vjerojatnost da će se ponoviti.

Ali primjera radi, neću se previše zamarati i stavit ću 7 izreka u svoj niz.

Zatim ću morati stvoriti varijablu u koju ću pohraniti rezultat funkcije array_rand(). U zagradama će ova funkcija imati dva argumenta: naziv našeg niza i broj nasumičnih elemenata koji su nam potrebni.

Kao što sam već rekao, funkcija ne vraća vrijednost elementa, već njegov ključ (ili broj na listi). Na taj način će ključ slučajnog elementa biti pohranjen u varijabli.

Nakon toga samo trebam prikazati vrijednost željenog elementa. Da bih to učinio, označavam naziv niza i u uglatim zagradama naziv naše varijable koja sadrži slučajni ključ.

to je sve Pogledajte kod ispod i mislim da ćete sve u potpunosti razumjeti:

". $frases[$rand_frases] .""; ?>

3 Odgovori od PunBB

  • Iz: Moskva, Sovkhoznay 3, apt. 98
  • Registriran: 2014.07.07
  • Postovi: 3,775
  • Samo volim PunBB:
  • 5 godina, 6 mjeseci,
  • Sviđa mi se: 463

Re: Kako odabrati slučajnu vrijednost iz niza u PHP-u

Sada vježbajmo ispisivanje nekoliko nasumičnih elemenata niza.

U slučaju jednog elementa, vraća se njegov ključ, a u slučaju više elemenata slučajnog niza, vraća se niz ključeva. Od toga ćemo krenuti pri prikazivanju na ekranu.

Prvo, napravimo niz u koji ćemo dodati 7 različitih imena.

Zatim kreiramo varijablu u kojoj će se bilježiti rad funkcije array_rand(). Samo sada u zagradama za ovu funkciju označavamo broj "2" kao drugi argument. To će značiti da su nam potrebna 2 nasumična elementa.

Rezultat funkcije u ovoj situaciji bit će niz koji sadrži dva nasumična ključa elemenata iz našeg glavnog niza.

Stoga, prilikom prikaza na ekranu, morate to uzeti u obzir i u uglatim zagradama navesti ne samo naziv varijable, već i naziv varijable, zatim uglate zagrade i indeks polja. Kako imamo 2 elementa, u prvom slučaju će indeks biti , a u drugom . (Sjećate se da indeksiranje u nizovima počinje od "0".)

to je sve Pogledajte kod kako bi stvari bile jasnije:

$names = array("Masha", "Sasha", "Nadya", "Mila", "Andrey", "Sergey", "Anton"); $rand_names = array_rand($names,2); odjek "

".$names[$rand_names]." i ".$names[$rand_names]."

";

Kao rezultat toga, na zaslonu će se prikazati dva nasumična imena. Svaki put kad se stranica osvježi, imena će se promijeniti.

Izvor

rand(1,N) ali isključujući array(a,b,c,..)

postoji li već ugrađena funkcija koju ne poznajem ili je moram sam implementirati (kako?)?

AŽURIRATI

Kvalificirano rješenje mora imati zlato, bez obzira na to koliko je velika ili mala veličina isključenog niza.

Ne postoji ugrađena funkcija, ali vi možete učini ovo:

Funkcija randWithout($from, $to, array $exceptions) ( sort($exceptions); // omogućuje nam korištenje break; u foreach pouzdano $number = rand($from, $to - count($exceptions)); / / ili mt_rand() foreach ($exceptions as $exception) ( if ($number >= $exception) ( $number++; // nadoknaditi prazninu ) else /*if ($number< $exception)*/ { break; } } return $number; }

Nije u mojoj glavi, pa bi trebalo poliranje - ali barem ne možete završiti u scenariju beskonačne petlje, čak ni hipotetski.

Bilješka: Funkcija se prekida ako $iznimke ispusi vaš raspon - na primjer pozivanje randWithout(1, 2, array(1,2)) ili randWithout(1, 2, array(0,1,2,3)) neće dati ništa razumno (očito), ali u ovom slučaju vraćeni broj bit će izvan raspona $from – $to, tako da ga je lako uhvatiti.

Ako je zajamčeno da je $exceptions već sortirano, sort($exceptions); može se izbrisati.

Slatkiš za oči: donekle minimalna vizualizacija algoritma.

Mislim da ne postoji takva ugrađena funkcija; vjerojatno ćete ga morati kodirati sami.

Za kodiranje ovoga imate dva rješenja:

  • Koristite petlju za pozivanje rand() ili mt_rand() dok ne vrati točnu vrijednost
    • što znači pozivanje rand() više puta, najgori mogući scenarij
    • ali ovo bi trebalo dobro funkcionirati ako je N velik i nemate mnogo zabranjenih vrijednosti.
  • Napravite niz koji sadrži samo dozvoljene vrijednosti
    • I koristiti niz_rand odabrati jednu vrijednost iz njega
    • koji će dobro funkcionirati ako je N mali

Ovisno o tome što trebate i zašto, ovaj bi pristup mogao biti zanimljiva alternativa.

$brojevi = array_diff(range(1, N), array(a, b, c)); // Bilo (nije pravi odgovor, ali bi mogao biti koristan, ovisno o vašim okolnostima) shuffle($numbers); // $numbers je sada nasumično sortirano polje koje sadrži sve brojeve koji vas zanimaju // Ili: $x = $numbers; // $x je sada nasumični broj odabran iz skupa brojeva koji vas zanimaju

Dakle, ako ne trebate generirati skup potencijalnih brojeva svaki put, već generirajte skup jednom i zatim odaberite hrpu nasumičnih brojeva iz istog skupa, ovo bi mogao biti dobar način.

Najlakši način...

Morate izračunati niz nedostajućih lokacija tako da možete odabrati slučajni položaj u kontinuiranom nizu duljine M = N - #iznimaka i jednostavno ga preslikati natrag na izvorni niz s rupama. Ovo će zahtijevati vrijeme i prostor jednako proslijeđenom nizu. Ne poznajem php kao rupu u zemlji, pa oprostite na primjeru koda polu-psudo teksta.

  1. Napravite novi niz Offset iste duljine kao niz Exceptions.
  2. u Offset[i] će pohraniti prvi indeks u zamišljeni neprazan niz, koji bi propustio i elemenata u originalnom nizu.
  3. Sada odaberite slučajnu stavku. Odaberite slučajni broj, r, do 0..M broja preostalih elemenata.
  4. Pronađite taj pomak[i]<= r < Offest это легко с бинарным поиском
  5. Povratak r+i

Sada, ovo je samo skica, morat ćete se pozabaviti krajevima nizova i ako je nešto indeksirano u obliku 0 ili 1 i sve te frke. Ako ste pametni, zapravo možete izračunati polje Offset u hodu iz izvornika, ali to je malo manje jasno.

Možda je prekasno za odgovor, ali pronašao sam ovaj dio koda negdje u svom umu dok sam pokušavao dobiti nasumične podatke iz baze podataka na temelju nasumičnog ID-a osim nekog broja.

$isključeniPodaci = polje(); // Ovo je vaš isključeni broj $maxVal = $this->db->count_all_results("game_pertanyaan"); // Dohvati najveći broj na temelju moje baze podataka $randomNum = rand(1, $maxVal); // Napravite prvu inicijaciju, mislim da ovo možete staviti izravno u parametar while > in_array, čini se da također radi, ovisi o vama while (in_array($randomNum, $excludedData)) ( $randomNum = rand(1, $maxVal); $randomNum; //Vaš slučajni broj isključujući neki broj koji odaberete

Nasumični brojevi su sastavni dio programiranja, posebno kada su u pitanju sigurnosni sustavi. Na primjer, kriptografija se oslanja na generiranje nasumičnih vrijednosti za proizvodnju brojeva koji se ne mogu predvidjeti. Naravno, u PHP-u nasumični brojevi igraju veliku ulogu: uz pomoć njih možemo generirati žetone, soli i druge vrijednosti.

Generiranje slučajnih brojeva temelji se na posebnim algoritmima. Ulazni parametar od kojeg će algoritam krenuti može biti ili slučajna vrijednost ili unaprijed određena vrijednost.

U ovom ćemo članku govoriti o nasumičnim brojevima: kako se generiraju i gdje se mogu koristiti.

Korištenje slučajnih brojeva

U PHP-u nasumični brojevi igraju veliku ulogu jer... vrlo često koristi u razne svrhe. Uglavnom se odnose na sigurnost. Na temelju njih generiraju se CSRF tokeni, API ključevi, autentifikacijske vrijednosti, vrijednosti za ponovno postavljanje lozinki i još mnogo toga. Sve je to učinjeno tako da je rezultatsku vrijednost nemoguće predvidjeti.

Najvažniji primjeri korištenja slučajnih vrijednosti su:

  • Generiranje soli za kriptografiju- nasumični broj soli koristi se, u pravilu, za jednosmjernu enkripciju, kao i za raspršivanje lozinki. Ova slučajna vrijednost se koristi kao inicijalizacijski vektor u kriptografiji.
  • Generirajte nasumične vrijednosti kao što je ID sesije- PHP se koristi za izradu ogromnog broja aplikacija gdje je sigurnost na prvom mjestu. Velik dio funkcionalnosti temelji se na radu sa sesijama i generiranim ID-ovima sesija.
  • Generiranje autentifikacijskih tokena koje je gotovo nemoguće predvidjeti- mnoge PHP aplikacije temelje se na radu s drugim sustavima putem posebnih API sučelja. Obično morate proći kroz postupak provjere autentičnosti prije korištenja API-ja. Vrlo je teško doći do vrijednosti za tokene koje je teško pronaći. Zato se u ovim problemima koriste slučajni brojevi.

Generatori slučajnih brojeva

Nasumične brojeve koji se koriste u gore opisanim slučajevima generiraju pseudogeneratori u PHP-u. Dostupno je nekoliko algoritama:

    Linearna kongruentna metoda, kada se koristi funkcija lcg_value().

    Mersenneov vrtlog, koji koristi funkcija mt_rand().

    Funkcija rand() koristi sličnu funkciju u jeziku C.

Zapravo, ove funkcije ne vraćaju nasumične brojeve, već brojeve raspoređene na takav način da izgledaju nasumično. Redoslijed ovih brojeva ovisi o temeljnom slučajnom broju unutar implementiranog algoritma.

Osnovni brojevi za generatore

Osnovni brojevi ili vektori osnovnih brojeva su skupovi podataka koji se koriste za generiranje nasumičnih brojeva. Generatori pseudoslučajnih brojeva rade samo ako počnu od njih. Ako napadač zna ovaj osnovni broj, tada će u budućnosti moći predvidjeti vrijednosti vaših nasumičnih brojeva.

U PHP-u možete odrediti osnovne brojeve na dva načina. Prvi je korištenje funkcije mt_srand(). Ova se metoda uglavnom koristi u jediničnim testovima nasumičnih serija. Drugi način je pustiti PHP da sam generira osnovne brojeve. Od verzije 4.2 PHP nudi ovu značajku. Nakon toga će se Mersenneov vrtlog koristiti za generiranje nasumičnih brojeva.

PHP generira osnovni broj, ovisno o operativnom sustavu. Na Linux platformama možete koristiti funkcije mcrypt_create_iv() ili openssl_pseudo_random_bytes() u /dev/urandom. Windows nudi poseban pseudogenerator kojem se može pristupiti putem funkcija openssl_pseudo_random_bytes() i mcrypt_create_iv().

Zaključak

Budući da nasumični brojevi igraju veliku ulogu u izgradnji sigurnih web aplikacija, moramo znati više o tome kako oni funkcioniraju. Ako želite sami generirati osnovne brojeve za pseudogeneratore, provjerite jesu li vaše metode pouzdane.


Nasumične vrijednosti su posvuda u PHP-u. U svim okvirima, u mnogim knjižnicama. Vjerojatno ste sami napisali gomilu koda koji koristi nasumične vrijednosti za generiranje tokena i soli te kao ulaz u funkcije. Također, slučajne vrijednosti igraju važnu ulogu u rješavanju raznih problema:

  1. Za nasumični odabir opcija iz grupe ili niza poznatih opcija.
  2. Za generiranje vektora inicijalizacije tijekom enkripcije.
  3. Za generiranje nepredvidivih tokena ili jednokratnih vrijednosti tijekom autorizacije.
  4. Za generiranje jedinstvenih identifikatora, kao što su ID-ovi sesije.

U svim tim slučajevima postoji karakteristična ranjivost. Ako napadač pogodi ili predvidi izlaz vašeg Generatora slučajnih brojeva (RNG) ili Generatora pseudo-slučajnih brojeva (PRNG), moći će izračunati tokene, soli, jednokratne brojeve i vektore kriptografske inicijalizacije koje stvara ovaj generator. Stoga je vrlo važno generirati kvalitetne slučajne vrijednosti, tj. one koje je izuzetno teško predvidjeti. Tokene za poništavanje lozinke, CSRF tokene, API ključeve, nonce ili autorizacijske tokene nikada nemojte učiniti predvidljivima!


Postoje još dvije potencijalne ranjivosti povezane s nasumičnim vrijednostima u PHP-u:

  1. Otkrivanje informacija.
  2. Nedovoljna entropija.

U ovom kontekstu, "otkrivanje informacija" odnosi se na curenje unutarnjeg stanja generatora pseudoslučajnih brojeva - njegove početne vrijednosti. Ovakva curenja mogu znatno olakšati predviđanje budućih PRNG izlaza.


"Nedostatak entropije" opisuje situaciju u kojoj je varijabilnost početnog unutarnjeg stanja (seed) PRNG-a ili njegovog izlaza toliko mala da je cijeli raspon mogućih vrijednosti relativno lako primijeniti brutalnom silom. Nisu baš dobre vijesti za PHP programere.


Detaljno ćemo pogledati obje ranjivosti s primjerima scenarija napada. Ali prvo, shvatimo što je zapravo slučajna vrijednost kada je u pitanju PHP programiranje.

Što rade slučajne vrijednosti?

Zbunjenost oko svrhe slučajnih varijabli pojačana je općim nesporazumom. Nema sumnje da ste čuli za razliku između kriptografski jakih nasumičnih vrijednosti i nejasnih "jedinstvenih" vrijednosti "za druge svrhe." Glavni dojam je da slučajne vrijednosti koje se koriste u kriptografiji zahtijevaju visoku kvalitetu slučajnosti (točnije, visoku entropiju), dok vrijednosti za druge aplikacije mogu proći s manjom entropijom. Taj dojam smatram lažnim i kontraproduktivnim. Prava razlika između nepredvidivih slučajnih vrijednosti i onih potrebnih za trivijalne zadatke je u tome što predvidljivost potonjih ne povlači za sobom štetne posljedice. Ovo u potpunosti isključuje kriptografiju iz razmatranja. Drugim riječima, ako koristite slučajnu vrijednost u netrivijalnom problemu, trebali biste automatski odabrati mnogo jače RNG-ove.


Snaga slučajnih vrijednosti određena je entropijom utrošenom za njihovo generiranje. Entropija je mjera nesigurnosti izražena u "bitovima". Na primjer, ako uzmem binarni bit, njegova bi vrijednost mogla biti 0 ili 1. Ako napadač ne zna točnu vrijednost, tada imamo entropiju od 2 bita (tj. bacanje novčića). Ako napadač zna da je vrijednost uvijek 1, tada imamo entropiju od 0 bita, budući da je predvidljivost antonim neizvjesnosti. Također, broj bitova može biti u rasponu od 0 do 2. Na primjer, ako je 99% vremena binarni bit 1, tada entropija može biti nešto veća od 0. Dakle, što više nedefiniranih binarnih bitova odaberemo, to bolje.


U PHP-u se to može jasnije vidjeti. Funkcija mt_rand() generira slučajne vrijednosti, to su uvijek brojevi. Ne prikazuje slova, posebne znakove ili druga značenja. To znači da za svaki bajt napadač ima mnogo manje pogađanja, tj. nisku entropiju. Ako zamijenimo mt_rand() čitanjem bajtova iz Linux izvora /dev/random, dobivamo stvarno nasumične bajtove: generiraju se na temelju buke koju generiraju upravljački programi sustava i drugi izvori. Očito je da je ova opcija puno bolja jer daje znatno više bitova entropije.


O nepoželjnosti mt_rand() govori i činjenica da on nije generator istinski slučajnih, već pseudoslučajnih brojeva ili, kako se još naziva, deterministički generator slučajnih binarnih nizova (Deterministic Random Bit Generator, DRBG ). Implementira algoritam nazvan Mersenne Twister, koji generira brojeve raspoređene na takav način da je rezultat blizak rezultatu pravog generatora slučajnih brojeva. mt_rand() koristi samo jednu slučajnu vrijednost - početnu (seed), na temelju nje fiksni algoritam generira pseudo-slučajne vrijednosti.


Pogledajte ovaj primjer, možete sami isprobati:


mt_srand(1361152757.2); za ($i=1; $i< 25; $i++) { echo mt_rand(), PHP_EOL; }

Ovo je jednostavna petlja koja se izvodi nakon što PHP Mersenneova vrtložna funkcija dobije početnu, unaprijed postavljenu vrijednost. Dobiven je iz izlaza funkcije dane kao primjer u dokumentaciji za mt_srand() i korištenjem trenutnih sekundi i mikrosekundi. Ako pokrenete gornji kod, on će prikazati 25 pseudo-slučajnih brojeva. Izgledaju nasumično, nema slučajnosti, sve je u redu. Pokrenimo kod ponovo. Jeste li što primijetili? Naime: prikazuju se ISTI brojevi. Pustimo ga treći, četvrti, peti put. U starijim verzijama PHP-a rezultat može biti drugačiji, ali to nije problem jer je zajednički svim modernim verzijama PHP-a.


Ako napadač dobije početnu vrijednost takvog PRNG-a, tada će moći predvidjeti cijeli izlaz mt_rand() . Stoga je zaštita početne vrijednosti od najveće važnosti. Ako ga izgubite, više nemate pravo generirati slučajne vrijednosti...


Početnu vrijednost možete generirati na jedan od dva načina:

  • ručno, pomoću funkcije mt_srand(),
  • zanemarit ćete mt_srand() i pustiti PHP da ga automatski generira.

Druga opcija je poželjnija, ali čak i danas naslijeđene aplikacije često nasljeđuju upotrebu mt_srand(), čak i nakon prijenosa na modernije verzije PHP-a.


To povećava rizik da će napadač vratiti početnu vrijednost (Seed Recovery Attack), što će mu dati dovoljno informacija za predviđanje budućih vrijednosti. Kao rezultat toga, svaka aplikacija nakon takvog curenja postaje ranjiva na napad otkrivanjem informacija. To je stvarna ranjivost, usprkos svojoj naizgled pasivnoj prirodi. Curenje informacija o lokalnom sustavu može pomoći napadaču u sljedećim napadima, koji će prekršiti načelo dubinske obrane.

Slučajne vrijednosti u PHP-u

PHP koristi tri PRNG-a, a ako napadač dobije pristup sjemenkama koje se koriste u njihovim algoritmima, moći će predvidjeti rezultate njihova rada:

  1. Linearni kongruencijalni generator (LCG), lcg_value() .
  2. Mersennov vrtlog, mt_rand() .
  3. Lokalno podržana C funkcija rand() .

Ovi se generatori također koriste interno, za funkcije kao što su array_rand() i uniqid(). To znači da napadač može predvidjeti izlaz ovih i drugih funkcija koje koriste PHP-ove unutarnje PRNG-ove ako dobiju sve potrebne početne vrijednosti. To također znači da ne možete poboljšati svoju obranu zbunjujući napadača višestrukim pozivima generatorima. To posebno vrijedi za aplikacije otvorenog koda. Napadač je u stanju predvidjeti SVE izlaze za bilo koju početnu vrijednost koja mu je poznata.


Kako bi poboljšao kvalitetu generiranih nasumičnih vrijednosti za netrivijalne zadatke, PHP treba vanjske izvore entropije koje pruža operativni sustav. U Linuxu se obično koristi /dev/urandom, možete ga čitati izravno ili mu pristupiti neizravno pomoću funkcija openssl_pseudo_random_bytes() ili mcrypt_create_iv(). Oba mogu koristiti kriptografski siguran generator pseudoslučajnih brojeva (CSPRNG) u sustavu Windows, ali u PHP-u u korisničkom prostoru još uvijek ne postoji izravna metoda za dobivanje podataka iz ovog generatora bez proširenja koja pružaju te funkcije. Drugim riječima, provjerite ima li vaša poslužiteljska verzija PHP-a omogućeno proširenje OpenSSL ili Mcrypt.


/dev/urandom je PRNG, ali često dobiva nova sjemena iz izvora visoke entropije /dev/random. To ga čini nezanimljivom metom za napadača. Pokušavamo izbjeći čitanje izravno iz /dev/random jer je to resurs koji blokira. Ako mu ponestane entropije, tada će sva čitanja biti blokirana dok ponovno ne dobije dovoljno entropije iz okruženja sustava. Iako za najvažnije zadatke trebate koristiti /dev/random .


Sve ovo nas dovodi do pravila:


Svi procesi koji uključuju upotrebu netrivijalnih nasumičnih brojeva MORAJU koristiti openssl_pseudo_random_bytes(). Alternativno, MOŽETE pokušati izravno čitati bajtove iz /dev/urandom. Ako nijedna opcija ne radi i nemate izbora, tada MORATE generirati vrijednost velikim miješanjem podataka iz više dostupnih izvora slučajnih ili tajnih vrijednosti.

Osnovnu implementaciju ovog pravila pronaći ćete u referentnoj biblioteci SecurityMultiTool. Kao i obično, interni PHP radije otežava život programerima umjesto izravnog uključivanja sigurnih rješenja u PHP jezgru.


Dosta teorije, sada da vidimo kako možete napasti aplikaciju, naoružani gore navedenim.

Napad na generatore slučajnih brojeva u PHP-u

Iz više razloga PHP koristi PRNG za rješavanje netrivijalnih problema.


Funkcija openssl_pseudo_random_bytes() bila je dostupna samo u PHP-u 5.3. Na Windowsima je uzrokovao probleme s blokiranjem sve dok nije izašla verzija 5.3.4. Također u PHP 5.3, funkcija mcrypt_create_iv() u sustavu Windows počela je podržavati izvor MCRYPT_DEV_URANDOM. Prethodno je Windows podržavao samo MCRYPT_RAND - u biti isti PRNG sustava koji interno koristi funkcija rand(). Kao što vidite, prije PHP-a 5.3 bilo je mnogo praznina, tako da se mnoge naslijeđene aplikacije napisane u prethodnim verzijama možda nisu prebacile na jače PRNG-ove.


Izbor Openssl i Mcrypt ekstenzija je po vašem nahođenju. Budući da se ne može pouzdati u njihovu dostupnost čak ni na poslužiteljima koji pokreću PHP 5.3, aplikacije često koriste PRNG-ove ugrađene u PHP kao zamjenu za generiranje netrivijalnih slučajnih vrijednosti.


Ali u oba slučaja imamo netrivijalne probleme koji koriste nasumične vrijednosti koje generiraju PRNG-ovi sa sjemenkama niske entropije. To nas čini ranjivima na napade povrata. Pogledajmo jednostavan primjer.


Zamislimo da smo na mreži pronašli aplikaciju koja koristi sljedeći kod za generiranje tokena koji se koriste u raznim zadacima u cijeloj aplikaciji:


$token = hash("sha512", mt_rand());

Postoje složeniji načini generiranja tokena, ali ovo je dobra opcija. Postoji samo jedan poziv za mt_rand(), hashiran sa SHA512. U praksi, ako programer odluči da su PHP-ove funkcije nasumičnih vrijednosti "dovoljno nasumične", tada će najvjerojatnije odabrati pojednostavljeni pristup sve dok se ne spomene riječ "kriptografija". Na primjer, nekriptografski slučajevi uključuju pristupne tokene, CSRF tokene, API jednokratne vrijednosti i tokene za ponovno postavljanje lozinke. Prije nego što nastavim, detaljno ću opisati puni opseg ranjivosti ove aplikacije kako biste bolje razumjeli što aplikacije uopće čini ranjivima.

Karakteristike ranjive aplikacije

Ovo nije iscrpan popis. U praksi se popis karakteristika može razlikovati!

1. Poslužitelj koristi mod_php, koji, kada se koristi s KeepAlive, dopušta da više zahtjeva posluži isti PHP proces

Ovo je važno jer se generatori slučajnih brojeva u PHP-u postavljaju samo jednom po procesu. Ako možemo uputiti dva ili više zahtjeva procesu, on će koristiti istu početnu vrijednost. Bit napada je korištenje ekspanzije jednog tokena za izdvajanje početne vrijednosti, koja je potrebna za predviđanje drugog tokena generiranog na temelju ISTE početne vrijednosti (tj. u istom procesu). Budući da je mod_php idealan za korištenje višestrukih upita za dohvaćanje povezanih nasumičnih vrijednosti, ponekad je moguće dohvatiti više vrijednosti povezanih s mt_rand() samo jednim upitom. Ovo čini sve mod_php zahtjeve suvišnima. Na primjer, dio entropije koja se koristi za generiranje sjemena za mt_rand() može procuriti kroz ID-ove sesije ili izlazne vrijednosti u istom zahtjevu.

2. Poslužitelj otkriva CSRF tokene, tokene za ponovno postavljanje lozinke ili tokene za potvrdu računa generirane na temelju mt_rand() tokena

Da bismo izdvojili početnu vrijednost, moramo izravno provjeriti broj koji su generirali generatori u PHP-u. I nije čak ni važno kako se koristi. Možemo ga izdvojiti iz bilo koje dostupne vrijednosti, bilo da je to izlaz funkcije mt_rand(), ili raspršeni CSRF, ili token za potvrdu računa. Prikladni su čak i neizravni izvori u kojima slučajna vrijednost određuje različito ponašanje na izlazu, što otkriva upravo tu vrijednost. Glavno ograničenje je da mora biti iz istog procesa koji generira drugi token koji pokušavamo predvidjeti. A ovo je ranjivost "otkrivanja informacija". Kao što ćemo uskoro vidjeti, curenje PRNG izlaza može biti izuzetno opasno. Imajte na umu da ranjivost nije ograničena na jednu aplikaciju: možete čitati PRNG izlaz iz jedne aplikacije na poslužitelju i koristiti ga za određivanje izlaza iz druge aplikacije na istom poslužitelju, sve dok obje koriste isti PHP proces.

3. Poznati slabi algoritam za generiranje tokena

Možete ga izračunati:

  • udubivši se u izvore aplikacije otvorenog koda,
  • podmićivanje zaposlenika s pristupom osobnom izvornom kodu,
  • pronalaženje bivšeg zaposlenika koji gaji kiv prema bivšem poslodavcu,
  • ili jednostavno nagađanje koji bi algoritam mogao postojati.

Neke su metode generiranja tokena očitije, neke su popularnije. Zaista slabo generiranje znači upotrebu jednog od PHP-ovih generatora slučajnih brojeva (npr. mt_rand()), slabu entropiju (nema drugih izvora nedefiniranih podataka) i/ili slabo raspršivanje (npr. MD5 ili nikakvo raspršivanje). Gore razmotren primjer koda ima obilježja slabe metode generiranja. Također sam koristio SHA512 raspršivanje kako bih pokazao da je maskiranje uvijek nezadovoljavajuće rješenje. SHA512 je slabo raspršivanje jer je brz za izračunavanje, što znači da napadač može brutalno izvršiti unos podataka na bilo koji CPU ili GPU nevjerojatnom brzinom. I ne zaboravite da je Mooreov zakon još uvijek na snazi, što znači da će brute force brzina rasti sa svakom novom generacijom CPU/GPU-a. Stoga se lozinke moraju raspršiti pomoću alata kojima je potrebno fiksno vrijeme za probijanje, bez obzira na performanse procesora ili Mooreov zakon.

Izvođenje napada

Naš napad je vrlo jednostavan. Kao dio povezivanja s PHP procesom, održat ćemo brzu sesiju i poslati dva odvojena HTTP zahtjeva (zahtjev A i zahtjev B). Poslužitelj će zadržati sesiju dok ne primi drugi zahtjev. Zahtjev A usmjeren je na dobivanje nekog dostupnog tokena kao što je CSRF, token za ponovno postavljanje lozinke (poslano napadaču poštom) ili nešto slično. Ne zaboravite na druge značajke kao što je ugrađeno označavanje koje se koristi u zahtjevima za proizvoljne ID-ove, itd. Mučit ćemo izvorni token dok nam ne da svoju početnu vrijednost. Sve je to dio napada oporavka seeda: gdje seed ima tako malu entropiju da se može brutalno forsirati ili potražiti u unaprijed izračunatoj duginoj tablici.


Upit B će riješiti zanimljiviji problem. Zatražimo ponovno postavljanje lozinke lokalnog administratora. To će pokrenuti generiranje tokena (koristeći nasumični broj temeljen na istom seedu koji povlačimo sa zahtjevom A ako su oba zahtjeva uspješno poslana istom PHP procesu). Ovaj token bit će pohranjen u bazi podataka u iščekivanju trenutka kada administrator upotrijebi poveznicu za ponovno postavljanje lozinke koja mu je poslana e-poštom. Ako možemo izdvojiti početnu vrijednost za token iz zahtjeva A, tada znajući kako se generira token iz zahtjeva B, možemo predvidjeti token za ponovno postavljanje lozinke. To znači da možemo kliknuti na poveznicu za resetiranje prije nego što administrator pročita pismo!


Evo slijeda događaja:

  1. Koristeći zahtjev A, dobivamo token i vršimo obrnuti inženjering kako bismo izračunali početnu vrijednost.
  2. Koristeći zahtjev B, dobivamo token generiran na temelju iste početne vrijednosti. Ovaj je token pohranjen u bazi podataka aplikacije za buduće ponovno postavljanje lozinke.
  3. Razbijamo SHA512 hash kako bismo dobili nasumični broj koji generira poslužitelj.
  4. Koristeći dobivenu slučajnu vrijednost, grubo forsiramo početnu vrijednost koja je generirana uz njegovu pomoć.
  5. Koristimo seed za izračunavanje niza slučajnih vrijednosti koje vjerojatno mogu činiti osnovu tokena za poništavanje lozinke.
  6. Koristimo ovaj token(e) za poništavanje administratorske lozinke.
  7. Dobivamo pristup administratorskom računu, zabavljamo se i koristimo. Pa, barem se zabavljamo.

Počnimo hakirati...

Hakiranje aplikacija korak po korak

Korak 1. Napravite zahtjev A za dohvaćanje tokena

Pretpostavljamo da ciljni token i token za ponovno postavljanje lozinke ovise o izlazu funkcije mt_rand(). Stoga ga trebate odabrati. U aplikaciji u našem zamišljenom scenariju svi se tokeni generiraju na isti način, tako da možemo jednostavno izdvojiti CSRF token i spremiti ga za kasnije.

Korak 2. Izvedite zahtjev B za primanje tokena za ponovno postavljanje lozinke generiranog za administratorski račun

Ovaj zahtjev je jednostavno podnošenje obrasca za ponovno postavljanje lozinke. Token će biti spremljen u bazu podataka i poslan korisniku poštom. Moramo ispravno izračunati ovaj token. Ako su specifikacije poslužitelja točne, tada zahtjev B koristi isti PHP proces kao zahtjev A. Stoga će pozivi na mt_rand() koristiti istu početnu vrijednost u oba slučaja. Možete čak upotrijebiti Zahtjev A za hvatanje CSRF tokena obrasca za poništavanje kako biste omogućili podnošenje u svrhu pojednostavljenja postupka (izbjegavajući međukružno putovanje).

Korak 3. Hakirajte SHA512 hash tokena primljenog od zahtjeva A

SHA512 izaziva strahopoštovanje među programerima: ima najveći broj u cijeloj obitelji SHA-2 algoritama. Međutim, postoji jedan problem s metodom generiranja tokena naše žrtve - nasumične vrijednosti ograničene su samo na brojeve (tj. stupanj nesigurnosti ili entropije je zanemariv). Ako provjerite izlaz funkcije mt_getrandmax(), vidjet ćete da je najveći nasumični broj koji mt_rand() može generirati 2,147 milijardi i neka promjena. Ovaj ograničeni broj značajki čini SHA512 ranjivim na grubu silu.


Samo me nemoj vjerovati na riječ. Ako imate diskretnu video karticu jedne od najnovijih generacija, tada možete ići na sljedeći način. Budući da tražimo jedan hash, odlučio sam upotrijebiti prekrasan brute force alat - hashcat-lite. Ovo je jedna od najbržih verzija hashcata i dostupna je za sve glavne operativne sustave, uključujući Windows.


Koristite ovaj kod za generiranje tokena:


$rand = mt_rand(); echo "Slučajni broj: ", $rand, PHP_EOL; $token = hash("sha512", $rand); echo "Token: ", $token, PHP_EOL;

Ovaj kod reproducira token iz zahtjeva A (sadrži nasumični broj koji nam je potreban i skriven je u SHA512 hash) i pokreće ga kroz hashcat:


./oclHashcat-lite64 -m1700 --pw-min=1 --pw-max=10 -1?d -o ./seed.txt ?d?d?d?d?d?d?d?d?d?d

Evo što sve te opcije znače:

  • -m1700: Određuje algoritam raspršivanja, gdje 1700 znači SHA512.
  • --pw-min=1: Definira minimalnu duljinu unosa raspršene vrijednosti.
  • --pw-max=10: Definira maksimalnu duljinu unosa raspršene vrijednosti (10 za mt_rand()).
  • -1?d: navodi da trebamo prilagođeni rječnik samo brojeva (tj. 0-9).
  • -o ./seed.txt: datoteka za pisanje rezultata. Ništa se ne prikazuje na ekranu, stoga ne zaboravite postaviti ovu opciju!
  • ?d?d?d?d?d?d?d?d?d?d: maska ​​koja specificira format za korištenje (sve znamenke do najviše 10).

Ako sve radi ispravno i vaš se GPU ne topi, Hashcat će izračunati hashirani slučajni broj za nekoliko minuta. Da, minute. Prethodno sam objasnio kako entropija funkcionira. Uvjerite se sami. Funkcija mt_rand() ima toliko malo mogućnosti da se SHA512 raspršivanja svih vrijednosti zapravo mogu izračunati u vrlo kratkom vremenu. Dakle, nije bilo smisla hashirati izlaz mt_rand() .

Korak 4. Vratite početnu vrijednost pomoću svježe hakiranog slučajnog broja

Kao što smo vidjeli gore, potrebno je samo nekoliko minuta da se izdvoji bilo koja vrijednost koju generira mt_rand() iz SHA512. Naoružani slučajnom vrijednošću, možemo pokrenuti još jedan brute force alat - php_mt_seed. Ovaj mali uslužni program uzima izlaz mt_rand() i, nakon brute force-a, izračunava početnu vrijednost iz koje je analiza mogla biti generirana. Preuzmite trenutnu verziju, prevedite i pokrenite. Ako imate problema s kompilacijom, pokušajte sa starijom verzijom (s novima sam imao problema s virtualnim okruženjima).


./php_mt_seed

Ovo može potrajati malo dulje od SHA512 krekiranja jer se radi na CPU-u. Na pristojnom procesoru, uslužni program će pronaći cijeli mogući raspon početne vrijednosti za nekoliko minuta. Rezultat je jedna ili više mogućih vrijednosti (to jest, vrijednosti koje su se mogle koristiti za proizvodnju slučajnog broja). Opet vidimo rezultat slabe entropije, samo ovaj put u odnosu na PHP-ovo generiranje početnih vrijednosti za Mersenneovu vrtložnu funkciju. Kasnije ćemo pogledati kako su te vrijednosti generirane, tako da ćete vidjeti zašto se gruba sila može izvesti tako brzo.


Stoga smo prije ovoga koristili jednostavne alate za hakiranje dostupne na webu. Usmjereni su na pozive mt_rand(), ali ilustriraju ideju koja se može primijeniti na druge scenarije (na primjer, sekvencijalni pozivi mt_rand() prilikom generiranja tokena). Također imajte na umu da brzina hakiranja ne sprječava generiranje duginih tablica koje uzimaju u obzir specifične pristupe generiranja tokena. Evo još jednog alata koji iskorištava ranjivosti mt_rand() i napisan je u Pythonu.

Korak 5. Generirajte moguće tokene za poništavanje lozinke administratorskog računa

Pretpostavimo da su unutar zahtjeva A i B samo dva zahtjeva upućena mt_rand(). Sada počnimo predviđati tokene koristeći prethodno izračunate moguće početne vrijednosti:


function predict($seed) ( /** * Proslijedite početnu vrijednost u PRNG */ mt_srand($seed); /** * Preskočite poziv funkcije iz zahtjeva A */ mt_rand(); /** * Predvidite i vratite jedan generiran u zahtjevu B token */ $token = hash("sha512", mt_rand());

Ova funkcija predviđa token resetiranja za svako moguće početno mjesto.

Koraci 6 i 7: Poništite lozinku svog administratorskog računa i zabavite se!

Sada trebate prikupiti URL koji sadrži token koji će vam omogućiti poništavanje administratorske lozinke zbog ranjivosti aplikacije i pristup vašem računu. Možda ćete otkriti da možete objaviti nefiltrirani HTML na forumu ili u članku (često kršenje načela dubinske obrane). To će vam omogućiti izvođenje opsežnog XSS napada na sve ostale korisnike aplikacije, zaražavajući njihova računala zlonamjernim softverom i nadgledanjem Man-In-The-Browser. Ozbiljno, zašto stati samo na dobivanju pristupa? Smisao ovih naizgled pasivnih i ne baš opasnih ranjivosti je pomoći napadaču da polako prodre do mjesta gdje konačno može postići svoj glavni cilj. Hakiranje je poput igranja arkadne borbene igre u kojoj morate brzo pritisnuti pravu kombinaciju kako biste oslobodili niz snažnih napada.

Analiza nakon napada

Gornji scenarij i jednostavnost koraka trebali bi vam jasno pokazati opasnosti mt_rand() . Rizici su toliko očiti da sada sve slabo skrivene izlazne vrijednosti mt_rand() koje su dostupne napadaču u bilo kojem obliku možemo smatrati ranjivošću "otkrivanja informacija".


Štoviše, postoji i druga strana ove priče. Na primjer, ako ovisite o biblioteci koja bezazleno koristi mt_rand() za neke važne zadatke, čak i bez davanja rezultirajućih vrijednosti, tada ćete korištenjem "propustljivog" tokena za svoje potrebe ugroziti ovu biblioteku. A to je problem jer biblioteka ili okvir ne čini ništa da ublaži napad vraćanja početne vrijednosti. Trebamo li kriviti korisnika za curenje mt_rand() vrijednosti - ili biblioteku jer nije primijenila bolje nasumične vrijednosti?


Zapravo, oboje su prilično krivi. Knjižnica ne bi trebala odabrati mt_rand() (ili bilo koji drugi izvor slabe entropije) za važne probleme kao jedini izvor slučajnih vrijednosti. I korisnik ne bi trebao pisati kod koji propušta mt_rand() vrijednosti. Dakle, da, možete početi upirati optužujućim prstom u nepismene primjere korištenja mt_rand() , čak i ako to ne dovede do izravnog curenja informacija.


Ne trebate se brinuti samo o ranjivostima otkrivanja informacija. Također je važno biti svjestan nedostatka entropijskih ranjivosti, koje ostavljaju aplikacije ranjivima na grubu silu osjetljivih tokena, ključeva ili nonce koji tehnički nisu kriptografski, ali se koriste u radu netrivijalnih funkcija aplikacije.

I sad je sve isto

Sada znamo da se korištenje PRNG-ova ugrađenih u PHP smatra nedostatkom entropijske ranjivosti (tj. smanjenje neizvjesnosti olakšava grubu silu). Možemo proširiti naš napad:


Ranjivost otkrivanja informacija čini ovu metodu generiranja tokena potpuno beskorisnom. Da bismo razumjeli zašto, pogledajmo pobliže PHP funkciju uniqid(). Njegova definicija:


Na temelju trenutnog vremena u mikrosekundama dobiva jedinstveni identifikator prefiksa.


Kao što se sjećate, entropija je mjera nesigurnosti. Zbog ranjivosti otkrivanja informacija, vrijednosti koje generira mt_rand() mogu procuriti, tako da korištenje mt_rand() kao jedinstvenog prefiksa identifikatora ne dodaje nultu nesigurnost. U našem primjeru, jedina druga vrsta unosa u uniqid() je vrijeme. Ali definitivno NIJE nejasno. Mijenja se linearno i predvidljivo. A predvidljive vrijednosti imaju izuzetno nisku entropiju.


Naravno, definicija se odnosi na "mikrosekunde", tj. milijunti dio sekunde. Ovo nam daje 1.000.000 mogućih brojeva. Ovdje ignoriram vrijednosti veće od 1 sekunde jer su njihov udio i mjerljivost toliko veliki (na primjer, zaglavlje HTTP datuma u odgovoru) da ne daje gotovo ništa. Prije nego što uđemo u detalje, raščlanimo funkciju uniqid() i pogledajmo njen C kod:


gettimeofday((struct timeval *) &tv, (struct timezone *) NULL); sec = (int) tv.tv_sec; usec = (int) (tv.tv_usec % 0x100000); /* usec može imati maksimalnu vrijednost 0xF423F, tako da koristimo * usec samo pet heksadecimalnih brojeva.

*/ if (more_entropy) ( spprintf(&uniqid, 0, "%s%08x%05x%.8F", prefix, sec, usec, php_combined_lcg(TSRMLS_C) * 10); ) else ( spprintf(&uniqid, 0, "% s%08x%05x", prefiks, sec, usec); ) RETURN_STRING(uniqid, 0);


Ako ovo izgleda previše komplicirano, možete replicirati sve u dobrom starom PHP-u:

function unique_id($prefix = "", $more_entropy = false) ( list($usec, $sec) = explode(" ", microtime()); $usec *= 1000000; if(true === $more_entropy) ( return sprintf("%s%08x%05x%.8F", $prefix, $sec, $usec, lcg_value()*10); else ( return sprintf("%s%08x%05x", $prefix, $ sec.) , $usec);


Ovaj kod nam govori da će jednostavno pozivanje uniqid() bez parametara vratiti niz od 13 znakova. Prvih 8 znakova je trenutna Unix vremenska oznaka (u sekundama), izražena u heksadecimalnom obliku. Posljednjih 5 znakova su dodatne mikrosekunde u heksadecimalnom obliku. Drugim riječima, osnovna funkcija uniqid() pruža vrlo precizno mjerenje sistemskog vremena, koje se može izdvojiti iz jednostavnog poziva uniqid() s kodom poput ovog:

$id = uniqid(); $vrijeme = str_split($id, 8); $sec = hexdec("0x" . $vrijeme); $usec = hexdec("0x" . $vrijeme); echo "Sekunde: ", $sec, PHP_EOL, "Mikrosekunde: ", $usec, PHP_EOL;


Pogledajte C kod. Točno sistemsko vrijeme nikada nije skriveno u izlazu, bez obzira na parametre:

echo uniqid(), PHP_EOL; // 514ee7f81c4b8 echo uniqid("prefiks-"), PHP_EOL; // prefix-514ee7f81c746 echo uniqid("prefix-", true), PHP_EOL; // prefiks-514ee7f81c8993.39593322

Brute force jedinstveni identifikatori


Nakon razmišljanja, postaje očito da je otkrivanje bilo koje uniqid() vrijednosti napadaču još jedan primjer potencijalne ranjivosti otkrivanja informacija. Ovo curi vrlo precizno sistemsko vrijeme, koje se može koristiti za predviđanje unosa za naredne uniqid() pozive. To pomaže u rješavanju svih dilema koje se javljaju prilikom pokušaja predviđanja mikrosekundi sužavanjem 1.000.000 mogućnosti na uži raspon. Budući da se ovo curenje moglo spomenuti kasnije, ono tehnički nije potrebno u našem primjeru. Pogledajmo ponovo izvorni uniqid() kod tokena:

Iz ovog primjera možemo vidjeti da ćemo izvođenjem napada resetiranja protiv mt_rand() u kombinaciji s otkrivanjem informacija iz uniqid() izračunati relativno mali skup SHA512 hashova, za koje se može ispostaviti da su poništavanje lozinki ili drugi važni tokeni. Ako vam je potreban uzak raspon vremenskih oznaka bez iskorištavanja curenja sistemskog vremena iz uniqid(), analizirajmo odgovore poslužitelja, koji obično sadrže zaglavlje HTTP datuma. Odavde možete dobiti točne vremenske oznake poslužitelja. A budući da je u ovom slučaju entropija jednaka milijunu mogućih mikrosekundnih vrijednosti, možete je bruteforceirati u nekoliko sekundi!


Hoće li nas povećanje entropije spasiti?

Naravno, moguće je dodati entropiju uniqid() postavljanjem drugog parametra funkcije na TRUE:


Kao što C kod pokazuje, novi izvor entropije koristi izlaz interne funkcije php_combined_lcg(). Ova je funkcija izložena korisničkom prostoru putem funkcije lcg_value(), koju sam koristio u svojoj PHP konverziji funkcije uniqid(). U biti, kombinira dvije vrijednosti koje generiraju dva linearna kongruentna generatora s odvojenim početnim vrijednostima. Ispod je kod koji je generatore opskrbio ovim početnim vrijednostima. Kao i kod mt_rand(), oni se generiraju jednom po PHP procesu i ponovno koriste u svim sljedećim pozivima.


static void lcg_seed(TSRMLS_D) /* ((( */ ( struct timeval tv; if (gettimeofday(&tv, NULL) == 0) ( LCG(s1) = tv.tv_sec ^ (tv.tv_usec<<11); } else { LCG(s1) = 1; } #ifdef ZTS LCG(s2) = (long) tsrm_thread_id(); #else LCG(s2) = (long) getpid(); #endif /* Add entropy to s2 by calling gettimeofday() again */ if (gettimeofday(&tv, NULL) == 0) { LCG(s2) ^= (tv.tv_usec<<11); } LCG(seeded) = 1; }

Ako predugo gledate i poželite nešto baciti na monitor, bolje nemojte. Monitori su danas skupi.


Obje početne vrijednosti koriste funkciju C gettimeofday() za snimanje trenutnog vremena u sekundama i mikrosekundama od Unix epohe (odnosi se na sat poslužitelja). Treba napomenuti da su oba poziva implementirana u izvornom kodu, tako da će vrijednost brojača microsecond() između njih biti minimalna, što smanjuje unesenu nesigurnost. Druga početna vrijednost također će biti umiješana u ID trenutnog procesa, koji u većini slučajeva pod Linuxom neće premašiti 32,768. Naravno, možete ručno podići ograničenje na oko 4 milijuna promjenom /proc/sys/kernel/pid_max. , ali ovo je vrlo nepoželjno.


Ispostavilo se da su primarni izvor entropije koju koriste ovi LCG mikrosekunde. Na primjer, sjećate se početne vrijednosti mt_rand()? Pogodite kako se izračunava.


#ifdef PHP_WIN32 #define GENERATE_SEED() (((dugo) (vrijeme(0) * GetCurrentProcessId())) ^ ((dugo) (1000000.0 * php_combined_lcg(TSRMLS_C)))) #else #define GENERATE_SEED() (((dugo ) (vrijeme(0) * getpid())) ^ ((dugo) (1000000.0 * php_combined_lcg(TSRMLS_C)))) #endif

To znači da su sve početne vrijednosti koje se koriste u PHP-u međusobno ovisne. Čak se isti ulazni podaci miješaju nekoliko puta. Možda biste mogli ograničiti raspon početnih mikrosekundi kao što smo gore spomenuli: koristeći dva upita, s prvim koji skače između sekundi (tako da bi mikrovrijeme bilo 0 + vrijeme izvršenja sljedećeg gettimeofday() C poziva). Možete čak izračunati mikrosekundnu deltu između drugih gettimeofday() poziva ako imate pristup izvornom kodu (PHP-ova priroda otvorenog koda pomaže). Da ne spominjemo, grubo forsiranje seeda pomoću mt_rand() daje vam konačan seed koji omogućuje izvanmrežnu provjeru.


Međutim, glavni problem leži u php_combined_lcg() . Ovo je implementacija funkcije lcg_value() u korisničkom prostoru niske razine koja dobiva početnu vrijednost jednom po PHP procesu. A ako znate ovu vrijednost, tada možete predvidjeti izlaz. Ako razbiješ ovaj orah, to je to, igra je gotova.

Postoji aplikacija za to...

Proveo sam puno vremena fokusirajući se na praktične stvari, pa se vratimo na njih. Nije tako lako dobiti dvije početne vrijednosti koje koristi php_combined_lcg() - možda neće biti moguće stvoriti izravno curenje. Funkcija lcg_value() je relativno malo poznata, a programeri se češće oslanjaju na mt_rand() kada im je potreban PRNG ugrađen u PHP. Ne želim spriječiti lcg_value() da procuri vrijednost, ali to je nepopularna funkcija. Par kombiniranih LCG-ova također ne odražava funkciju sijanja (tako da ne možete samo tražiti pozive na mt_srand() da identificirate nepropusni mehanizam sijanja naslijeđen iz nečijeg naslijeđenog koda). Međutim, postoji pouzdan izvor koji izravno pruža podatke za brute-forcing seeds: ID-ovi sesija u PHP-u.


spprintf(&buf, 0, "%.15s%ld%ld%0.8F", remote_addr ? remote_addr: "", tv.tv_sec, (long int)tv.tv_usec, php_combined_lcg(TSRMLS_C) * 10);

Ovaj kod generira pre-hash vrijednost za ID sesije koristeći IP, vremensku oznaku, mikrosekunde i... php_combined_lcg() izlaz. S obzirom na značajno smanjenje broja mikrotemporalnih mogućnosti (ovaj kod treba 1 za generiranje ID-a i 2 za php_combined_lcg(), što rezultira minimalnom razlikom među njima), sada ga možemo grubo forsirati. Pa, vjerojatno.


Kao što se možda sjećate, PHP sada podržava nove opcije sesije kao što su session.entropy_file i session.entropy_length. Ovo je učinjeno kako bi se spriječila gruba sila ID-ova sesije, tijekom koje možete brzo (neće trajati satima) dobiti dvije početne vrijednosti za LCG generatore kombinirane pomoću php_combined_lcg(). Ako koristite PHP 5.3 ili niži, možda niste ispravno konfigurirali ove opcije. To znači da postoji još jedna korisna ranjivost otkrivanja informacija koja vam omogućuje grubu silu ID-ova sesije kako biste dobili početne vrijednosti za LCG.


Za takve slučajeve postoji Windows aplikacija koja vam omogućuje izračunavanje LCG vrijednosti.


Usput, poznavanje LCG stanja omogućuje vam da razumijete kako mt_rand() dobiva početnu vrijednost, tako da je ovo još jedan način da se zaobiđe nedostatak curenja vrijednosti mt_rand().


Što sve ovo znači u smislu dodavanja entropije povratnim vrijednostima uniqid()?


$token = hash("sha512", uniqid(mt_rand(), true));

Ovo je još jedan primjer potencijalne ranjivosti nedostatka entropije. Ne možete se osloniti na entropiju s curenjem (čak i ako niste odgovorni za njih!). Zahvaljujući curenju informacija o ID-u sesije, napadač može predvidjeti i vrijednost entropije koja je dodatno dodana ovom ID-u.


Opet, tko je kriv? Ako se aplikacija X oslanja na uniqid(), ali korisnik ili druga aplikacija na istom poslužitelju propušta interno LCG stanje, tada trebate poduzeti radnju u obje situacije. Korisnici bi trebali biti sigurni da ID-ovi sesija koriste dovoljno visoku entropiju, a programeri trećih strana trebali bi razumjeti da njihovim metodama za generiranje slučajnih vrijednosti nedostaje entropija, pa se moraju prebaciti na prikladnije alternative (čak i ako su dostupni samo izvori slabe entropije!) .

U potrazi za entropijom

PHP sam po sebi nije sposoban generirati jaku entropiju. Ne postoji čak ni osnovni API za prijenos podataka iz PRNG generatora na razini operativnog sustava, koji su pouzdani izvori jake entropije. Stoga se trebate osloniti na opcijska proširenja openssl i mcrypt. Oni nude značajke koje su puno bolje od svojih propusnih, predvidljivih rođaka niske entropije.


Nažalost, budući da su oba proširenja izborna, u nekim slučajevima nemamo drugog izbora nego osloniti se na slabe izvore entropije kao posljednje sredstvo. Kada se to dogodi, slabu entropiju mt_rand() treba nadopuniti dodatnim izvorima nesigurnosti, miješajući njihove podatke u jedan skup iz kojeg se mogu izvući pseudoslučajni bajtovi. Anthony Ferrara je već implementirao sličan slučajni generator koji koristi snažan entropijski mikser u svojoj biblioteci RandomLib. To je ono što programeri trebaju činiti kad god je to moguće.


Izbjegnite iskušenje da sakrijete slabost svoje entropije raspršivanjem složenih matematičkih transformacija. Napadač će sve ovo ponoviti čim sazna primarnu početnu vrijednost. Takvi trikovi samo će malo povećati količinu izračuna tijekom grube sile. Ne zaboravite: što je niža entropija, manja je neizvjesnost; Što je manje neizvjesnosti, to je manje prilika potrebno grubo forsirati. Jedino opravdano rješenje je povećanje količine entropije koju koristite bilo kojim dostupnim sredstvom.


Knjižnica RandomLib generira nasumične bajtove miješanjem podataka iz različitih entropijskih izvora i lokaliziranjem informacija koje bi napadaču mogle trebati da nagađa. Na primjer, možete miješati izlaze mt_rand(), uniqid() i lcg_value(), dodati PID, potrošnju memorije, neka druga mjerenja mikrovremena, $_ENV serijalizaciju, posix_times() itd. Ili ići još dalje, ovo omogućuje RandomLib rastegljivost. Recimo da koristimo neke delte u mikrosekundama (tj. izmjerimo koliko mikrosekundi treba funkciji da radi s pseudo-slučajnim ulaznim podacima poput hash() poziva).

Dodajte oznake