Trenutno stanje: PHP2Shell. Dve kritični ranljivosti PHP (CVSS 9,2) omogočata izvedbo škodljive kode brez prijave. Popravki od 21. 10. 2026.Kaj je zdaj treba storiti →

Varna naključna števila in žetoni v PHP z random_bytes

Kako razvijalci v PHP ustvarjajo žetone za povezave do gesel, ključe API in enkratne kode: kriptografsko varni viri, zadostna dolžina, shranjevanje kot zgoščena vrednost in omejena veljavnost.

Stanje 5 min branjaVaren razvoj

Na kratko: Varne žetone v PHP ustvarite z random_bytes(), na primer 32 bajtov, ki jih z bin2hex() ali Base64-URL pretvorite v besedilo. Za naključna števila v območju obstaja random_int(), za kode iz stalnega nabora znakov od PHP 8.3 Random\Randomizer::getBytesFromString(). Žetone shranjujte kot zgoščeno vrednost, omejite njihovo veljavnost, uporabite jih samo enkrat in jih primerjajte s hash_equals().

Povezava, ki jo je prelahko uganiti

„Ste pozabili geslo?“ En klik in sistem pošlje povezavo z dolgo kodo. Kdor povezavo pozna, lahko nastavi geslo. Prav zato mora biti ta koda nepredvidljiva. Če ni, napadalec ne potrebuje niti nabiralnika niti starega gesla, le dovolj poskusov.

Pri pregledih aplikacij PHP se presenetljivo pogosto najdejo žetoni, sestavljeni iz ure, ID-ja uporabnika ali preprostega generatorja naključnih števil. Izgledajo dolgi in naključni, a niso. Enako velja za ključe API, povabilne povezave, povezave za souporabo dokumentov, potrditvene kode in žetone CSRF.

Tveganje je otipljivo: predvidljiva povezava za ponastavitev omogoča prevzem vsakega računa, katerega e-poštni naslov je znan, ne da bi lastnik kaj opazil, dokler se ne more več prijaviti sam.

PHP od različice 7.0 ponuja preprosto in varno rešitev. Uporabiti jo je treba le dosledno.

random_bytes v PHP: od kod prihajajo varne naključne vrednosti

random_bytes() po priročniku PHP daje kriptografsko varne naključne bajte, primerne tudi za dolgoživeče skrivnosti, kot so ključi. Funkcija uporablja vir naključnosti operacijskega sistema, pod Linuxom na primer getrandom(), pod Windows vmesnik CNG. Če ne najde primernega vira, od PHP 8.2 sproži Random\RandomException.

Pregled kaže, katere funkcije so za skrivnosti primerne:

Funkcija Primerna za žetone? Opomba
random_bytes() da varni bajti, od PHP 7.0
random_int() da varno število v območju, od PHP 7.0
Random\Randomizer z varnim pogonom (Secure) da Secure je po priročniku privzeti pogon
rand(), mt_rand() ne predvidljiva, samo za namene, ki niso varnostno pomembni
uniqid() ne temelji na času, namenjen enoličnosti
md5(time()), sha1($id) ne vnos je mogoče uganiti, zgoščena vrednost tega ne spremeni
str_shuffle(), array_rand() ne ne uporabljata varnega generatorja

Ustvarjanje žetonov: dolžina in kodiranje

32 naključnih bajtov ustreza 256 bitom in je za skoraj vsak namen več kot dovolj. Za prenos v povezavah ali glavah je treba žeton pretvoriti v besedilo. Običajni sta dve različici:

<?php
declare(strict_types=1);

// Hex: 64 znakov, samo 0-9 in a-f
$tokenHex = bin2hex(random_bytes(32));

// Base64-URL: 43 znakov, varno v URL-jih brez dodatnega kodiranja
$tokenUrl = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');

// Naključno število v območju, na primer za izbiro
$index = random_int(0, 9);

// Od PHP 8.3: šestmestna številčna koda, npr. za potrditev po SMS
$randomizer = new \Random\Randomizer();
$code = $randomizer->getBytesFromString('0123456789', 6);

Kratke številčne kode so sprejemljive samo, če je število poskusov strogo omejeno in koda po nekaj minutah poteče. Za povezave, poslane po e-pošti, vedno vzemite dolg žeton.

Za ključe API, ki jih partnerji ali lastni sistemi uporabljajo trajno, sta se obnesli dve dopolnitvi. Kratka, ne skrivna predpona pred naključnim delom, na primer za okolje ali namen, olajša prepoznavanje v dnevnikih in pri najdbah v repozitorijih kode. In ključ se prikaže samo enkrat ob ustvarjanju; potem sistem pozna le še njegovo zgoščeno vrednost. Če se ključ izgubi, se zamenja, ne obnovi.

Previdno pri samograjenih žetonih, ki vsebujejo podatke, na primer ID uporabnika in čas poteka v Base64. Kodiranje ni zaščita: vsak lahko take žetone prebere in spremeni. Če naj žeton nosi podatke, mora biti podpisan s skrivnim ključem, na primer s hash_hmac('sha256', ...), podpis pa se preveri s hash_equals(). Za zahtevnejše primere so preverjene knjižnice boljše od lastnih konstrukcij.

Shranjevanje, preverjanje in potek žetonov

Varno ustvarjen žeton je šele polovica dela. Vsaj enako pomembno je, kako z njim ravna aplikacija. Obnesel se je ta postopek za povezave za ponastavitev in povabila:

  1. Shranite samo zgoščeno vrednost: v bazi podatkov je hash('sha256', $token), sam žeton gre uporabniku samo v povezavi.
  2. Omejite veljavnost: skupaj shranite čas poteka in ga preverite ob unovčenju.
  3. Uporabite enkrat: po uspešni uporabi vnos izbrišite ali ga označite kot porabljenega.
  4. Ob spremembah razveljavite: po spremembi gesla izbrišite vse odprte žetone za ponastavitev tega računa.
  5. Primerjajte konstantno: kjer se žeton neposredno primerja s shranjeno vrednostjo, uporabite hash_equals(), s shranjeno vrednostjo kot prvim parametrom.
<?php
// Ustvarjanje
$token = bin2hex(random_bytes(32));
$eintrag = [
    'konto_id'   => $kontoId,
    'token_hash' => hash('sha256', $token),
    'gueltig_bis'=> (new DateTimeImmutable('+30 minutes'))->format('Y-m-d H:i:s'),
];
// $token pošljite samo v povezavi, $eintrag shranite

// Unovčenje
$gesucht = hash('sha256', (string) ($_GET['token'] ?? ''));
// Vnos poiščite s pripravljenim stavkom prek token_hash,
// preverite potek, nato vnos takoj izbrišite

Omejite tudi število zahtev za ponastavitev na račun in obdobje, da se funkcija ne more zlorabiti za množično pošiljanje e-pošte. Odgovor na zahtevo za ponastavitev poleg tega ne sme izdati, ali navedeni naslov sploh ima račun.

Hiter hash, kot je SHA-256, tu zadošča, ker je žeton sam že dolg in naključen. Za gesla, ki jih izbirajo ljudje, to ne velja; tam so potrebni počasni postopki, kot pojasnjuje Varno shranjevanje gesel v PHP.

Nasvet

Žetoni ne sodijo v dnevniške datoteke. Preverite, ali vaš spletni strežnik beleži polne URL-je s parametri poizvedbe in ali poročila o napakah pošiljajo podatke zahtev.

Po incidentu: obnovite žetone in ključe

Kdor je lahko izvajal kodo na strežniku, je praviloma imel dostop tudi do shranjenih žetonov, ključev API in podatkov sej. Če po PHP2Shell ali drugem incidentu obstaja sum vdora, zato ne obnovite samo gesel, ampak tudi dolgoživeče žetone in ključe: dostope API, podpisne ključe za piškotke ali povezave, dostope do ponudnikov plačil in dostave.

Seje so poseben primer: ID-je sej ustvari PHP sam. Kako zavarujete njihove nastavitve in po incidentu seje prekinete, opisuje vodnik Zavarovanje sej v PHP. Za žetone obrazcev proti ponarejenim zahtevam glejte Zaščita pred CSRF v aplikacijah PHP. Nadaljnja vgrajena orodja predstavlja Vgrajene varnostne funkcije PHP.

Kako konkretno pomaga PHP SOS?

  • V vaši aplikaciji poiščemo vsa mesta, kjer se ustvarjajo žetoni, kode in ključi, in nadomestimo nevarne vire.
  • Preverimo shranjevanje, potek in enkratno uporabo povezav za ponastavitev, povabilo in souporabo.
  • Poskrbimo, da žetoni ne končajo v dnevnikih, poročilih o napakah ali orodjih za analizo.
  • Po incidentu z vami načrtujemo urejeno zamenjavo žetonov in ključev.

Pregled lastne programske opreme PHP ponujamo kot varnostni pregled in penetracijski test.

Pogosta vprašanja

Zakaj mt_rand za žetone ni primeren?

mt_rand in rand sta namenjena statistiki in igram, ne skrivnostim. Njune izhode je včasih mogoče napovedati. Za vse, česar napadalec ne sme uganiti, uporabite random_bytes ali random_int.

Kako dolg naj bo žeton?

Za povezave za ponastavitev gesel, povabila ali ključe API je 32 naključnih bajtov trdna izbira, kot heksadecimalno besedilo 64 znakov. 16 bajtov, torej 128 bitov, je razširjena spodnja meja.

Ali je uniqid varen žeton?

Ne. uniqid temelji na trenutnem času in je namenjen enoličnosti, ne tajnosti. Tudi kombinacije, kot je md5 časa ali ID-ja uporabnika, so predvidljive.

Ali moram žetone za ponastavitev v bazi podatkov šifrirati?

Namesto žetona shranite njegovo zgoščeno vrednost, na primer s SHA-256. Kdor prebere bazo podatkov, tako ne more sestaviti veljavnih povezav. Ker je žeton sam že dolg in naključen, ne potrebuje počasnega postopka kot pri geslih.

Kaj se zgodi, če sistem ne najde vira naključnosti?

random_bytes in random_int od PHP 8.2 sprožita Random\RandomException. Ta primer ujemite in postopek prekinite, namesto da bi se zatekli k šibkejšemu viru.

Viri

Takojšnji stik

Vsaka ura šteje. Pišite nam zdaj.

Na kratko opišite sistem, različico PHP in opažanje. Odzovemo se, razjasnimo stanje in delamo na daljavo, dokler incident ni zaključen in dokumentiran.

Takojšnja pomoč