Как слабые сравнения в 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()
, - валидировать типы входных данных.