Как слабые сравнения в PHP приводят к уязвимостям
PHP Type Juggling (Слабые сравнения) — уязвимость и примеры эксплуатации
PHP остаётся одним из самых популярных языков веб-разработки. Его гибкость и простота привели к тому, что на нём создано огромное количество CMS и веб-приложений. Однако у этой гибкости есть и оборотная сторона — особенности работы PHP с типами данных нередко приводят к уязвимостям.
Одной из таких уязвимостей является PHP Type Juggling (жонглирование типами), или, по-другому, слабые сравнения.
Суть: при использовании операторов == и != PHP автоматически приводит значения к общему типу. Это удобно для программиста, но может привести к обходу авторизации или подделке проверок хэшей.
TL;DR
- Уязвимость возникает, если используется слабое сравнение (
==,!=) вместо строгого (===,!==). - PHP приводит разные типы к одному виду, напр. строка
"9 Phones"→ число9. - Это может вызвать неожиданные результаты:
"password" == 0→true. - Последствия: обход авторизации, взлом восстановления пароля, некорректные проверки токенов.
Как работает жонглирование типами
Пример 1. Строка и число
var_dump("9 Phones" == 9); // true
PHP превращает "9 Phones" → 9. Сравнение становится 9 == 9.
---
Пример 2. Уязвимый код входа
if ($_POST['passwd'] == "password") {
login();
}
Атакующий может отправить 0 или "0e12345", и условие сработает.
---
Пример 3. Ноль и строка
var_dump(0 == "password"); // true
Строка "password" приводится к числу → 0. В итоге 0 == 0.
---
Пример 4. Защита строгим сравнением
var_dump("0" === "password"); // false
Строгое сравнение (===) учитывает и тип, и значение.
---
Пример 5. JSON и ID
{"id":"0"}
{"id":0}
- В первом случае
"0"— строка. - Во втором — число
0.
При слабом сравнении PHP решит, что это одно и то же. Для авторизации — катастрофа.
---
Пример 6. Число и строка
var_dump(4 === "4"); // false
Хотя 4 == "4" дало бы true, строгая проверка сразу выявляет различие.
---
Пример 7. Несовпадение
$int_val = 4;
$str_val = "4 str";
$str_val_2 = "4";
if ($int_val === $str_val) {
echo "False";
}
if ($int_val === $str_val_2) {
echo "False";
}
Даже если строка выглядит как число, строгая проверка не позволит провести атаку.
---
Пример 8. Явное приведение
$int_val = 4;
$str_val = "4 str";
if ($int_val === (int)$str_val) {
echo "True"; // сработает
}
Мы явно сказали PHP: «преврати строку в число». Получили 4.
---
Пример 9. Magic Hashes
if ('0e462097431906509019562988736854' == '0') {
echo "Matched";
}
Строка "0e..." воспринимается PHP как число в научной записи (0 × 10^N = 0).
Значит, "0e12345" == 0 → true.
Это даёт возможность атакующему подделывать хэши и обходить проверки.
Пример из практики: Сброс пароля
В одной из систем сброс пароля работал так:
$sig = substr(hash("sha256", $user.$token.$time.$pass), 0, 8);
if ($sig == $_GET['sig']) {
// ...
}
- Хэш усекался до 8 символов.
- Сравнение выполнялось через
==. - Атакующий подбирал
$time, пока результат не начинался с0e.... - В итоге условие всегда возвращало
true.
Таким образом, можно было сбросить пароль администратора.
Почему это возможно?
- POST/GET параметры приходят строками. PHP приведёт строку к числу.
- JSON и
unserialize()позволяют атакующему управлять типами. - Усечённые хэши повышают вероятность появления «магического» значения
0e....
Как защититься
- Использовать строгое сравнение
===,!==. - Для токенов и хэшей —
hash_equals(). - Никогда не усекать хэши, хранить и проверять полный.
- Делать явное приведение и фильтрацию входных данных (
filter_var). - Не использовать
unserialize()илиjson_decode()без строгой схемы.
Заключение
Type Juggling — это особенность PHP, которая при невнимательном программировании превращается в уязвимость.
Небрежное использование == может привести к:
- обходу авторизации,
- подделке хэшей (magic hashes),
- взлому восстановления пароля.
Золотое правило:
- всегда использовать строгие проверки (
===), - сравнивать хэши через
hash_equals(), - валидировать типы входных данных.