Как да изберете произволна стойност от масив в PHP. Php random: Как да получите произволна стойност от масив с помощта на произволни числа

изберете елемент (11)

Имам масив, наречен $ran = array(1,2,3,4);

Трябва да получа произволна стойност от този масив и да я съхраня в променлива, как мога да направя това?

Отговори

Базирам отговора си на функцията на @OlafurWaage. Опитах се да го използвам, но се натъкнах на проблеми с помощта, когато се опитах да променя върнатия обект. Актуализирах функцията му за предаване и връщане по препратка. Нова функция:

Функция &random_value(&$array, $default=null) ( $k = mt_rand(0, count($array) - 1); if (isset($array[$k])) ( return $array[$k]; ) else ( return $default; ) )

За повече информация вижте моя въпрос в Предаване/връщане на препратки към обекти + Промяната на обект не работи

$стойност = $масив;

Можете да използвате функцията array_rand, за да изберете произволен ключ от вашия масив, както е показано по-долу.

$масив = масив("едно", "две", "три", "четири", "пет", "шест"); ехо $масив;

или можете да използвате функциите rand и count, за да изберете произволен индекс.

$масив = масив("едно", "две", "три", "четири", "пет", "шест"); ехо $масив;

$random = $ran;

Това също е полезно като функция, ако имате нужда от стойност

Функция random_value($array, $default=null) ( $k = mt_rand(0, count($array) - 1); return isset($array[$k])? $array[$k]: $default; )

Вашият избор има ли някакви последици за сигурността? Ако е така, използвайте random_int() и array_keys(). (random_bytes() е само PHP 7, но има polyfill за PHP 5).

Функция random_index(array $source) ( $max = count($source) - 1; $r = random_int(0, $max); $k = array_keys($source); return $k[$r]; )

Приложение:

$array = [ "apple" => 1234, "boy" => 2345, "cat" => 3456, "dog" => 4567, "echo" => 5678, "fortune" => 6789 ]; $i = случаен_индекс($масив); var_dump([$i, $масив[$i]]);

Функция array_random($array, $amount = 1) ( $keys = array_rand($array, $amount); if ($amount == 1) ( return $array[$keys]; ) $results = ; foreach ($keys като $key) ( $results = $array[$key]; ) върне $results; )

Приложение:

$items = ["foo", "bar", "baz", "lorem"=>"ipsum"]; array_random($items); // "лента" array_random($items, 2); // ["foo", "ipsum"]

Няколко бележки:

  • $сума трябва да е по-малка или равна на count($array).
  • array_rand() не разбърква ключовете (от PHP 5.2.10, вижте), така че избраните от вас елементи винаги ще бъдат в оригиналния ред. Използвайте shuffle(), ако е необходимо.

редактиране:Функцията Laravel нарасна значително оттогава, вижте Laravel 5.4"s Arr::random(). Ето нещо по-сложно, произтичащо от зряла функция на Laravel:

Функция array_random($array, $number = null) ( $requested = ($number === null) ? 1: $number; $count = count($array); if ($requested > $count) ( хвърляне на нов \ RangeException("Вие поискахте ($requested) елементи, но има само ($count) налични елементи."); ) if ($number === null) ( return $array; ) if ((int) $number == = 0) ( return ; ) $keys = (масив) array_rand($array, $number); $results = ; foreach ($keys като $key) ( $results = $array[$key]; ) return $results; )

Няколко акцента:

  • Хвърлете изключение, ако няма достатъчно налични елементи
  • array_random($array, 1) връща масив от един елемент (#19826)
  • Поддържана стойност "0" за брой елементи (Това е чисто и просто.

Вече няколко пъти ме питаха как съм случаен изход на кавичкина моя уебсайт в блока " Интелигентни цитатиСлед това успях да разбера, че проблемът тук е в неразбирането на хората, как да получите произволен елемент от масив в PHP. Задачата е проста, но въпреки това, тъй като възникват въпроси, те трябва да получат отговор.

Веднага ще ви дам кода. Да кажем, че има масив с набор от кавички. И трябва да изберете един произволен един от тях и да изведете:

$кавички = масив(); // Инициализиране на празен масив
$quotes = "Бъдете внимателни към мислите си, те са началото на действията."; // Първи цитат
$quotes = "Не оцеляват най-умните или най-силните, а най-възприемчивите към промяна."; // Втори цитат
$quotes = "Животът е планина: изкачваш се бавно, слизаш бързо."; // Трети цитат
$quotes = "Хората не искат да бъдат богати, хората искат да бъдат по-богати от другите."; // Четвърти цитат
$число = mt_rand(0, брой($кавички) - 1); // Вземете произволно число от 0 до (дължина на масива минус 1) включително
echo $quotes[$number]; // Изведете цитат
?>

Ключовият момент е получаване на произволно число. Всичко, което трябва да направите, е да поставите правилните граници. Ако трябва да изберете произволен елемент по цялата дължина на масива, тогава това е от 0 преди ( дължина на масива минус 1). И тогава просто издърпайте елемент от масива с получения произволен индекс.

Що се отнася до задачата с цитати, по-добре е да ги съхранявате в база данни. По принцип, ако сайтът е много прост, тогава може да се направи в текстов файл. Но ако в база данни, тогава е по-добре да се използва РАНД()И ОГРАНИЧЕНИЕ V SQL заявка, така че веднага да получите единична и произволна оферта от базата данни.


Случайните стойности са навсякъде в PHP. Във всички рамки, в много библиотеки. Вероятно сами сте написали много код, който използва произволни стойности за генериране на жетони и соли и като вход към функции. Също така, случайните стойности играят важна роля при решаването на различни проблеми:

  1. За произволен избор на опции от набор или набор от известни опции.
  2. За генериране на инициализиращи вектори за криптиране.
  3. За генериране на непредвидими жетони или еднократни стойности по време на оторизация.
  4. За генериране на уникални идентификатори, като идентификатори на сесии.

Във всички тези случаи има характерна уязвимост. Ако нападател познае или предвиди изхода на вашия генератор на произволни числа (RNG) или генератор на псевдо-случайни числа (PRNG), той ще може да изчисли токените, солите, nonces и векторите за криптографска инициализация, създадени от този генератор. Ето защо е много важно да се генерират висококачествени произволни стойности, т.е. такива, които са изключително трудни за прогнозиране. Никога не правете предсказуеми токените за повторно задаване на парола, CSRF токените, API ключовете, nonces или токените за оторизация!


Има две други потенциални уязвимости, свързани с произволни стойности в PHP:

  1. Разкриване на информация.
  2. Недостатъчна ентропия.

В този контекст „разкриване на информация“ се отнася до изтичане на вътрешното състояние на генератор на псевдослучайни числа – неговата начална стойност. Течове като тези могат да направят прогнозирането на бъдещи PRNG резултати много по-лесно.


„Липсата на ентропия“ описва ситуация, при която променливостта на първоначалното вътрешно състояние (семена) на PRNG или неговия изход е толкова малка, че целият диапазон от възможни стойности е относително лесен за груба сила. Не е много добра новина за PHP програмистите.


Ще разгледаме подробно и двете уязвимости с примерни сценарии за атака. Но първо, нека разберем какво всъщност е случайна стойност, когато става дума за програмиране на PHP.

Какво правят случайните стойности?

Объркването относно предназначението на случайните променливи се усложнява от общо неразбиране. Без съмнение сте чували за разликата между криптографски силни произволни стойности и неясни „уникални“ стойности „за други цели“. Основното впечатление е, че произволните стойности, използвани в криптографията, изискват висококачествена случайност (или по-точно висока ентропия), докато стойностите за други приложения могат да преминат с по-малко ентропия. Намирам това впечатление за погрешно и контрапродуктивно. Истинската разлика между непредсказуемите случайни стойности и тези, необходими за тривиални задачи, е, че предвидимостта на последните не води до вредни последици. Това напълно изключва криптографията от разглеждане. С други думи, ако използвате произволна стойност в нетривиален проблем, трябва автоматично да изберете много по-силни RNG.


Силата на произволните стойности се определя от ентропията, изразходвана за генерирането им. Ентропията е мярка за несигурност, изразена в "битове". Например, ако взема двоичен бит, неговата стойност може да бъде 0 или 1. Ако нападателят не знае точната стойност, тогава имаме ентропия от 2 бита (т.е. хвърляне на монета). Ако атакуващият знае, че стойността винаги е 1, тогава имаме ентропия от 0 бита, тъй като предвидимостта е антонимът на несигурността. Освен това броят на битовете може да варира от 0 до 2. Например, ако 99% от времето един двоичен бит е 1, тогава ентропията може да е малко по-голяма от 0. Така че колкото повече недефинирани двоични битове избираме, толкова по-добре.


В PHP това може да се види по-ясно. Функцията mt_rand() генерира произволни стойности, те винаги са числа. Не показва букви, специални знаци или други значения. Това означава, че за всеки байт нападателят има много по-малко предположения, т.е. ниска ентропия. Ако заменим mt_rand() с четене на байтове от източника на Linux /dev/random, получаваме наистина произволни байтове: те се генерират въз основа на шум, генериран от драйвери на системни устройства и други източници. Очевидно тази опция е много по-добра, защото осигурява значително повече битове ентропия.


Нежелаността на mt_rand() се посочва и от факта, че той не е генератор на наистина произволни, а псевдослучайни числа или, както се нарича още, детерминистичен генератор на произволни двоични последователности (Deterministic Random Bit Generator, DRBG ). Той прилага алгоритъм, наречен Mersenne Twister, който генерира числа, разпределени по такъв начин, че резултатът да е близък до резултата от истински генератор на случайни числа. mt_rand() използва само една случайна стойност - семената; въз основа на нея фиксиран алгоритъм генерира псевдослучайни стойности.


Разгледайте този пример, можете да го тествате сами:


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

Това е прост цикъл, изпълняван след като функцията PHP Mersenne vortex получи първоначална, предварително зададена стойност. Той беше получен от изхода на функцията, дадена като пример в документацията за mt_srand() и използвайки текущите секунди и микросекунди. Ако изпълните горния код, той ще покаже 25 псевдослучайни числа. Изглеждат произволни, няма съвпадения, всичко е наред. Нека стартираме кода отново. Забелязахте ли нещо? А именно: показват се ЕДНАКВИ числа. Нека го пуснем за трети, четвърти, пети път. В по-старите версии на PHP резултатът може да е различен, но това не е проблемът, тъй като е общ за всички съвременни версии на PHP.


Ако атакуващият получи началната стойност на такъв PRNG, тогава той ще може да предвиди целия изход на mt_rand(). Така че защитата на първоначалната стойност е от изключително значение. Ако го загубите, вече нямате право да генерирате произволни стойности...


Можете да генерирате първоначалната стойност по един от двата начина:

  • ръчно, като използвате функцията mt_srand(),
  • ще игнорирате mt_srand() и ще оставите PHP да го генерира автоматично.

Вторият вариант е за предпочитане, но дори и днес наследените приложения често наследяват използването на mt_srand(), дори след пренасяне към по-модерни версии на PHP.


Това увеличава риска нападателят да възстанови първоначалната стойност (Seed Recovery Attack), което ще му даде достатъчно информация, за да предвиди бъдещи стойности. В резултат на това всяко приложение след такова изтичане става уязвимо за атака за разкриване на информация. Това е истинска уязвимост, въпреки привидно пасивната си природа. Изтичането на информация за локалната система може да помогне на нападателя при последващи атаки, които ще нарушат принципа на защита в дълбочина.

Случайни стойности в PHP

PHP използва три PRNG и ако атакуващият получи достъп до семената, използвани в техните алгоритми, той ще може да предвиди резултатите от тяхната работа:

  1. Линеен конгруентен генератор (LCG), lcg_value().
  2. Мерсенов вихър, mt_rand() .
  3. Локално поддържана C функция rand().

Тези генератори се използват и вътрешно, за функции като array_rand() и uniqid(). Това означава, че атакуващият може да предвиди изхода на тези и други функции, които използват вътрешните PRNG на PHP, ако получат всички необходими семена. Това също означава, че не можете да подобрите защитата си, като обърквате нападателя с множество обаждания към генераторите. Това важи особено за приложения с отворен код. Нападателят е в състояние да предвиди ВСИЧКИ изходи за всяка начална стойност, която му е известна.


За да подобри качеството на генерираните произволни стойности за нетривиални задачи, PHP се нуждае от външни източници на ентропия, предоставени от операционната система. В Linux обикновено се използва /dev/urandom, можете да го прочетете директно или да получите индиректен достъп до него чрез функциите openssl_pseudo_random_bytes() или mcrypt_create_iv(). И двата могат да използват криптографски защитен генератор на псевдослучайни числа (CSPRNG) в Windows, но в PHP в потребителското пространство все още няма директен метод за получаване на данни от този генератор без разширенията, предоставени от тези функции. С други думи, уверете се, че вашата сървърна версия на PHP има активирано разширение OpenSSL или Mcrypt.


/dev/urandom е PRNG, но често получава нови семена от източника с висока ентропия /dev/random. Това го прави безинтересна цел за нападател. Опитваме се да избягваме четене директно от /dev/random, защото това е блокиращ ресурс. Ако ентропията му свърши, тогава всички четения ще бъдат блокирани, докато отново не получи достатъчно ентропия от системната среда. Въпреки че за най-важните задачи трябва да използвате /dev/random.


Всичко това ни води до правилото:


Всички процеси, които включват използването на нетривиални произволни числа, ТРЯБВА да използват openssl_pseudo_random_bytes(). Като алтернатива МОЖЕТЕ да опитате да прочетете байтове директно от /dev/urandom. Ако нито една от опциите не работи и нямате избор, тогава ТРЯБВА да генерирате стойността чрез силно смесване на данни от множество налични източници на произволни или тайни стойности.

Ще намерите основно изпълнение на това правило в референтната библиотека на SecurityMultiTool. Както обикновено, вътрешните елементи на PHP предпочитат да усложняват живота на програмистите, вместо директно да включват сигурни решения в ядрото на PHP.


Стига теория, сега да видим как можете да атакувате приложение, въоръжени с горното.

Атака срещу генератори на случайни числа в PHP

Поради редица причини PHP използва PRNG за решаване на нетривиални проблеми.


Функцията openssl_pseudo_random_bytes() беше достъпна само в PHP 5.3. В Windows причинява проблеми с блокирането, докато не излезе версия 5.3.4. Също така в PHP 5.3 функцията mcrypt_create_iv() на Windows започна да поддържа източника MCRYPT_DEV_URANDOM. Преди това Windows поддържаше само MCRYPT_RAND - по същество същият системен PRNG, използван вътрешно от функцията rand(). Както можете да видите, преди PHP 5.3 имаше много пропуски, така че много наследени приложения, написани в предишни версии, може да не са преминали към по-силни PRNG.


Изборът на Openssl и Mcrypt разширения е по ваша преценка. Тъй като не може да се разчита, че са налични дори на сървъри, работещи с PHP 5.3, приложенията често използват PRNG, вградени в PHP, като резервен вариант за генериране на нетривиални произволни стойности.


Но и в двата случая имаме нетривиални проблеми, които използват произволни стойности, генерирани от PRNG със семена с ниска ентропия. Това ни прави уязвими за атаки за извличане. Нека да разгледаме един прост пример.


Нека си представим, че намерихме онлайн приложение, което използва следния код за генериране на токени, които се използват в различни задачи в приложението:


$token = хеш("sha512", mt_rand());

Има по-сложни средства за генериране на токени, но това е добър вариант. Има само едно извикване на mt_rand(), хеширано с SHA512. На практика, ако програмист реши, че функциите за произволна стойност на PHP са „достатъчно произволни“, тогава той най-вероятно ще избере опростения подход, докато не се спомене думата „криптография“. Например некриптографските случаи включват токени за достъп, CSRF токени, еднократни стойности на API и токени за повторно задаване на парола. Преди да продължа, ще опиша подробно пълната степен на уязвимостта на това приложение, така че да разберете по-добре какво прави приложенията уязвими на първо място.

Характеристики на уязвимото приложение

Това не е изчерпателен списък. На практика списъкът с характеристики може да се различава!

1. Сървърът използва mod_php, който, когато се използва с KeepAlive, позволява множество заявки да бъдат обслужвани от един и същ PHP процес

Това е важно, тъй като генераторите на произволни числа в PHP се поставят само веднъж на процес. Ако можем да направим две или повече заявки към процеса, тогава той ще използва същата първоначална стойност. Същността на атаката е да се използва разширяването на един токен за извличане на началната стойност, която е необходима за прогнозиране на друг токен, генериран въз основа на СЪЩАТА начална стойност (т.е. в същия процес). Тъй като mod_php е идеален за използване на множество заявки за извличане на свързани произволни стойности, понякога може да е възможно да се извлекат множество стойности, свързани с mt_rand() само с една заявка. Това прави всички изисквания на mod_php излишни. Например, част от ентропията, използвана за генериране на семената за mt_rand(), може да изтече през идентификатори на сесии или изходни стойности в същата заявка.

2. Сървърът разкрива CSRF токени, токени за нулиране на парола или токени за потвърждение на акаунт, генерирани въз основа на токени mt_rand().

За да извлечем началната стойност, трябва директно да проверим числото, генерирано от генераторите в PHP. И дори няма значение как се използва. Можем да го извлечем от всяка налична стойност, било то резултат от mt_rand(), или хеширан CSRF, или токен за потвърждение на акаунт. Подходящи са дори индиректни източници, при които произволна стойност определя различно поведение на изхода, което разкрива точно тази стойност. Основното ограничение е, че трябва да е от същия процес, който генерира втория токен, който се опитваме да предвидим. И това е уязвимост на „разкриване на информация“. Както скоро ще видим, изтичането на PRNG изход може да бъде изключително опасно. Имайте предвид, че уязвимостта не е ограничена до едно приложение: можете да прочетете PRNG изхода в едно приложение на сървъра и да го използвате, за да определите изхода в друго приложение на същия сървър, стига и двете да използват един и същ PHP процес.

3. Известен слаб алгоритъм за генериране на токени

Можете да го изчислите:

  • след като се задълбочихте в източниците на приложението с отворен код,
  • подкупване на служител с достъп до личен изходен код,
  • намиране на бивш служител, който таи злоба към бившия си работодател,
  • или просто познайте какъв алгоритъм може да има.

Някои методи за генериране на токени са по-очевидни, други са по-популярни. Наистина слабо генериране означава да използвате един от генераторите на случайни числа на PHP (напр. mt_rand()), слаба ентропия (няма други източници на недефинирани данни) и/или слабо хеширане (напр. MD5 или никакво хеширане). Примерът за код, обсъден по-горе, има отличителните белези на слаб метод за генериране. Използвах също хеширане на SHA512, за да покажа, че маскирането винаги е незадоволително решение. SHA512 е слабо хеширане, тъй като е бърз за изчисляване, което означава, че нападателят може грубо да форсира входните данни на всеки CPU или GPU с невероятна скорост. И не забравяйте, че законът на Мур също все още е в сила, което означава, че скоростта на грубата сила ще се увеличава с всяко ново поколение CPU/GPU. Следователно паролите трябва да се хешират с помощта на инструменти, които отнемат фиксирано време за разбиване, независимо от производителността на процесора или закона на Мур.

Извършване на атака

Нашата атака е съвсем проста. Като част от свързването към PHP процес, ние ще проведем бърза сесия и ще изпратим две отделни HTTP заявки (заявка A и заявка B). Сесията ще бъде задържана от сървъра до получаване на втората заявка. Заявка A е насочена към получаване на някакъв наличен токен като CSRF, токен за нулиране на парола (изпратен до нападателя по пощата) или нещо подобно. Не забравяйте за други функции като вградено маркиране, използвано при заявки за произволни идентификатори и т.н. Ще измъчваме оригиналния токен, докато не ни даде първоначалната си стойност. Всичко това е част от атака за възстановяване на семето: когато семето има толкова малка ентропия, че може да бъде грубо форсирано или потърсено в предварително изчислена дъгова таблица.


Заявка B ще реши по-интересен проблем. Нека направим заявка за нулиране на паролата на локалния администратор. Това ще задейства генерирането на токен (използвайки произволно число, базирано на същото семе, което изтегляме с Заявка А, ако и двете заявки са изпратени успешно до един и същ PHP процес). Този токен ще се съхранява в базата данни в очакване на момента, в който администраторът използва връзката за нулиране на паролата, изпратена му по имейл. Ако можем да извлечем началната стойност за токена от заявка A, тогава знаейки как се генерира токенът от заявка B, можем да предвидим токена за нулиране на паролата. Това означава, че можем да последваме връзката за нулиране, преди администраторът да прочете писмото!


Ето последователността от събития:

  1. Използвайки заявка A, получаваме токена и го проектираме обратно, за да изчислим първоначалната стойност.
  2. Използвайки заявка B, получаваме токен, генериран въз основа на същата първоначална стойност. Този токен се съхранява в базата данни на приложението за бъдещо нулиране на парола.
  3. Разбиваме хеша на SHA512, за да получим произволното число, генерирано от сървъра.
  4. Използвайки получената произволна стойност, ние грубо форсираме първоначалната стойност, която е генерирана с негова помощ.
  5. Ние използваме семената, за да изчислим поредица от произволни стойности, които вероятно могат да формират основата на токен за нулиране на парола.
  6. Ние използваме този токен(а) за нулиране на администраторската парола.
  7. Получаваме достъп до администраторския акаунт, забавляваме се и печелим. Е, поне се забавляваме.

Да започнем да хакваме...

Стъпка по стъпка хакване на приложения

Стъпка 1. Направете заявка A за извличане на токена

Предполагаме, че целевият токен и токенът за нулиране на парола зависят от изхода на mt_rand(). Следователно трябва да го изберете. В приложението в нашия въображаем сценарий всички токени се генерират по един и същи начин, така че можем просто да извлечем CSRF токена и да го запазим за по-късно.

Стъпка 2. Изпълнете заявка B, за да получите токена за нулиране на парола, генериран за администраторския акаунт

Тази заявка е просто изпращане на формуляр за нулиране на парола. Токенът ще бъде записан в базата данни и изпратен на потребителя по пощата. Трябва да изчислим правилно този токен. Ако спецификациите на сървъра са точни, тогава заявка B използва същия PHP процес като заявка A. Следователно извикванията към mt_rand() ще използват същата начална стойност и в двата случая. Можете дори да използвате Заявка A, за да заснемете CSRF токена на формуляра за нулиране, за да разрешите подаването с цел рационализиране на процедурата (избягване на междинното двупосочно пътуване).

Стъпка 3. Хакнете SHA512 хеша на токена, получен от заявка A

SHA512 вдъхва страхопочитание сред програмистите: той има най-големия брой в цялото семейство алгоритми SHA-2. Има обаче един проблем с метода за генериране на токени на нашата жертва - произволните стойности са ограничени само до числа (т.е. степента на несигурност или ентропията е незначителна). Ако проверите изхода на mt_getrandmax(), ще откриете, че най-голямото произволно число, което mt_rand() може да генерира, е 2,147 милиарда и известна промяна. Този ограничен брой функции прави SHA512 уязвим на груба сила.


Просто не ми вярвай на думата. Ако имате дискретна видеокарта от едно от най-новите поколения, тогава можете да отидете по следния начин. Тъй като търсим единичен хеш, реших да използвам чудесен инструмент за груба сила - hashcat-lite. Това е една от най-бързите версии на hashcat и е достъпна за всички основни операционни системи, включително Windows.


Използвайте този код, за да генерирате токен:


$rand = mt_rand(); echo "Случайно число: ", $rand, PHP_EOL; $token = хеш ("sha512", $rand); echo "Token: ", $token, PHP_EOL;

Този код възпроизвежда токена от заявка A (съдържа произволното число, от което се нуждаем, и е скрито в хеша на SHA512) и го изпълнява през 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

Ето какво означават всички тези опции:

  • -m1700: Указва алгоритъма за хеширане, където 1700 означава SHA512.
  • --pw-min=1: Определя минималната входна дължина на хешираната стойност.
  • --pw-max=10: Дефинира максималната входна дължина на хешираната стойност (10 за mt_rand()).
  • -1?d: указва, че имаме нужда от персонализиран речник само с числа (т.е. 0-9).
  • -o ./seed.txt: файл за запис на резултатите. Нищо не се показва на екрана, така че не забравяйте да зададете тази опция!
  • ?d?d?d?d?d?d?d?d?d?d: маска, която указва формата, който да се използва (всички цифри до максимум 10).

Ако всичко работи правилно и графичният ви процесор не се стопи, Hashcat ще изчисли хешираното произволно число след няколко минути. Да, минути. По-рано обясних как работи ентропията. Вижте сами. Функцията mt_rand() има толкова малко възможности, че SHA512 хешовете на всички стойности всъщност могат да бъдат изчислени за много кратко време. Така че нямаше смисъл да се хешира изхода на mt_rand().

Стъпка 4. Възстановете първоначалната стойност, като използвате прясно хакнато произволно число

Както видяхме по-горе, отнема само няколко минути, за да извлечете всяка стойност, генерирана от mt_rand() от SHA512. Въоръжени с произволна стойност, можем да стартираме друг груб инструмент - php_mt_seed. Тази малка помощна програма взема резултата от mt_rand() и след груба сила изчислява първоначалната стойност, от която би могъл да бъде генериран анализът. Изтеглете текущата версия, компилирайте и стартирайте. Ако имате проблеми с компилацията, опитайте по-стара версия (имах проблеми с виртуалните среди с новите).


./php_mt_seed

Това може да отнеме малко повече време от кракването на SHA512, тъй като се извършва на процесора. На приличен процесор помощната програма ще намери целия възможен диапазон на първоначалната стойност за няколко минути. Резултатът е една или повече възможни стойности (т.е. стойностите, които биха могли да бъдат използвани за получаване на случайно число). Отново виждаме резултата от слаба ентропия, само този път във връзка с генерирането на начални стойности от PHP за функцията Mersenne vortex. Ще разгледаме как са генерирани тези стойности по-късно, така че ще разберете защо грубата сила може да бъде направена толкова бързо.


И така, преди това използвахме прости хакерски инструменти, налични в мрежата. Те са насочени към извиквания на mt_rand(), но илюстрират идея, която може да се приложи към други сценарии (например последователни извиквания на mt_rand() при генериране на токени). Също така имайте предвид, че скоростта на хакване не предотвратява генерирането на дъгови таблици, които вземат предвид специфични подходи за генериране на токени. Ето още един инструмент, който използва уязвимостите на mt_rand() и е написан на Python.

Стъпка 5. Генерирайте възможни маркери за нулиране на парола за администраторски акаунт

Да приемем, че в заявките A и B са направени само две заявки към mt_rand(). Сега нека започнем да предвиждаме токени, като използваме предварително изчислените възможни начални стойности:


функция predict($seed) ( /** * Предаване на първоначалната стойност на PRNG */ mt_srand($seed); /** * Пропускане на извикването на функция от заявка A */ mt_rand(); /** * Прогнозиране и връщане на един генериран в токена B на заявка */ $token = hash("sha512", mt_rand()); return $token; )

Тази функция предвижда нулиране на токен за всяко възможно семе.

Стъпки 6 и 7: Нулирайте паролата на вашия администраторски акаунт и се забавлявайте!

Сега трябва да съберете URL адрес, съдържащ токен, който ще ви позволи да нулирате администраторската парола поради уязвимостта на приложението и да получите достъп до вашия акаунт. Може да откриете, че можете да публикувате нефилтриран HTML във форум или в статия (често срещано нарушение на принципа на защита в дълбочина). Това ще ви позволи да извършите обширна XSS атака на всички други потребители на приложението, заразявайки компютрите им със злонамерен софтуер и мониторинг Man-In-The-Browser. Сериозно, защо да спрем само с получаване на достъп? Целта на тези привидно пасивни и не много опасни уязвимости е да помогнат на нападателя бавно да проникне до мястото, където най-накрая може да постигне основната си цел. Хакването е като да играете аркадна бойна игра, където трябва бързо да натиснете правилната комбинация, за да отприщите серия от мощни атаки.

Анализ след нападение

Горният сценарий и простотата на стъпките трябва ясно да ви демонстрират опасностите от mt_rand(). Рисковете са толкова очевидни, че сега можем да считаме всички слабо скрити изходни стойности на mt_rand(), достъпни за нападател под каквато и да е форма, за уязвимост на „разкриване на информация“.


Освен това тази история има и втора страна. Например, ако зависиш от библиотека, която невинно използва mt_rand() за някои важни задачи, дори без да дава получените стойности, тогава като използваш „изтекъл“ токен за твоите нужди, ще компрометираш тази библиотека. И това е проблем, защото библиотеката или рамката не прави нищо, за да смекчи атаката за възстановяване на първоначалната стойност. Трябва ли да обвиняваме потребителя за изтичане на стойности на mt_rand() или библиотеката, че не е приложила по-добри произволни стойности?


Всъщност и двамата са доста виновни. Библиотеката не трябва да избира mt_rand() (или друг единичен източник на слаба ентропия) за важни проблеми като единствен източник на произволни стойности. И потребителят не трябва да пише код, който пропуска стойности на mt_rand(). Така че да, можете да започнете да сочите с обвинителен пръст неграмотни примери за използване на mt_rand(), дори ако това не води до директни течове.


Трябва да се тревожите не само за уязвимостите при разкриване на информация. Също така е важно да сте наясно с липсата на ентропийни уязвимости, които правят приложенията уязвими на груба сила на чувствителни токени, ключове или nonces, които технически не са криптографски, но се използват при работата на нетривиални функции на приложението.

И сега всичко е същото

Вече знаем, че използването на PRNG, вградени в PHP, се счита за липса на ентропийна уязвимост (т.е. намаляването на несигурността улеснява грубата сила). Можем да разширим нашата атака:


Уязвимостта при разкриване на информация прави този метод за генериране на токени напълно безполезен. За да разберем защо, нека разгледаме по-подробно функцията uniqid() на PHP. Определението му:


Въз основа на текущото време в микросекунди, получава уникален префиксен идентификатор.


Както си спомняте, ентропията е мярка за несигурност. Поради уязвимост при разкриване на информация, стойностите, генерирани от mt_rand(), могат да бъдат изтекли, така че използването на mt_rand() като префикс на уникален идентификатор добавя нулева несигурност. В нашия пример единственият друг вид въвеждане на uniqid() е времето. Но определено НЕ е неясно. Променя се линейно и предвидимо. А предвидимите стойности имат изключително ниска ентропия.


Разбира се, определението се отнася до "микросекунди", т.е. милионни от секундата. Това ни дава 1 000 000 възможни числа. Тук пренебрегвам стойности, по-големи от 1 секунда, защото тяхната част и измеримост са толкова големи (например заглавката на HTTP Date в отговор), че не дава почти нищо. Преди да навлезем в подробности, нека анализираме функцията uniqid() и да разгледаме нейния C код:


gettimeofday((struct timeval *) &tv, (struct timezone *) NULL); sec = (int) tv.tv_sec; usec = (int) (tv.tv_usec % 0x100000); /* usec може да има максимална стойност 0xF423F, така че използваме * usecs само пет шестнадесетични числа. */ 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", префикс, sec, usec); ) RETURN_STRING(uniqid, 0);

Ако това изглежда твърде сложно, можете да копирате всичко в добрия стар PHP:


функция 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, $ сек, $usec); ) )

Този код ни казва, че простото извикване на uniqid() без параметри ще ни върне низ от 13 знака. Първите 8 знака са текущото времево клеймо на Unix (в секунди), изразено в шестнадесетичен формат. Последните 5 знака са допълнителни микросекунди в шестнадесетична система. С други думи, основната функция uniqid() осигурява много точно измерване на системното време, което може да бъде извлечено от просто извикване на uniqid() с код като този:


$id = uniqid(); $време = str_split($id, 8); $sec = hexdec("0x". $time); $usec = hexdec("0x". $time); echo "Секунди: ", $sec, PHP_EOL, "Микросекунди: ", $usec, PHP_EOL;

Вижте C кода. Точното системно време никога не е скрито в изхода, независимо от параметрите:


ехо uniqid(), PHP_EOL; // 514ee7f81c4b8 echo uniqid("prefix-"), PHP_EOL; // prefix-514ee7f81c746 echo uniqid("prefix-", true), PHP_EOL; // префикс-514ee7f81c8993.39593322

Груба сила уникални идентификатори

След размисъл става очевидно, че разкриването на стойност на uniqid() на нападател е друг пример за потенциална уязвимост при разкриване на информация. Това изтича много прецизно системно време, което може да се използва за прогнозиране на въвеждане за последващи uniqid() извиквания. Това помага за разрешаването на всякакви дилеми, които възникват, когато се опитвате да предвидите микросекунди, като стесните 1 000 000 възможности до по-тесен диапазон. Тъй като това изтичане можеше да бъде споменато по-късно, то технически не е необходимо в нашия пример. Нека да разгледаме отново оригиналния код на uniqid() токен:


$token = хеш("sha512", uniqid(mt_rand()));

От този пример можем да видим, че чрез извършване на атака за нулиране на mt_rand() в комбинация с разкриване на информация от uniqid() ще изчислим сравнително малък набор от SHA512 хешове, които може да се окажат нулиране на парола или други важни токени. Ако се нуждаете от тесен диапазон от времеви марки, без да използвате изтичането на системно време от uniqid(), тогава нека анализираме отговорите на сървъра, които обикновено съдържат HTTP заглавка на датата. От тук можете да получите точните времеви клейма на сървъра. И тъй като в този случай ентропията е равна на един милион възможни микросекундни стойности, можете да го направите брутфорс за няколко секунди!


Увеличаването на ентропията ще ни спаси ли?

Разбира се, възможно е да добавите ентропия към uniqid(), като зададете втория параметър на функцията на TRUE:


Както показва C кодът, новият източник на ентропия използва изхода на вътрешната функция php_combined_lcg(). Тази функция е изложена на потребителското пространство чрез функцията lcg_value(), която използвах в моето PHP преобразуване на функцията uniqid(). По същество той комбинира две стойности, генерирани от два линейни конгруентни генератора с отделни начални стойности. По-долу е кодът, който доставя на генераторите тези първоначални стойности. Както при mt_rand(), те се генерират веднъж за PHP процес и се използват повторно във всички следващи извиквания.


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; }

Ако го гледате твърде дълго и искате да хвърлите нещо по монитора, тогава по-добре не го правете. В наши дни мониторите са скъпи.


И двете първоначални стойности използват функцията C gettimeofday(), за да уловят текущото време в секунди и микросекунди от епохата на Unix (позовавайки се на часовника на сървъра). Трябва да се отбележи, че и двете извиквания са имплементирани в изходния код, така че стойността на брояча microsecond() между тях ще бъде минимална, което намалява въведената несигурност. Втората първоначална стойност също ще бъде смесена в идентификатора на текущия процес, който в повечето случаи под Linux няма да надвишава 32 768. Разбира се, можете ръчно да увеличите ограничението до около 4 милиона, като промените /proc/sys/kernel/pid_max , но това е много нежелателно.


Оказва се, че основният източник на ентропия, използван от тези LCG, са микросекунди. Например, помните ли първоначалната ни стойност mt_rand()? Познайте как се изчислява.


#ifdef PHP_WIN32 #define GENERATE_SEED() (((long) (time(0) * GetCurrentProcessId())) ^ ((long) (1000000.0 * php_combined_lcg(TSRMLS_C)))) #else #define GENERATE_SEED() (((long ) (време(0) * getpid())) ^ ((дълъг) (1000000.0 * php_combined_lcg(TSRMLS_C)))) #endif

Това означава, че всички първоначални стойности, използвани в PHP, са взаимозависими. Дори едни и същи входни данни се смесват няколко пъти. Може би бихте могли да ограничите диапазона от начални микросекунди, както обсъдихме по-горе: като използвате две заявки, като първата прескача между секунди (така че микровремето ще бъде 0 + времето за изпълнение на следващото gettimeofday() C извикване). Можете дори да изчислите микросекундната делта между други извиквания на gettimeofday(), ако имате достъп до изходния код (отвореният код на PHP помага). Да не говорим, че грубото форсиране на семето с mt_rand() ви дава окончателно семе, което позволява офлайн проверка.


Основният проблем обаче е в php_combined_lcg() . Това е имплементация в потребителско пространство на ниско ниво на функцията lcg_value(), която получава първоначалната стойност веднъж за PHP процес. И ако знаете тази стойност, тогава можете да предвидите изхода. Ако счупите този орех, това е, играта свърши.

Има приложение за това...

Прекарах много време, фокусирайки се върху практически неща, така че нека се върнем към тях отново. Не е толкова лесно да се получат двете първоначални стойности, използвани от php_combined_lcg() - може да не е възможно да се създаде директно изтичане. Функцията lcg_value() е относително малко известна и програмистите по-често разчитат на mt_rand(), когато имат нужда от PRNG, вграден в PHP. Не искам да попреча на lcg_value() да изтече стойността, но това е непопулярна функция. Двойката комбинирани LCG също не отразява функцията за зареждане (така че не можете просто да търсите извиквания към mt_srand(), за да идентифицирате изтичащ механизъм за зареждане, наследен от нечий наследен код). Въпреки това има надежден източник, който директно предоставя данни за семена за грубо форсиране: идентификатори на сесии в PHP.


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);

Този код генерира предварителна хеш стойност за идентификатора на сесията, като използва IP, клеймо за време, микросекунди и... php_combined_lcg() изход. Като се има предвид значителното намаляване на броя на микро-времевите възможности (този код се нуждае от 1 за генериране на ID и 2 за php_combined_lcg(), което води до минимална разлика между тях), сега можем да го форсираме грубо. Е, вероятно.


Както може би си спомняте, PHP вече поддържа нови опции за сесии като session.entropy_file и session.entropy_length. Това се прави, за да се предотвратят груби идентификационни номера на сесии, по време на които можете бързо (няма да отнеме часове) да получите две първоначални стойности за LCG генератори, комбинирани с помощта на php_combined_lcg(). Ако използвате PHP 5.3 или по-ниска версия, може да сте конфигурирали тези опции неправилно. Това означава, че има друга полезна уязвимост при разкриване на информация, която ви позволява да използвате груба сила на ID на сесии, за да получите първоначални стойности за LCG.


За такива случаи има приложение за Windows, което ви позволява да изчислявате LCG стойности.


Между другото, познаването на състоянията на LCG ви позволява да разберете как mt_rand() получава първоначалната стойност, така че това е друг начин да заобиколите липсата на изтичане на стойност на mt_rand().


Какво означава всичко това по отношение на добавянето на ентропия към върнатите стойности на uniqid()?


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

Това е друг пример за потенциалната уязвимост на липсата на ентропия. Не можете да разчитате на ентропия с течове (дори и да не носите отговорност за тях!). Благодарение на изтичането на информация за идентификатора на сесията, атакуващият може също да предвиди стойността на ентропията, която е била допълнително добавена към този идентификатор.


Отново кой е виновен? Ако приложението X разчита на uniqid(), но потребител или друго приложение на същия сървър изпуска вътрешно LCG състояние, тогава трябва да предприемете действие и в двете ситуации. Потребителите трябва да са сигурни, че идентификаторите на сесии използват достатъчно висока ентропия, а програмистите на трети страни трябва да разберат, че техните методи за генериране на произволни стойности нямат ентропия, така че трябва да преминат към по-подходящи алтернативи (дори ако са налични само източници на слаба ентропия!) .

В търсене на ентропия

PHP сам по себе си не е в състояние да генерира силна ентропия. Няма дори основен API за прехвърляне на данни от PRNG генератори на ниво операционна система, които са надеждни източници на силна ентропия. Следователно трябва да разчитате на опционалните разширения openssl и mcrypt. Те предлагат функции, които са много по-добри от техните пропускливи, предсказуеми братовчеди с ниска ентропия.


За съжаление, тъй като и двете разширения не са задължителни, в някои случаи нямаме друг избор, освен да разчитаме на слаби източници на ентропия като последна мярка. Когато това се случи, слабата ентропия на mt_rand() трябва да бъде допълнена с допълнителни източници на несигурност, смесвайки техните данни в един пул, от който могат да се изтеглят псевдослучайни байтове. Подобен произволен генератор, използващ силен миксер на ентропия, вече е внедрен от Антъни Ферара в неговата библиотека RandomLib. Това е, което програмистите трябва да правят, когато е възможно.


Избягвайте изкушението да скриете слабостта на вашата ентропия чрез хеширане на сложни математически трансформации. Нападателят ще повтори всичко това веднага щом открие първичната начална стойност. Такива трикове само леко ще увеличат количеството изчисления по време на груба сила. Не забравяйте: колкото по-ниска е ентропията, толкова по-малко несигурност; Колкото по-малко е несигурността, толкова по-малко възможности трябва да бъдат грубо форсирани. Единственото оправдано решение е да увеличите запаса от ентропия, който използвате, с всякакви налични средства.


Библиотеката RandomLib генерира произволни байтове чрез смесване на данни от различни източници на ентропия и локализиране на информация, която може да е необходима на атакуващия, за да направи предположения. Например, можете да смесите изходите на mt_rand(), uniqid() и lcg_value(), да добавите PID, потребление на памет, някакво друго измерване на микровреме, $_ENV сериализация, posix_times() и т.н. Или отидете дори по-далеч, това позволява RandomLib разтегливост. Да кажем, че използваме някои делти в микросекунди (т.е. измерваме колко микросекунди са нужни на функция, за да работи с псевдослучайни входни данни като извиквания на hash().

Добави тагове
  • от:
  • Регистриран: 2014.07.07
  • Публикации: 3,775
  • Просто харесвам PunBB:
  • 5 години, 6 месеци,
  • харесвания: 463

Тема: Как да изберете произволна стойност от масив в PHP

Използвайки тази функция, можем да изберем произволен елемент (или елементи) от масива. Да, точно елементът или елементите! Това може да е един елемент или може да има няколко. Всичко зависи от задачата, пред която сте изправени.

Тук обаче трябва да се има предвид, че функцията няма да върне стойността на елемента, а неговия ключ (или ключове, ако има няколко елемента).

Функцията приема като параметри в скоби: името на масива, с който работим и броя на елементите, които трябва да бъдат избрани.

Като цяло всичко е просто! И ще бъде още по-лесно, когато разгледаме всичко това с примери.

Нека първо изберем един произволен елемент от масива.

2 Отговор от PunBB

  • от: Москва, Совхознай 3, ап. 98
  • Регистриран: 2014.07.07
  • Публикации: 3,775
  • Просто харесвам PunBB:
  • 5 години, 6 месеци,
  • харесвания: 463

Нека си представим, че някъде в горната част на нашия уебсайт искаме да покажем някои цитати. Разбира се, кавичките трябва да се променят. Всеки път, когато потребител посети вашия сайт, вие искате потребителят да види нова оферта.

Както вероятно се досещате, най-лесният начин да приложите това е да поставите всички налични цитати и поговорки в масив и след това да изберете произволен елемент от този масив и да го покажете на екрана.

Колкото повече кавички имате в масива, толкова по-малка е вероятността да се повторят.

Но за пример, няма да се занимавам много и ще поставя 7 поговорки в моя масив.

След това ще трябва да създам променлива, в която ще съхранявам резултата от функцията array_rand(). В скоби тази функция ще има два аргумента: името на нашия масив и броя произволни елементи, от които се нуждаем.

Както вече казах, функцията не връща стойността на елемента, а неговия ключ (или номер в списъка). Така ключът на произволния елемент ще бъде съхранен в променливата.

След това просто трябва да покажа стойността на желания елемент. За да направя това, посочвам името на масива и в квадратни скоби името на нашата променлива, която съдържа произволен ключ.

Това е всичко. Вижте кода по-долу и мисля, че ще разберете всичко напълно:

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

3 Отговор от PunBB

  • от: Москва, Совхознай 3, ап. 98
  • Регистриран: 2014.07.07
  • Публикации: 3,775
  • Просто харесвам PunBB:
  • 5 години, 6 месеци,
  • харесвания: 463

Re: Как да избера произволна стойност от масив в PHP

Сега нека се упражним в отпечатването на няколко елемента от произволен масив.

При един елемент се връща неговият ключ, а при няколко елемента от произволен масив се връща масив от ключове. Това е, от което ще започнем, когато показваме на екрана.

Първо, нека създадем масив, в който ще добавим 7 различни имена.

След това създаваме променлива, в която ще бъде записана работата на функцията array_rand(). Едва сега в скоби за тази функция посочваме числото „2“ като втори аргумент. Това ще означава, че имаме нужда от 2 произволни елемента.

Резултатът от функцията в тази ситуация ще бъде масив, съдържащ два произволни ключа от елементи от основния ни масив.

Следователно, когато показвате на екрана, трябва да вземете това предвид и да посочите в квадратни скоби не само името на променливата, а името на променливата, след това квадратни скоби и индекса на масива. Тъй като имаме 2 елемента, в първия случай индексът ще бъде , а във втория . (Помните, че индексирането в масиви започва от "0".)

Това е всичко. Разгледайте кода, за да направите нещата по-ясни:

$names = array("Маша", "Саша", "Надя", "Мила", "Андрей", "Сергей", "Антон"); $rand_names = array_rand($names,2); ехо "

".$names[$rand_names]." и ".$names[$rand_names]."

";

В резултат на това на екрана ще се покажат две произволни имена. Всеки път, когато страницата се опреснява, имената ще се променят.

Източник



Свързани публикации