Перейти к содержанию

Как слабые сравнения в PHP приводят к уязвимостям

Материал из Викижурнал

PHP Type Juggling (Слабые сравнения) — уязвимость и примеры эксплуатации

PHP остаётся одним из самых популярных языков веб-разработки. Его гибкость и простота привели к тому, что на нём создано огромное количество CMS и веб-приложений. Однако у этой гибкости есть и оборотная сторона — особенности работы PHP с типами данных нередко приводят к уязвимостям.

Одной из таких уязвимостей является PHP Type Juggling (жонглирование типами), или, по-другому, слабые сравнения. Суть: при использовании операторов == и != PHP автоматически приводит значения к общему типу. Это удобно для программиста, но может привести к обходу авторизации или подделке проверок хэшей.

TL;DR

  • Уязвимость возникает, если используется слабое сравнение (==, !=) вместо строгого (===, !==).
  • PHP приводит разные типы к одному виду, напр. строка "9 Phones" → число 9.
  • Это может вызвать неожиданные результаты: "password" == 0true.
  • Последствия: обход авторизации, взлом восстановления пароля, некорректные проверки токенов.

Как работает жонглирование типами

Пример 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" == 0true. Это даёт возможность атакующему подделывать хэши и обходить проверки.

Пример из практики: Сброс пароля

В одной из систем сброс пароля работал так:

$sig = substr(hash("sha256", $user.$token.$time.$pass), 0, 8);
if ($sig == $_GET['sig']) {
    // ...
}
  • Хэш усекался до 8 символов.
  • Сравнение выполнялось через ==.
  • Атакующий подбирал $time, пока результат не начинался с 0e....
  • В итоге условие всегда возвращало true.

Таким образом, можно было сбросить пароль администратора.

Почему это возможно?

  • POST/GET параметры приходят строками. PHP приведёт строку к числу.
  • JSON и unserialize() позволяют атакующему управлять типами.
  • Усечённые хэши повышают вероятность появления «магического» значения 0e....

Как защититься

  1. Использовать строгое сравнение ===, !==.
  2. Для токенов и хэшей — hash_equals().
  3. Никогда не усекать хэши, хранить и проверять полный.
  4. Делать явное приведение и фильтрацию входных данных (filter_var).
  5. Не использовать unserialize() или json_decode() без строгой схемы.

Заключение

Type Juggling — это особенность PHP, которая при невнимательном программировании превращается в уязвимость. Небрежное использование == может привести к:

  • обходу авторизации,
  • подделке хэшей (magic hashes),
  • взлому восстановления пароля.

Золотое правило:

  • всегда использовать строгие проверки (===),
  • сравнивать хэши через hash_equals(),
  • валидировать типы входных данных.