mbator.pl / PHP Type Juggling
Autor: Maciek Bator

PHP Type Juggling

Type juggling, czyli w dosłownym tłumaczeniu żonglowanie typami, to feature występujący w niektórych językach programowania, który pozwala na łatwiejsze wykonywanie operacji na zmiennych o różnych typach.

Czym jest type juggling? Co to jest type juggling? Jak działa type juggling?

Jak pewnie wiesz, PHP nie wymaga, a nawet nie wspiera definiowania typu zmiennych. Ich typ jest określany na podstawie przypisanych do niej danych na przykład tak:

$zmienna = "tekst";  // zmienna jest typu "string"
$zmienna = 2;   // zmienna jest typu "integer" (liczba całkowita)
$zmienna = 1.3;  // zmienna jest typu "float" (liczba zmiennoprzecinkowa)

Co ciekawe (i co jest tematem tego wpisu), podobna sytuacji dopasowania typu występuje podczas korzystania z operatorów porównujących.

if ($zmienna_a == $zmienna_b) // jeśli a równe jest b
if ($zmienna_a > $zmienna_b) // jeśli a jest większe niż b
if ($zmienna_a <= $zmienna_b) // jeśli a jest mniejsze lub równe b

i wszelkich innych operacji, na przykład gdy mnożymy liczbę całkowitą z liczbą zmiennoprzecinkową:

$zmienno = 0.5; // zmienna zmienno jest typu float
$wynik = 3; // zmienna wynik jest typu int
$wynik *= $zmienno; // mnożymy je przez siebie
// teraz zmienna wynik jest typu float (równa 1.5)

Chciałbym jednak wrócić do porównań bo to właśnie tutaj pojawiają się problemy z tą właściwością języka PHP. Mogłeś się spotkać z następującym zapisem:

if ($zmienna_a === $zmienna_b)

I zastanawiać się, czy ktoś nie popełnił w nim literówki, w końcu operatorem sprawdzającym czy zmienne są sobie równe jest ==. Owszem jest to prawdą, ale === również do tego służy. Jaka jest różnica?

Otóż operator == wykonuje type juggling przed porównaniem, a ===, nie wykonuje dopasowania typów przed porównaniem. Nazywane one są kolejno luźnym (loose) i ścisłymi (strict).

Parę przykładowych porównań z pogrubionymi różnicami w wynikach:

loose:
0xF == 15 = true
0xF == "15" = true
"0xF" == 15 = false
"0xF" == "15" = false
"0xF" == 0xF = false
"15" == 15 = true
strict:
0xF === 15 = true
0xF === "15" = false
"0xF" === 15 = false
"0xF" === "15" = false
"0xF" === 0xF = false
"15" === 15 = false

* 0xF to liczba 15 zapisana w notacji szesnastkowej

Konsekwencje

Może to prowadzić do mniej lub bardziej poważnych luk w bezpieczeństwie aplikacji, mam na to banalny i trywialny przykład, nie realistyczny, zbyt prosty by występował w naturze (a przynajmniej módlmy się o to). Mimo tego demonstruje on konsekwencje korzystania z luźnego porównania.

<?php
if (!empty($_POST['auth'])) { // Sprawdzamy czy $POST['auth'] nie jest puste
        if ($_POST['auth'] == "00001234") { // Sprawdzamy czy $POST['auth'] jest równe naszemu pinowi
                echo("Dostępu udzielono");
        }
        die(); // kończymy wykonywanie skryptu (nie wyświetlamy html poniżej)
}
?>
<form action="index.php" method="POST">
PIN: <input name="auth" type="number"></input>
<input type="submit"/>
</form>

Nasz kod porównuje pin w aplikacji (w domyśle może być on wygenerowany losowo na podstawie czasu, na przykład jak przy weryfikacji dwuetapowej) z tym wpisanym przez użytkownika.

Na pierwszy rzut oka mamy 10^8 kombinacji pinu (8 pozycji po 10 znaków), całkiem sporo możliwości jak na siłowy sposób szczególnie że nasz pin może zmieniać się co parę sekund (jak to często bywa przy weryfikacji dwuetapowej). Gdyby mimo tego spróbować uzyskać dostęp siłą (sprawdzając wszystkie możliwości po kolei) czyli licząc od 0 przez 1, 2, …, 10, 11, …, 100, 101, … i tak dalej. To dostęp otrzymamy już przy… 1234 próbie.

Otóż gdyby interpreter PHP upersonifikować to jego myśli zapewne brzmiałby w przybliżeniu tak:

„Oh w $_POST[‘auth’] jest string, który wygląda jak liczba! O i w podanym mi drugim argumencie też! Programiście pewnie chodziło o porównanie dwóch liczb!”

A więc string "00001234" jak i również string "1234" zmienił on nam na liczbę 1234!

Podsumowując

Przez nie których type juggling uważany jest za feature, bo ułatwia czasami życie programistom, a przez innych za antifeature, bo oprogramowanie nie robi dokładnie tego co dyktuje mu programista. Należy, więc uważać na to ile znaków się równości się stawia, co się porównuje ze sobą oraz jak PHP zmyślnie sobie to wszystko interpretuje (tutaj jest napisane jak).