# Type Juggling в PHP: хлабавите сравнения като риск за сигурността

> Разберете PHP Type Juggling: как сравнява ==, какво промени PHP 8.0 и защо токени, хешове и роли трябва да се сравняват стриктно.

Източник: https://phpsos.de/bg/blog/type-juggling-php/  
Актуалност: 25.10.2026  
Доставчик: PHP SOS, Спешна помощ за PHP и сигурност на сървъри. Незабавна помощ по WhatsApp: https://wa.me/491736494914?text=%D0%97%D0%B4%D1%80%D0%B0%D0%B2%D0%B5%D0%B9%D1%82%D0%B5%2C%20PHP%20SOS%2C%20%D0%B8%D0%BC%D0%B0%D0%BC%D0%B5%20%D0%BD%D1%83%D0%B6%D0%B4%D0%B0%20%D0%BE%D1%82%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%20%D1%81%20%D0%BD%D0%B0%D1%88%D0%B8%D1%8F%20PHP%20%D1%81%D1%8A%D1%80%D0%B2%D1%8A%D1%80.%20%D0%A4%D0%B8%D1%80%D0%BC%D0%B0%3A%20%E2%80%A6%20%D0%A3%D0%B5%D0%B1%D1%81%D0%B0%D0%B9%D1%82%3A%20%E2%80%A6

**Накратко:** PHP Type Juggling е автоматичното преобразуване на типове, което при хлабаво сравнение с `==` решава резултата. От PHP 8.0 правилата са по-строги, но числовите низове продължават да се сравняват като числа, а `true`, `false` и `null` следват собствени правила. За проверки, свързани със сигурността, затова важи: `===`, `in_array(..., true)`, `match` вместо `switch`, `hash_equals()` за токени и `password_verify()` за пароли.

## Сравнение, което е вярно, въпреки че не би трябвало да е

Много уязвимости в PHP приложения не се крият в сложни функции, а в един-единствен знак: `==`, където е трябвало да стои `===`. Кодът изглежда правилен, тестовете с нормален вход минават и въпреки това проверката при определени условия допуска нещо, което е трябвало да спре.

Причината е свойство на PHP, което в ежедневието е практично: при нужда стойностите се преобразуват в друг тип. Низ от формуляр така може да се сравни без заобикаляне с число от базата данни. При входове, токени, роли или полета за състояние точно тази щедрост може да стане опасна.

## Как PHP сравнява с ==

Наръчникът на PHP описва подробно правилата за хлабави сравнения. За сигурността са важни най-вече тези случаи:

| Сравнение | PHP 7 | PHP 8 | Причина |
|---|---|---|---|
| `0 == "a"` | вярно | невярно | от PHP 8.0 се сравняват като низове |
| `"1" == "01"` | вярно | вярно | и двата са числови, сравнение като число |
| `"10" == "1e1"` | вярно | вярно | експоненциалният запис е числов |
| `100 == "1e2"` | вярно | вярно | число срещу числов низ |
| `null == false` | вярно | вярно | сравнение като булева стойност |
| `"abc" == true` | вярно | вярно | непразен низ се счита за вярно |

Промяната в PHP 8.0 обезвреди цял клас грешки: по-рано нечислов низ при сравнение с число ставаше `0`. Това засягаше и `switch` конструкциите. Останалите редове от таблицата обаче показват, че хлабавите сравнения продължават да крият изненади.

И операторите `<`, `>` и операторът за сравнение `<=>` следват тези правила. При сортиране това обикновено е безобидно, но при проверки като „сума по-малка от лимит“ със стойности от заявката типът трябва да е известен предварително.

Особено коварен е редът с експоненциалния запис. Хеш стойностите и токените често се състоят от цифри и букви. Ако такава стойност случайно изглежда като число в експоненциален запис, `==` може да сравни два различни хеша като равни. Това е фонът на много съобщения за заобикаляне на входа в по-стари PHP приложения.

## PHP Type Juggling в типични проверки за сигурност

Хлабавите сравнения се крият на повече места, отколкото само в оператора `==`:

1. **`in_array()` и `array_search()`** сравняват хлабаво без трети параметър. Списък с разрешени роли или ID-та така става по-мек, отколкото се предполага.
2. **`switch`** сравнява хлабаво. Изразът `match`, наличен от PHP 8.0, според наръчника сравнява безопасно по тип с `===` и хвърля грешка, ако никой клон не съвпада.
3. **JSON входовете** връщат истински типове. Поле, което очаквате като низ, може да пристигне като `true` или като число и да съвпадне изненадващо с `==`.
4. **`strcmp()` за тайни** е неподходящо, защото времето за изпълнение зависи от съдържанието и случаите на грешка лесно се оценяват погрешно.
5. **Проверка на върнатите стойности:** функции като `strpos()` връщат `0` при попадение в началото и `false` при липса на попадение. `if (!strpos(...))` бърка двете.

## Сигурни сравнения: сравнителен преглед

Следващите примери показват всеки път несигурния и сигурния вариант:

```php
<?php
declare(strict_types=1);

// Проверка на роля
$erlaubteRollen = ['admin', 'redaktion'];
in_array($rolle, $erlaubteRollen);          // несигурно: хлабаво
in_array($rolle, $erlaubteRollen, true);    // сигурно: стриктно

// Оценка на състояние
$aktion = match ($status) {                 // стриктно, грешка при непозната стойност
    'offen'      => 'bearbeiten',
    'erledigt'   => 'archivieren',
};

// Проверка на токен от връзка
if ($tokenAusLink == $gespeichertesToken) { /* несигурно */ }
if (is_string($tokenAusLink) && hash_equals($gespeichertesToken, $tokenAusLink)) { /* сигурно */ }

// Проверка на парола
if (md5($eingabe) == $hash) { /* несигурно: слаб метод и хлабаво сравнение */ }
if (password_verify($eingabe, $hash)) { /* сигурно */ }

// Търсене на подниз
if (strpos($text, 'abc') !== false) { /* правилно: включително попадение на позиция 0 */ }
```

`hash_equals()` според наръчника очаква известната тайна като първи и стойността на потребителя като втори параметър и сравнява за константно време. Как изобщо да създавате токени сигурно, описва [Сигурни случайни числа и токени в PHP](https://phpsos.de/bg/blog/sichere-tokens-php/).

> **Важно:**
> `declare(strict_types=1)` не прави сравненията стриктни. Директивата засяга само преобразуването на типове при извикване на функции и върнатите стойности. Хлабавите сравнения трябва сами да замените в кода.

Стриктните сравнения имат обратна страна: те се провалят, ако типовете не си подхождат. `'42' === 42` е невярно. Стойностите от формуляри и URL адреси пристигат в PHP като низове, стойностите от базата данни, в зависимост от драйвера и настройката, като низове или числа. Който въведе навсякъде `===`, без да изравни типовете, създава грешки, които в експлоатацията се забелязват като отхвърляне на легитимни потребители.

Решението е ясна граница: входовете се валидират при получаването и се довеждат до правилния тип, например с `filter_var($wert, FILTER_VALIDATE_INT)`, а след това в кода се третират само с декларации на типове и стриктни сравнения. Декларациите на типове при параметри, върнати стойности и свойства помагат, защото грешните типове тогава се забелязват рано със съобщение за грешка, вместо тихо да изчезнат в едно сравнение.

## Систематична проверка на съществуващия код

В разраснали се проекти се намират стотици хлабави сравнения. Не всяко е проблем. Съсредоточете се върху местата, където се взема решение за достъп:

```bash
# Хлабави сравнения в редове със свързани със сигурността понятия
grep -rnE "[^=!]==[^=]" src/ --include="*.php" | grep -iE "token|hash|pass|role|rolle|admin|auth|sign"
# in_array без стриктно сравнение
grep -rnE "in_array\([^)]*\)" src/ --include="*.php" | grep -v ", true)"
```

Инструменти за статичен анализ като PHPStan или Psalm могат при желание постоянно да докладват хлабави сравнения. Така новите случаи изобщо не попадат в главния клон. Основите за типовете описва [Типовата система на PHP](https://phpsos.de/bg/php/typsystem/); как входовете се довеждат предварително до правилния тип, показва [Валидиране и филтриране на вход в PHP](https://phpsos.de/bg/blog/eingaben-validieren-php/).

## Какво означава това за стари системи

Приложенията, които още работят на PHP 7, са засегнати от старите, по-небрежни правила за сравнение. Към това се добавя втори проблем: за PHP 8.1 и по-стари проектът PHP няма повече официални актуализации за сигурност, включително срещу PHP2Shell. Положението описва [PHP 8.1 и по-стари: какво важи сега](https://phpsos.de/bg/php2shell/php-8-1-und-aelter/).

При миграция към PHP 8 някои резултати от сравнения се променят. Това обикновено е подобрение, но може да счупи код, който се е осланял на старото поведение. Затова планирайте тестове, които целенасочено покриват сравнения с числа, празни стойности и `null`.

## Как помага PHP SOS конкретно?

- Търсим във Вашия код хлабави сравнения на места, които решават за вход, права или плащания.
- Заменяме ги със стриктни сравнения, изрази `match` и подходящи функции за проверка.
- Настройваме статичния анализ така, че новите хлабави сравнения да се забелязват в build-а.
- Съпровождаме миграции към актуални версии на PHP с тестове за променените правила за сравнение.

Проверка на Вашия софтуер описва [Одит на сигурността и тест за проникване](https://phpsos.de/bg/leistungen/sicherheitsaudit-penetrationstest/).


## Често задавани въпроси

**Какво е Type Juggling в PHP?**  
PHP преобразува автоматично стойности от различни типове при някои операции, например низ в число. При хлабавото сравнение с == това преобразуване решава резултата. Това е удобно, но може да доведе до сравнения, които са верни, въпреки че стойностите не изглеждат равни.

**Реши ли PHP 8 проблема?**  
Отчасти. От PHP 8.0 число се сравнява с нечислов низ като низ, така че 0 == 'a' вече не е вярно. Два числови низа обаче продължават да се сравняват като числа, а сравненията с true, false и null следват собствени правила.

**Помага ли declare(strict_types=1) срещу хлабави сравнения?**  
Не. strict_types управлява дали скаларните типове се преобразуват автоматично при извикване на функции и връщане на стойности. Върху операторите за сравнение == и != няма влияние.

**Как да сравнявам правилно пароли и токени?**  
Паролите проверявате с password_verify, а токените и подписите с hash_equals. И двете сравняват точно и, при hash_equals, за константно време. Сравнение с == или strcmp не е подходящо за това.

## Източници

- [Наръчник на PHP: оператори за сравнение](https://www.php.net/manual/de/language.operators.comparison.php)
- [Наръчник на PHP: match](https://www.php.net/manual/de/control-structures.match.php)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
