unserialize() у PHP відтворює не лише дані, а й об'єкти довільних класів з довільними значеннями властивостей. При цьому автоматично викликаються магічні методи: __wakeup(), __unserialize(), а згодом __destruct().
Якщо нападник контролює рядок для unserialize(), він може створити об'єкти класів, які вже є в застосунку чи його залежностях, з потрібними йому властивостями. Ланцюжок викликів магічних методів («POP chain») призводить до запису файлів, SQL-запитів чи виконання коду. Готові ланцюжки для популярних фреймворків і бібліотек відомі й зібрані в інструментах на кшталт PHPGGC.
// Вразливо
$cart = unserialize($_COOKIE['cart']);
Захист:
- Не десеріалізувати дані від користувача. Для обміну даними -
json_decode(): він створює лише масиви йstdClass, без виклику коду класів. - Якщо
unserialize()неминучий -allowed_classes:
$data = unserialize($payload, ['allowed_classes' => false]); // лише скаляри й масиви
$data = unserialize($payload, ['allowed_classes' => [Money::class]]); // білий список
- Підписувати серіалізовані дані, що проходять через клієнта (HMAC), і перевіряти підпис до десеріалізації.
Де це трапляється в Laravel-застосунках:
- Laravel шифрує cookie й підписує завдання черги, тож напряму від користувача серіалізовані дані туди не потрапляють. Але витік
APP_KEYдозволяє підробити зашифровані дані - і раніше це давало виконання коду через десеріалізацію. Тому витік ключа - критичний інцидент. - Кеш і сесії в Redis/Memcached серіалізуються: доступ нападника до кешу - теж шлях до цієї атаки. Laravel дозволяє обмежити класи, які можна десеріалізувати з кешу (
serializable_classesуconfig/cache.php). - Phar-архіви: у старих версіях PHP файлові функції з шляхом
phar://десеріалізували метадані архіву. PHP 8.0 це прибрав.
Аналогічні проблеми мають pickle у Python, Java-серіалізація, YAML.load у Ruby.