Як PHP вибрати випадкове значення з масиву. Php рандомний: Як отримати випадкове значення з масиву Використання випадкових чисел

вибрати елемент (11)

У мене є масив із ім'ям $ran = array(1,2,3,4);

Мені потрібно отримати випадкове значення з цього масиву та зберегти його у змінній, як я можу це зробити?

Answers

Я ґрунтую свою відповідь на функції @ÓlafurWaage. Я спробував використати його, але зіткнувся з довідковими проблемами, коли спробував змінити об'єкт, що повертається. Я оновив його функцію для передачі та повернення за посиланням. Нова функція:

Function &random_value(&$array, $default=null) ( $k = mt_rand(0, count($array) - 1); if (isset($array[$k]))) ( return $array[$k]; ) else ( return $default; ) )

Для отримання додаткової інформації див. Моє питання у розділі «Передача / Повернення посилань на об'єкт + зміна об'єкта» не працює

$ value = $ array;

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

$array = array("one", "two", "three", "four", "five", "six"); echo $array;

або можна використовувати функції rand і count для вибору випадкового індексу.

$array = array("one", "two", "three", "four", "five", "six"); echo $array;

$ random = $ ran;

Це також корисно, як функція, якщо вам потрібно значення

Function 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, але для PHP 5 існує поліполк).

Function 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 = random_index($array); var_dump([$i, $array[$i]]);

Function array_random($array, $amount = 1) ( $keys = array_rand($array, $amount); if ($amount == 1) ( return $array[$keys]; ) $results = ; foreach ($keys as $key) ( $results = $array[$key]; ) return $results; )

Застосування:

$items = ["foo", "bar", "baz", "lorem" => "ipsum"]; array_random($items); // "bar" array_random($items, 2); // ["foo", "ipsum"]

Декілька приміток:

  • $amount повинна бути меншою або дорівнює count($array) .
  • array_rand() не перетасовує ключі (починаючи з PHP 5.2.10, див.), тому вибрані вами елементи завжди будуть в оригінальному порядку. За потреби використовуйте shuffle() .

edit:Функція Laravel помітно зросла з тих пір, див. Laravel 5.4"s Arr::random() .

Function array_random($array, $number = null) ( $requested = ($number === null) ? 1: $number; $count = count($array); if ($requested > $count) ( throw new \ RangeException("You requested ($requested) items, but there are only ($count) items available."); ) if ($number === null) ( return $array; ) if ((int) $number == = 0) ( return ; ) $keys = (array) array_rand($array, $number); $results = ; )

Декілька основних моментів:

  • Викинути виняток, якщо не вистачає доступних предметів
  • array_random($array, 1) повертає масив з одного елемента (#19826)
  • Значення підтримки «0» для кількості елементів (це просто чисто і просто.

У мене вже питали кілька разів, як я роблю випадкове виведення цитату себе на сайті в блоці Розумні цитати". Далі мені вдалося з'ясувати, що проблема тут з нерозумінням людей, як вивести випадковий елемент з масиву в PHP. Завдання просте, але тим не менше, якщо виникають питання, то треба на них відповідати.

Відразу наведу код. Припустимо, є масив із набором цитат. І потрібно вибрати одну випадкову з них та вивести:

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

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

Що ж до завдання з цитатами, їх краще зберігати у базі даних. В принципі, якщо сайт дуже простий, то можна і в текстовому файлі. Але якщо у базі даних, то краще використовувати RAND()і LIMITв SQL-запит, щоб Ви відразу отримували єдину та випадкову цитату з бази даних.


Випадкові значення в PHP всюди. У всіх фреймворках, у багатьох бібліотеках. Ймовірно, ви самі написали купу коду, що використовує випадкові значення для генерування токенів і солей, а також як вхідні дані для функцій. Також випадкові значення відіграють важливу роль при вирішенні різних завдань:

  1. Для випадкового вибору опцій із пулу або діапазону відомих опцій.
  2. Для створення векторів ініціалізації при шифруванні.
  3. Для генерування непередбачуваних токенів або одноразових значень авторизації.
  4. Для створення унікальних ідентифікаторів, наприклад ID сесій.

У всіх цих випадках є характерна вразливість. Якщо атакуючий вгадає або передбачить вихідні дані вашого генератора випадкових чисел (RNG, Random Number Generator) або генератора псевдовипадкових чисел (PRNG, Pseudo-Random Number Generator), то він зможе обчислити токени, солі, одноразові значення та криптографічні вектори ініціалізації, цього генератора. Тому дуже важливо генерувати високоякісні випадкові значення, т. Е. Ті, які вкрай важко передбачити. У жодному разі не допускайте передбачуваності токенів скидання паролів, CSRF-токенів, ключів API, одноразових значень та токенів авторизації!


У PHP із випадковими значеннями пов'язані ще дві потенційні вразливості:

  1. Розкриття інформації (Information Disclosure).
  2. Нестача ентропії (Insufficient Entropy).

У цьому контексті «розкриття інформації» відноситься до витоку внутрішнього стану генератора псевдовипадкових чисел – його початкового значення (seed value). Подібні витоку можуть сильно полегшити прогноз майбутніх вихідних даних PRNG.


«Брак ентропії» описує ситуацію, коли варіативність початкового внутрішнього стану (seed) 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() використовує лише одне випадкове значення - початкове (seed), на його основі фіксований алгоритм генерує псевдовипадкові значення.


Подивіться цей приклад, ви можете протестувати його самостійно:


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

Це простий цикл, який виконується після того, як PHP-функція вихору Мерсенна отримала початкове, заздалегідь встановлене значення. Воно було отримано на виході функції, що наводиться як приклад документації до mt_srand() і використовує поточні секунди і мікросекунди. Якщо виконати наведений код, він виведе на екран 25 псевдовипадкових чисел. Вони виглядають випадковими, жодних збігів, все чудово. Знову виконаємо код. Щось помітили? Саме: виводяться ТЕ ж саме числа. Запустимо втретє, четверте, вп'яте. У старих версіях PHP результат може бути різним, але це не стосується проблеми, оскільки вона є характерною для всіх сучасних версій PHP.


Якщо атакуючий отримає початкове значення такого PRNG, він зможе передбачити всі вихідні дані mt_rand() . Тож захист початкового значення – справа першорядної важливості. Якщо ви його втратите, то більше не маєте права генерувати випадкові значення.


Ви можете згенерувати початкове значення одним із двох способів:

  • вручну, за допомогою функції mt_srand() ,
  • ви проігноруєте mt_srand() і дозволите PHP згенерувати його автоматично.

Другий варіант краще, але і сьогодні легаси-додатки часто успадковують застосування mt_srand() навіть після портування на більш сучасні версії PHP.


Це підвищує ризик того, що атакуючий відновить початкове значення (атака Seed Recovery Attack), що дасть йому достатньо інформації для передбачення майбутніх значень. В результаті будь-який додаток після такого витоку стає вразливим для атаки розкриття інформації. Це справжнісінька вразливість, незважаючи на її явно пасивну природу. Витік інформації про локальну систему здатний допомогти атакуючому в наступних атаках, що порушить принцип ешелонованого захисту.

Випадкові значення в PHP

У PHP використовується три PRNG, і якщо зловмисник отримає доступ до початкових значень, які застосовуються в їх алгоритмах, він зможе передбачати результати їх роботи:

  1. Лінійний конгруентний генератор (Linear Congruential Generator, LCG), lcg_value().
  2. Вихор Мерсенна, mt_rand().
  3. Локально підтримувана C-функція rand().

Також ці генератори застосовуються для внутрішніх потреб, функцій на кшталт array_rand() і uniqid() . Це означає, що зловмисник може передбачати вихідні дані цих та інших функцій, що використовують внутрішні PRNG мови PHP, якщо набуде всіх необхідних початкових значень. Також це означає, що не вдасться покращити захист, заплутавши нападаючого за допомогою численних звернень до генераторів. Особливо це стосується Open Source додатків. Зловмисник здатний передбачити всі вихідні дані для будь-якого відомого йому початкового значення.


Щоб підвищити якість випадкових значень, що генеруються для нетривіальних завдань, 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 = hash("sha512", mt_rand());

Є й складніші засоби генерування токенів, але це непоганий варіант. Тут використовується лише один виклик mt_rand(), захешований за допомогою SHA512. Насправді, якщо програміст вирішить, що функції випадкових значень у PHP «досить випадкові», він напевно вибере спрощений підхід, доки прозвучить слово «криптографія». Наприклад, до некриптографічних випадків належать токени доступу, CSRF-токени, одноразові значення API та токени скидання паролів. Перш ніж продовжити, я докладно розпишу весь ступінь вразливості цієї програми, щоб ви краще розуміли, що взагалі робить програми вразливими.

Характеристики вразливої ​​програми

Це не вичерпний перелік. На практиці список характеристик може відрізнятись!

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

Це важливо тому, що генератори випадкових чисел PHP отримують початкові значення один раз на один процес. Якщо ми можемо зробити до процесу два запити і більше, то він буде використовувати те саме початкове значення. Суть атаки полягає в тому, щоб застосувати розкриття одного токена для отримання початкового значення, яке потрібно для передбачення іншого токена, що генерується на основі ТОГО Ж початкового значення (тобто в тому ж процесі). Оскільки mod_php ідеально підходить для декількох запитів для отримання пов'язаних випадкових значень, то іноді за допомогою всього одного запиту можна отримати кілька значень, що відносяться до mt_rand() . Це робить надмірними будь-які вимоги до mod_php. Наприклад, частина ентропії, яка використовується для генерування початкового значення для mt_rand() , може втекти через ID сесій або вихідні значення в тому самому запиті.

2. Сервер розкриває CSRF-токени, токени скидання паролів або підтвердження облікових записів, згенеровані на основі mt_rand()-токенів

Для отримання початкового значення нам потрібно безпосередньо перевірити число, створене генераторами PHP. Причому навіть не має значення, як воно використовується. Ми можемо вилучити його з будь-якого доступного значення, будь то вихідні дані mt_rand() , або хешований CSRF, або токен підтвердження облікового запису. Підійдуть навіть непрямі джерела, у яких випадкове значення визначає іншу поведінку на виході, що розкриває це значення. Головне обмеження полягає в тому, що воно має бути з того самого процесу, що генерує другий токен, який ми намагаємось передбачити. А це вразливість «розкриття інформації». Як ми скоро побачимо, витік вихідних даних PRNG може бути вкрай небезпечним. Зверніть увагу, що вразливість не обмежена єдиною програмою: ви можете зчитувати вихідні дані PRNG в одному додатку на сервері і застосовувати їх для визначення вихідних даних в іншому додатку на тому ж сервері, якщо обидва вони використовують один PHP-процес.

3. Відомий слабкий алгоритм генерування токенів

Ви можете обчислити його:

  • покопавшись у вихідниках open source програми,
  • давши хабар співробітнику з доступом до особистого вихідного коду,
  • знайшовши колишнього співробітника, що приховав образу на колишнього роботодавця,
  • або просто припустивши, який алгоритм може бути.

Деякі методи генерування токенів очевидніші, деякі - популярніші. По-справжньому слабкі засоби генерування відрізняються використанням одного з генераторів випадкових чисел PHP (наприклад, mt_rand()), слабкою ентропією (немає інших джерел невизначених даних) та/або слабким хешуванням (наприклад, MD5 або взагалі без хешування). Розглянутий вище приклад коду має ознаки слабкого методу генерування. Також я використав хешування SHA512, щоб продемонструвати, що маскування – це завжди незадовільне рішення. SHA512 - слабке хешування, оскільки воно швидко обчислюється, тобто атакуючий може з неймовірною швидкістю брутфорсувати вхідні дані на будь-які CPU або GPU. І не забувайте, що закон Мура теж діє, а значить, швидкість брутфорсу зростатиме з кожним новим поколінням CPU/GPU. Тому паролі повинні хешуватися за допомогою інструментів, зламування результатів яких вимагає фіксованого часу незалежно від продуктивності процесорів або закону Мура.

Виконання атаки

Наша атака досить проста. У рамках підключення до PHP-процесу ми проведемо швидку сесію та відправимо два окремі HTTP-запити (запит А та запит Б). Сесія буде утримуватися сервером, доки не буде отримано другий запит. Запит А націлений на отримання якого-небудь доступного токена на кшталт CSRF, токена скидання пароля (надсилається атакуючому поштою) або чогось подібного. Не забувайте і про інші можливості на зразок вбудованої розмітки (inline markup), що використовуються в запитах довільних ID і т. д. Ми мучимо вихідний токен, поки він нам не видасть своє початкове значення. Все це - частина атаки з відновленням початкового значення: коли початкове значення має таку маленьку ентропію, що його можна брутфорсити або пошукати в заздалегідь обчисленій райдужній таблиці .


Запит Б вирішуватиме цікавіше завдання. Зробимо запит на скидання локального адміністраторського пароля. Це запустить генерування токена (за допомогою випадкового числа на базі того самого початкового значення, які ми витягуємо за допомогою запиту А, якщо обидва запити успішно відправляються на той самий PHP-процес). Цей токен буде зберігатися в базі даних в очікуванні моменту, коли адміністратор скористається посиланням на скидання пароля, надісланого йому на пошту. Якщо ми зможемо отримати початкове значення для токена із запиту А, то, знаючи, як генерується токен із запиту Б, ми передбачимо токен скидання пароля. Отже, зможемо перейти за посиланням скидання до того, як адміністратор прочитає листа!


Ось послідовність розвитку подій:

  1. За допомогою запиту A отримуємо токен і піддаємо його зворотному інжинірингу для обчислення початкового значення.
  2. За допомогою запиту Б отримуємо токен, згенерований на базі того самого початкового значення. Цей токен зберігається у базі даних програми для майбутнього скидання пароля.
  3. Зламуємо хеш SHA512, щоб дістати згенероване сервером випадкове число.
  4. За допомогою отриманого випадкового значення брутфорсим початкове значення, яке було згенеровано за його допомогою.
  5. Використовуємо початкове значення для обчислення серії випадкових значень, які, ймовірно, можуть бути основою токена скидання пароля.
  6. Використовуємо цей токен(и) для скидання пароля адміністратора.
  7. Отримуємо доступ до адміністраторського облікового запису, розважаємось і отримуємо вигоду. Ну, як мінімум розважаємось.

Займемося хакінгом...

Покроковий злам програми

Крок 1. Здійснюємо запит А для отримання токена

Ми виходимо, що цільовий токен і токен скидання пароля залежить від вихідних даних mt_rand() . Тому потрібно вибрати саме його. У додатку в нашому уявному сценарії всі токени генеруються одним і тим же способом, тому можна просто витягти CSRF-токен і зберегти його на майбутнє.

Крок 2. Здійснюємо запит Б для отримання токена скидання пароля, згенерованого для адміністраторського облікового запису

Цей запит є простою відправкою форми скидання пароля. Токен буде збережено в базі даних і надіслано користувачеві поштою. Нам потрібно правильно вирахувати цей токен. Якщо характеристики сервера точні, то запит Б використовує той же PHP-процес, що і запит А. Отже, в обох випадках виклики mt_rand() будуть застосовувати одне й те початкове значення. Можна навіть використовувати запит А для захоплення CSRF-токена форми скидання, щоб увімкнути введення даних (submission) для упорядкування процедури (виключаємо проміжний round trip).

Крок 3. Зламуємо хешування SHA512 токена, отриманого за запитом А

SHA512 вселяє програмістам побожний трепет: у нього найбільший номер у всьому сімействі алгоритмів SHA-2. Однак у методі генерування токенів, обраному нашою жертвою, є одна проблема - випадкові значення обмежені лише цифрами (тобто ступінь невизначеності, або ентропія, незначна). Якщо ви перевірите вихідні дані mt_getrandmax() , то виявите, що найбільше випадкове число, яке може згенерувати mt_rand() - 2,147 мільярда з дрібницями. Ця обмежена кількість можливостей робить SHA512 уразливим для брутфорсу.


Тільки не вірте мені на слово. Якщо у вас є дискретна відеокарта одного з останніх поколінь, можна піти наступним шляхом. Оскільки ми шукаємо одиночний хеш, я вирішив скористатися чудовим інструментом для брутфорсу - hashcat-lite . Це одна з найшвидших версій hashcat, вона є для всіх основних операційних систем, включаючи Windows.


За допомогою цього коду згенеруйте токен:


$rand = mt_rand(); echo "Random Number: ", $rand, PHP_EOL; $token = hash("sha512", $rand); echo "Token:", $token, PHP_EOL;

Цей код відтворює токен із запиту А (він містить потрібне нам випадкове число і захований у хеш 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).

Якщо все спрацює правильно і ваш GPU не розплавиться, Hashcat обчислить випадкове захешоване число за пару хвилин. Так, хвилин. Раніше вже пояснював, як працює ентропія. Переконайтеся у цьому самі. У функції mt_rand() так мало можливостей, що SHA512-хеш всіх значень реально обчислити за дуже короткий час. Так що безглуздо було хешувати вихідні дані mt_rand().

Крок 4. Відновлюємо початкове значення за допомогою свіжозламаного випадкового числа

Як ми бачили вище, на вилучення з SHA512 будь-якого згенерованого mt_rand() значення потрібно всього кілька хвилин. Озброївшись випадковим значенням, ми можемо запустити інший інструмент для брутфорсу - php_mt_seed. Ця маленька утиліта бере вихідні дані mt_rand() і після брутфорсу обчислює початкове значення, на підставі якого могло бути згенероване аналізоване. Завантажте поточну версію, скомпілюйте та запустіть. Якщо з'являться проблеми з компіляцією, спробуйте старішу версію (з новими у мене були проблеми з віртуальним середовищем).


./php_mt_seed

Це може зайняти трохи більше часу, ніж зламування SHA512, оскільки виконується на CPU. На пристойному процесорі знайде весь можливий діапазон початкового значення за кілька хвилин. Результат - одне або кілька можливих значень (тобто значень, на підставі яких могло бути отримане дане випадкове число). Повторюся: ми спостерігаємо результат слабкої ентропії тільки цього разу щодо генерування в PHP початкових значень для функції вихору Мерсенна. Пізніше ми розглянемо, як були згенеровані ці значення, тому ви побачите, чому можна так швидко виконувати брутфорс.


Отже, раніше ми користувалися простими інструментами злому, доступними в мережі. Вони заточені під виклики mt_rand() , але ілюструють ідею, яка може бути застосована і до інших сценаріїв (наприклад, послідовні виклики mt_rand() при генеруванні токенів). Також майте на увазі, що швидкість злому не перешкоджає генеруванню райдужних таблиць, що враховують конкретні підходи до генерування токенів. Ось ще один інструмент, що експлуатує вразливість mt_rand() і написаний на Python.

Крок 5. Генеруємо можливі токени скидання пароля адміністраторського облікового запису

Припустимо, що в рамках запитів А і Б було зроблено лише два запити до mt_rand(). Тепер почнемо передбачати токени, використовуючи раніше обчислені можливі початкові значення:


function predict($seed) ( /** * Передаємо в PRNG початкове значення */ mt_srand($seed); /** * Пропускаємо виклик функції із запиту А */ mt_rand(); /** * Передбачаємо та повертаємо згенерований у запиті Б токен */ $token = hash("sha512", mt_rand());

Ця функція передбачає токен скидання для кожного можливого початкового значення.

Кроки 6 і 7. Скидаємо пароль адміністраторського облікового запису і веселимось!

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

Аналіз після атаки

Наведений вище сценарій і простота кроків повинні ясно продемонструвати вам небезпеку mt_rand() . Ризики настільки очевидні, що тепер ми можемо вважати будь-які слабко приховані вихідні значення mt_rand() , доступні нападнику в будь-якій формі, вразливістю розкриття інформації.


Більше того, ця історія має й другу сторону. Наприклад, якщо ви залежите від бібліотеки, яка невинно використовує mt_rand() для якихось важливих завдань, нехай і не надаючи отриманих значень, то, скориставшись для своїх потреб «дірявим» токеном, ви скомпрометуєте цю бібліотеку. І це проблема, тому що бібліотека чи фреймворк ніяк не допомагають пом'якшити атаку з відновленням початкового значення. Чи потрібно звинувачувати користувача за витік значень mt_rand() - чи бібліотеку за те, що вона не застосовує більш якісних випадкових значень?


Насправді обидва винні. Бібліотеці не слід вибирати mt_rand() (або будь-яке інше одиночне джерело слабкої ентропії) для важливих завдань як єдине джерело випадкових значень. А користувач не повинен писати код, з якого витікають значення mt_rand(). Так що так, можна почати обвинувально тикати пальцем у неписьменні приклади використання mt_rand() , навіть якщо це не призводить до прямих витоків.


Хвилюватися потрібно не лише з приводу вразливостей розкриття інформації. Необхідно пам'ятати і про вразливість нестачі ентропії, що залишають програми беззахисними перед брутфорсом важливих токенів, ключів або одноразових значень, які технічно не належать до криптографії, але використовуються в роботі нетривіальних функцій додатків.

А тепер все те саме

Тепер ми знаємо, що використання PRNG, вбудованих у PHP, вважається вразливістю нестачі ентропії (тобто зниження невизначеності полегшує брутфорс). Можна розширити нашу атаку:


Вразливість розкриття інформації робить цей метод генерування токенів абсолютно марним. Щоб зрозуміти причину, уважно подивимося на PHP-функцію uniqid() . Її визначення:


На основі поточного часу у мікросекундах отримує унікальний префікс-ідентифікатор.


Як пам'ятаєте, ентропія - це міра невизначеності. Через вразливість розкриття інформації можливий витік значень, що генеруються mt_rand() , так що використання mt_rand() як унікальний префікс-ідентифікатор додає нульову невизначеність. У нашому прикладі єдиний інший вид вхідних даних для uniqid() – це час. Але воно точно не є невизначеним. Воно змінюється лінійно та передбачувано. А у передбачуваних значень украй низька ентропія.


Звичайно, визначення вказує на "мікросекунди", тобто на мільйонні частки секунди. Це дає нам 1000000 можливих чисел. Тут я ігнорую значення більше 1 секунди, оскільки їх фракція та вимірюваність такі великі (наприклад, заголовок HTTP Date у відгуку), що це майже нічого не дає. Перш ніж заглибитись у деталі, давайте препаруємо функцію uniqid() та подивимося на її С-код:


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", prefix, sec, usec); ) RETURN_STRING(uniqid, 0);

Якщо це виглядає занадто складно, можна реплікувати все в старий добрий PHP:


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

Цей код говорить нам, що простий виклик uniqid() без параметрів поверне нам рядок із 13 символів. Перші 8 символів - поточна тимчасова мітка Unix (в секундах), виражена в шістнадцятковій формі. Останні 5 символів – додаткові мікросекунди у шістнадцятковій формі. Іншими словами, базова функція uniqid() забезпечує дуже точне вимірювання системного часу, який можна отримати з простого виклику uniqid() за допомогою такого коду:


$id = uniqid(); $time = str_split($id, 8); $sec = hexdec("0x". $time); $usec = hexdec("0x". $time); echo "Seconds: ", $sec, PHP_EOL, "Microseconds: ", $usec, PHP_EOL;

Подивіться на С-код. Точний системний час у вихідних даних ніколи не буває прихованим, незалежно від параметрів:


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

Брутфорс унікальних ідентифікаторів

Якщо подумати, стає очевидно, що розкриття зловмиснику будь-якого значення uniqid() - ще один приклад потенційної вразливості розкриття інформації. Відбувається виток дуже точного системного часу, який можна використовувати для прогнозування вхідних даних для наступних викликів uniqid() . Це допомагає вирішувати будь-які дилеми, що виникають при спробі прогнозування мікросекунд, за рахунок звуження 1000000 можливостей до вужчого діапазону. Оскільки про цей витік можна було згадати пізніше, то в нашому прикладі технічно вона не потрібна. Давайте знову подивимося на оригінальний код токена uniqid() :


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

З цього прикладу ми бачимо, що, виконавши проти mt_rand() атаку з відновленням початкового значення разом з розкриттям інформації з uniqid() , ми обчислимо відносно невеликий набір SHA512-хеш, які можуть виявитися скиданням пароля або іншими важливими токенами. Якщо вам потрібен вузький діапазон тимчасових міток без використання витоку системного часу з uniqid() , проаналізуємо серверні відгуки, які зазвичай містять заголовок HTTP Date. Звідси можна отримати точні серверні часові мітки. А оскільки в цьому випадку ентропія дорівнює одному мільйону можливих значень мікросекунд, то можна забрати її за кілька секунд!


Чи врятує нас збільшення ентропії?

Звичайно, є можливість додати ентропію в uniqid() шляхом присвоєння другому параметру функції TRUE:


Як свідчить С-код, нове джерело ентропії використовує вихідні дані внутрішньої функції 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; }

Якщо ви занадто довго на це дивитися і захочете кинути чимось у монітор, то краще не треба. Монітори нині дорогі.


Обидва початкові значення використовують в С функцію gettimeofday() для захоплення поточного часу в секундах і мікросекундах починаючи з Unix Epoch (належить до серверного годинника). Потрібно відзначити, що обидва виклики реалізовані у вихідному коді, так що значення лічильника microsecond() між ними буде мінімальним, що знижує невизначеність, що вноситься. Друге початкове значення також виявиться підмішаним в ID поточного процесу, який у більшості випадків під 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 ) (time(0) * getpid())) ^ ((long) (1000000.0 * php_combined_lcg(TSRMLS_C)))) #endif

Це означає, що всі початкові значення, що використовуються в PHP, взаємозалежні. Навіть кілька разів поєднуються однакові вхідні дані. Можливо, ви обмежите діапазон початкових мікросекунд, як ми обговорювали вище: за допомогою двох запитів, коли перший робить перехід між секундами (так що мікрочас буде 0 + час виконання наступного С-виклику gettimeofday()) . Ви можете обчислити дельту в мікросекундах між іншими викликами 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);

Цей код генерує передхешове (pre-hash) значення для ID сесії, використовуючи IP, тимчасову мітку, мікросекунди і вихідні дані php_combined_lcg() . Враховуючи істотне зниження кількості мікрочасових можливостей (це коду потрібна 1 для генерування ID і 2 для php_combined_lcg() , що призводить до мінімальної різниці між ними), ми тепер можемо виконати брутфорс. Ну, мабуть.


Як ви, напевно, пам'ятаєте, PHP тепер підтримує нові опції сесій на кшталт session.entropy_file та session.entropy_length. Це зроблено для запобігання брутфорсу ID сесій, у ході якого можна швидко (це не займе годинник) отримати два початкові значення для об'єднаних за допомогою php_combined_lcg() LCG-генераторів. Якщо ви використовуєте PHP 5.3 або нижче, можливо, неправильно налаштували ці опції. А це означає наявність іншої корисної вразливості розкриття інформації, що дозволяє виконувати брутфорс ID сесій для отримання початкових значень LCG.


Для подібних випадків існує Windows-додаток, що дозволяє обчислювати значення LCG.


До речі, знання станів LCG дозволяє зрозуміти, як mt_rand() отримує початкове значення, тому це ще один спосіб обійти брак витоків значення mt_rand() .


Що все це означає з точки зору додавання ентропії в значення uniqid() , що повертаються?


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

Це інший приклад потенційної вразливості нестачі ентропії. Не можна покладатися на ентропію з витоками (навіть якщо ви не відповідаєте за них!). Завдяки витоку інформації про ID сесії зловмисник може передбачити і значення ентропії, що було додатково додано в цей ID.


Знову ж таки, кого звинувачувати? Якщо додаток Х покладається на uniqid() , але користувач або інший додаток на тому ж сервері допускає витік внутрішнього стану LCG, потрібно вживати заходів в обох ситуаціях. Користувачі повинні бути впевнені, що ID сесій використовують досить високу ентропію, а сторонні програмісти повинні розуміти, що їх методам генерування випадкових значень не вистачає ентропії, тому потрібно перейти на більш відповідні альтернативи (навіть якщо доступні лише джерела слабкої ентропії!).

У пошуках ентропії

PHP сам по собі не здатний генерувати сильну ентропію. Тут навіть немає базового API передачі даних з PRNG-генераторів рівня операційної системи, є надійними джерелами сильної ентропії. Тому вам потрібно покладатися на опціональну наявність розширень openssl та mcrypt. Вони пропонують функції, які набагато кращі за своїх дірявих, передбачуваних, низькоентропійних родичок.


На жаль, оскільки обидва розширення опціональні, в деяких випадках ми не маємо іншого вибору, окрім як покладатися на джерела слабкої ентропії як останній відчайдушний рубіж. Коли таке трапляється, потрібно доповнювати слабку ентропію mt_rand() за допомогою додаткових джерел невизначеності, змішуючи дані в єдиний пул, з якого можна черпати псевдовипадкові байти. Подібний випадковий генератор, який використовує міксер сильної ентропії, вже реалізував Ентоні Феррара у своїй бібліотеці RandomLib. Ось що слід по можливості робити програмістам.


Уникайте спокуси приховати слабкість своєї ентропії за допомогою хешування складних математичних перетворень. Все це повторить зловмисник, як він дізнається первинне початкове значення. Подібні хитрощі лише трохи збільшать обсяг обчислень при брутфорсі. Не забувайте: що менше ентропія, то менше невизначеності; що менше невизначеності, то менше можливостей необхідно брутфорсить. Єдине виправдане рішення - будь-якими доступними способами збільшити пул використовуваної вами ентропії.


Бібліотека RandomLib генерує випадкові байти шляхом змішування даних із різних джерел ентропії та локалізації інформації, яка може знадобитися зловмиснику для припущень. Наприклад, можна змішати вихідні дані mt_rand() , uniqid() і lcg_value() , додати PID, споживання пам'яті, ще якийсь вимір мікрочасу, серіалізацію $_ENV, posix_times() і т.д. Або піти ще далі, це дозволяє розширюваність RandomLib. Припустимо, використовувати якісь дельти в мікросекундах (тобто вимірювати, скільки мікросекунд потрібно якоїсь функції для роботи з псевдовипадковими вхідними даними на кшталт викликів hash()).

Додати теги
  • Від:
  • Registered: 2014.07.07
  • Posts: 3,775
  • I just like PunBB:
  • 5 years, 6 months,
  • Likes: 463

Topic: Як у PHP вибрати випадкове значення з масиву

За допомогою цієї функції ми можемо вибрати довільний елемент (або елементи) масиву. Так, саме елемент чи елементи! Це може бути один елемент або їх може бути декілька. Все залежить від того завдання, яке перед Вами стоїть.

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

Як параметри у дужках функція приймає: ім'я масиву, з яким працюємо і кількість елементів, які необхідно вибрати.

Загалом усе просто! А ще простіше буде, коли ми це розглянемо на прикладах.

Давайте спочатку будемо вибирати один-єдиний випадковий елемент з масиву.

2 Reply by PunBB

  • Від: Москва, Sovkhoznay 3, apt. 98
  • Registered: 2014.07.07
  • Posts: 3,775
  • I just like PunBB:
  • 5 years, 6 months,
  • Likes: 463

Уявімо, що десь вгорі на нашому сайті ми хочемо виводити якісь цитати. Звісно, ​​цитати мають змінюватися. Щоразу заходячи на Ваш сайт, Ви хочете, щоб користувач бачив нову цитату.

Як Ви, напевно, здогадалися, найпростіший спосіб це реалізувати – це помістити всі наявні цитати-вислови у масив, а потім вибирати з цього масиву випадковий елемент та виводити його на екран.

Чим більше у Вас буде цитат у масиві, тим менша ймовірність їхнього повторення.

Але для прикладу я сильно морочитися не буду і поміщу в мій масив 7 висловів.

Далі мені потрібно буде створити змінну, в яку занесу результат роботи функції array_rand(). У дужках ця функція матиме два аргументи: ім'я нашого масиву і необхідну нам кількість випадкових елементів.

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

Після цього потрібно буде просто вивести на екран значення потрібного елемента. Для цього вказую ім'я масиву та у квадратних дужках ім'я нашої змінної, яка містить випадковий ключ.

От і все. Подивіться код нижче і, гадаю, Ви все зрозумієте остаточно:

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

3 Reply by PunBB

  • Від: Москва, Sovkhoznay 3, apt. 98
  • Registered: 2014.07.07
  • Posts: 3,775
  • I just like PunBB:
  • 5 years, 6 months,
  • Likes: 463

Як в PHP вибрати випадкове значення з масиву

Тепер давайте потренуємося з виведенням кількох випадкових елементів масиву.

У випадку з одним елементом повертається ключ, а у випадку з кількома випадковими елементами масиву повертається масив ключів. Від цього ми і відштовхуватимемося при виведенні на екран.

Спочатку створимо масив, до якого занесемо 7 різних імен.

Далі створюємо змінну, до якої буде занесено роботу функції array_rand(). Тільки тепер у дужках у цієї функції як другий аргумент вкажемо цифру «2». Це означатиме, що нам потрібні 2 випадкові елементи.

Результатом роботи функції в даній ситуації буде масив, що містить два випадкові ключі елементів з нашого основного масиву.

Тому при виведенні на екран це потрібно врахувати і в квадратних дужках вказати не просто ім'я змінної, а ім'я змінної, потім квадратні дужки та індекс масиву. Так як елемента у нас 2, то в першому випадку індекс буде , а в другому. (Ви пам'ятаєте, що індексація у масивах починається з «0».)

От і все. Подивіться на код, щоб Вам стало зрозуміліше:

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

".$names[$rand_names]." та ".$names[$rand_names]."

";

Як результат, на екран будуть виводитися два випадкові імені. Щоразу під час оновлення сторінки імена змінюватимуться.

Джерело



Подібні публікації