Röviden: Biztonságos tokeneket PHP-ban a random_bytes() függvénnyel hoz létre, például 32 bájtot, amelyet a bin2hex() vagy a Base64-URL segítségével alakít szöveggé. Egy tartományba eső véletlenszámokhoz a random_int() való, rögzített karakterkészletből álló kódokhoz a PHP 8.3 óta a Random\Randomizer::getBytesFromString(). A tokeneket hashként tárolja, érvényességüket korlátozza, csak egyszer használja őket, és a hash_equals() függvénnyel hasonlítja össze.
Egy link, amelyet túl könnyű kitalálni
„Elfelejtette a jelszavát?” Egy kattintás, és a rendszer elküld egy linket egy hosszú kóddal. Aki ismeri a linket, beállíthatja a jelszót. Éppen ezért ennek a kódnak megjósolhatatlannak kell lennie. Ha nem az, a támadónak sem a postafiókra, sem a régi jelszóra nincs szüksége, csak elég próbálkozásra.
A PHP-alkalmazások ellenőrzései során meglepően gyakran találni olyan tokeneket, amelyeket az időből, a felhasználói azonosítóból vagy egy egyszerű véletlenszám-generátorból építettek. Hosszúnak és véletlennek látszanak, de nem azok. Ugyanez érvényes az API-kulcsokra, a meghívó linkekre, a dokumentumok megosztási linkjeire, a megerősítő kódokra és a CSRF-tokenekre.
A kockázat kézzelfogható: egy megjósolható visszaállító link lehetővé teszi minden olyan fiók átvételét, amelynek az e-mail-címe ismert, anélkül hogy a tulajdonos bármit észrevenne, amíg maga nem tud többé bejelentkezni.
A PHP a 7.0-s verzió óta egyszerű és biztonságos megoldást kínál. Csak következetesen kell használni.
random_bytes PHP-ban: honnan származnak a biztonságos véletlenértékek
A random_bytes() a PHP kézikönyve szerint kriptográfiailag biztonságos véletlenbájtokat szolgáltat, amelyek hosszú életű titkokhoz, például kulcsokhoz is alkalmasak. A függvény az operációs rendszer véletlenforrását használja, Linuxon például a getrandom() hívást, Windowson a CNG-interfészt. Ha nem talál megfelelő forrást, a PHP 8.2 óta Random\RandomException kivételt dob.
Az áttekintés megmutatja, mely függvények alkalmasak titkokhoz:
| Függvény | Alkalmas tokenekhez? | Megjegyzés |
|---|---|---|
random_bytes() |
igen | biztonságos bájtok, a PHP 7.0 óta |
random_int() |
igen | biztonságos szám egy tartományban, a PHP 7.0 óta |
Random\Randomizer Secure engine-nel |
igen | a kézikönyv szerint a Secure az alapértelmezett engine |
rand(), mt_rand() |
nem | megjósolható, csak nem biztonsági célokra |
uniqid() |
nem | idő alapú, egyediségre való |
md5(time()), sha1($id) |
nem | a bemenet kitalálható, a hash ezen nem változtat |
str_shuffle(), array_rand() |
nem | nem használnak biztonságos generátort |
Tokenek létrehozása: hossz és kódolás
32 véletlen bájt 256 bitnek felel meg, és szinte minden célra bőven elég. Linkekben vagy fejlécekben való átvitelhez a tokent szöveggé kell alakítani. Két változat elterjedt:
<?php
declare(strict_types=1);
// Hexa: 64 karakter, csak 0-9 és a-f
$tokenHex = bin2hex(random_bytes(32));
// Base64-URL: 43 karakter, további kódolás nélkül biztonságos URL-ekben
$tokenUrl = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
// Véletlenszám egy tartományban, például egy kiválasztáshoz
$index = random_int(0, 9);
// A PHP 8.3-tól: hatjegyű számkód, pl. SMS-es megerősítéshez
$randomizer = new \Random\Randomizer();
$code = $randomizer->getBytesFromString('0123456789', 6);
A rövid számkódok csak akkor vállalhatók, ha a próbálkozások száma szigorúan korlátozott, és a kód néhány perc után lejár. Az e-mailben küldött linkekhez mindig a hosszú tokent használja.
A partnerek vagy saját rendszerek által tartósan használt API-kulcsokhoz két kiegészítés vált be. Egy rövid, nem titkos előtag a véletlen rész előtt, például a környezetre vagy a célra utalva, megkönnyíti a felismerést a naplókban és a kódtárakban való megtalálásnál. A kulcsot pedig csak egyszer, a létrehozáskor jelenítik meg; utána a rendszer már csak a hasht ismeri. Ha egy kulcs elvész, kicserélik, nem állítják helyre.
Óvatosan a saját építésű tokenekkel, amelyek információt tartalmaznak, például a felhasználói azonosítót és a lejárati időt Base64-ben. A kódolás nem védelem: ezeket a tokeneket bárki elolvashatja és módosíthatja. Ha egy tokennek adatot kell hordoznia, titkos kulccsal alá kell írni, például a hash_hmac('sha256', ...) segítségével, az aláírást pedig a hash_equals() függvénnyel ellenőrzik. Bonyolultabb esetekben az ellenőrzött könyvtárak előnyben részesítendők a saját konstrukciókkal szemben.
Tokenek tárolása, ellenőrzése és lejárata
Egy biztonságosan létrehozott token csak a munka fele. Legalább ilyen fontos, hogyan bánik vele az alkalmazás. Egy bevált eljárás visszaállító és meghívó linkekhez:
- Csak a hasht tárolni: az adatbázisban a
hash('sha256', $token)áll, maga a token csak linken keresztül jut el a felhasználóhoz. - Az érvényességet korlátozni: a lejárati időt is tárolni, és beváltáskor ellenőrizni.
- Egyszer használni: sikeres használat után a bejegyzést törölni vagy felhasználtként jelölni.
- Változásoknál érvényteleníteni: jelszómódosítás után a fiók összes nyitott visszaállító tokenjét törölni.
- Állandó időben összehasonlítani: ahol egy tokent közvetlenül egy tárolt értékkel hasonlítanak, a
hash_equals()-t használni, a tárolt értékkel első paraméterként.
<?php
// Létrehozás
$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'),
];
// A $token-t csak a linkben elküldeni, az $eintrag-ot tárolni
// Beváltás
$gesucht = hash('sha256', (string) ($_GET['token'] ?? ''));
// A bejegyzést prepared statementtel a token_hash alapján keresni,
// lejáratot ellenőrizni, utána a bejegyzést azonnal törölni
Korlátozza ezenkívül a visszaállítási kérések számát fiókonként és időszakonként, hogy a funkcióval ne lehessen tömeges e-mail-küldéssel visszaélni. A visszaállítási kérésre adott válasz ráadásul ne áruljon el, hogy a megadott címhez egyáltalán tartozik-e fiók.
Egy gyors hash, mint az SHA-256, itt elég, mert maga a token már hosszú és véletlen. Az emberek által választott jelszavakra ez nem érvényes; ott lassú eljárások kellenek, ahogyan a Jelszavak biztonságos tárolása PHP-ban elmagyarázza.
A tokenek nem valók naplófájlokba. Ellenőrizze, hogy a webszervere naplózza-e a lekérdezési paraméterekkel ellátott teljes URL-eket, és hogy a hibajelentések küldenek-e kéréseket tartalmazó adatokat.
Egy incidens után: tokenek és kulcsok megújítása
Aki kódot tudott végrehajtani egy szerveren, annak általában a tárolt tokenekhez, API-kulcsokhoz és munkamenet-adatokhoz is volt hozzáférése. Ha a PHP2Shell után vagy más incidens miatt betörés gyanúja áll fenn, ezért ne csak a jelszavakat, hanem a hosszú életű tokeneket és kulcsokat is újítsa meg: API-hozzáféréseket, a sütik vagy linkek aláíró kulcsait, a fizetési és szállítási szolgáltatások hozzáféréseit.
A sessionök különleges eset: a PHP maga hozza létre a session-azonosítókat. Hogyan biztosíthatja a beállításaikat, és hogyan szüntetheti meg a sessionöket egy incidens után, azt az A sessionök biztonságossá tétele PHP-ban útmutató írja le. A hamisított kérések elleni űrlaptokenekről lásd a CSRF-védelem PHP-alkalmazásokban cikket. További beépített eszközöket mutat be a A PHP beépített biztonsági funkciói.
Hogyan segít konkrétan a PHP SOS?
- Megkeressük az alkalmazásában az összes helyet, ahol tokeneket, kódokat és kulcsokat hoznak létre, és kicseréljük a nem biztonságos forrásokat.
- Ellenőrizzük a visszaállító, meghívó és megosztási linkek tárolását, lejáratát és egyszeri használatát.
- Gondoskodunk arról, hogy a tokenek ne kerüljenek naplókba, hibajelentésekbe vagy elemző eszközökbe.
- Egy incidens után Önnel együtt megtervezzük a tokenek és kulcsok rendezett cseréjét.
A saját PHP-szoftver ellenőrzését biztonsági auditként és penetrációs tesztként kínáljuk.
Gyakori kérdések
Miért alkalmatlan az mt_rand tokenekhez?
Az mt_rand és a rand statisztikához és játékokhoz való, nem titkokhoz. A kimeneteik adott körülmények között megjósolhatók. Mindenre, amit a támadó nem találhat ki, a random_bytes-t vagy a random_int-et használja.
Milyen hosszú legyen egy token?
Jelszó-visszaállító linkekhez, meghívókhoz vagy API-kulcsokhoz 32 véletlen bájt szilárd választás, hexa szövegként 64 karakter. A 16 bájt, vagyis 128 bit elterjedt alsó határ.
A uniqid biztonságos token?
Nem. A uniqid az aktuális időn alapul, és egyediségre való, nem titoktartásra. Az olyan kombinációk is megjósolhatók, mint az idő vagy a felhasználói azonosító md5-je.
Titkosítanom kell az adatbázisban a visszaállító tokeneket?
A token helyett annak hasht tárolja, például SHA-256-tal. Aki az adatbázist olvassa, ezzel nem tud érvényes linkeket építeni. Mivel maga a token már hosszú és véletlen, nincs szükség lassú eljárásra, mint a jelszavaknál.
Mi történik, ha a rendszer nem talál véletlenforrást?
A random_bytes és a random_int a PHP 8.2 óta Random\RandomException kivételt dob. Kapja el ezt az esetet, és szakítsa meg a folyamatot, ahelyett hogy gyengébb forrásra térne át.