Несподівана поведінка openssl_random_pseudo_bytes() приводить до фатальної втрати крипто-стійкості

Доброго часу доби всім.

Нещодавно в одному з проектів ми зіткнулися з наступною проблемою — функція openssl_random_pseudo_bytes() видавала повторювані псевдовипадкові послідовності!

Цього не може бути, тому що цього не може бути ніколи! — Скаже кожен, хто читав документацію цієї функції. І, так, $crypto_strong справно видавав TRUE.

І тим не менше — помилки унікальності при вставці в базу сипалися пачками і лог підтверджував — 32-байтные послідовності генерувалися повторно через різні інтервали, від доби до тижня. Розслідування тривало цілий місяць. Зараз я на 99% впевнений, що причина знайдена — але буду вдячний, якщо Хабражітелі підтвердять або спростують мої висновки.



А справа була в поєднанні особливостей відразу трьох продуктів:
  • Apache працює з prefork MPM
  • PHP має обмежену підтримку функцій OpenSSL
  • І самої бібліотеки OpenSSL має проблему Random fork-safety
Спрощено те, що відбувається виглядає так — Апач при старті створює першу копію ПХП яка стартує рандом-генератор OpenSSL. А далі — Апач створює і використовує форки, копіюючи в тому числі і початковий стан рандом-генератора.
Так як рандом генератор зав'язаний ще й на PID процесу — то проблема проявляється не відразу. Оскільки на Linux типове максимальне значення для PID 65536, то ось приблизно через таку кількість запитів до веб-сервера видаються псевдовипадкові послідовності і почнуть повторюватися. Більше точних технічних подробиць краще отримати вже наведеної вище статті бази знань OpenSLL

Проблема посилюється тим, що найкращі рекомендовані методи боротьби ( Call RAND_seed after a fork і Call RAND_poll after a fork) на ПХП незастосовні, так як ці функції OpenSSL просто недоступні ПХП.

На жаль, мені не вдалося знайти в мережі адекватних матеріалів з цієї проблеми, за винятком вже наведеної статті OpenSLL, але вона не описує конкретну зв'язку Apache + PHP + OpenSSL. Зате статей настійно рекомендують використовувати openssl_random_pseudo_bytes() як криптостійкий ГВЧ — предостатньо.

А король-то голий!

У підсумку довелося просто відмовитися від використання openssl_random_pseudo_bytes() і перейти на пряме читання з /dev/urandom. Не найблискучіше рішення — але достатнє у нашому випадку.

Оскільки автор не є експертом в області криптографії і мої висновки можуть бути невірними / неповні, а проблема є більш ніж серйозною, враховуючи поширеність рекомендацій по використанню openssl_random_pseudo_bytes(), то я обов'язково вивчу всі коментарі фахівців і можливо виправлю / доповню (або видалю, якщо в корені не правий) статтю. Також, якщо висновки підтвердяться, необхідно буде внести доповнення в документацію ПХП і пропозиції по додаванню RAND_seed/RAND_poll та / або їх виклики при старті програми ПХП.

Важливо! Apache повинен працювати в prefork режимі (MPM prefork). Версія ПХП з якою проблема перевірялася — 5.5.x, але, імовірно, буде відтворюватися в будь-якій версії має openssl_random_pseudo_bytes()

P. S. Я відписався в security@php.net — майже місяць тому. Ні відповіді, ні привіту. Або не отримали. Або проігнорували. Не знаю.
Так що виводжу статтю назад в онлайн.


Джерело: Хабрахабр
  • avatar
  • 0

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.