Juggling with PHP types

What the juggle

PHP’s type juggling is the result of PHP not requiring (or supporting) explicit type definitions in variable declaration. This is due to the fact variable types are determined by the context in which the variable is used.

<?php
$foo = "1";
$bar = 2;
?>

In the above code, we have two variables foo and bar. Speech marks are used to encase the value assigned to foo, this single action causes the foo variable to become a string. You will notice the lack of speech marks encasing the variable bar, this results in bar being considered an integer. We can use the interactive PHP prompt to confirm this:

php -a
Interactive mode enabled

php > $foo = 1;
php > $bar = "2";
php > echo gettype($foo);
integer
php > echo gettype($bar);
string

In that case what happens when we try to add foo to bar? Will we get 12, 3 or an error because surly you can’t add an integer and a string together:

php > echo $foo+$bar;
3

Who guessed correctly? Why do we get 3 as a result of adding a string and integer together? Well as stated above, this is due to the context the variables were used in. PHP saw that we wanted to add these two items together, it noted both values assigned to the variables were numbers (regardless of their type) and decided it would be helpful to us by juggling the type of bar from a string to an integer.

What’s the issue with juggling?

You may be thinking:

“PHP is just trying to help us out, I can’t see the problem with that”

You, the reader

Well this juggling of types can result in weird and wonderful issues within your applications that you didn’t foresee. We will provide a few examples of this in the following section.

Can you juggle?

Let’s jump into the meat of things with some examples. I’ve got a very basic PHP script below that makes use of the strstr function to look for an instance of “type juggle” in our $passwdguess variable, in the same if statement we also have a comparison checking if $passwdguess is greater than 10. Yes, that is correct, we are also checking that our password stored in $passwdguess, which is a string, is greater than 10:

<?php

$passwdguess = "type juggle";

if(strstr($passwdguess,"type juggle") == ($passwdguess > 10)) {
  echo "Correct password\n";
} else {
  echo "Wrong password\n";
}

Running the code in the current state will result in “Wrong password” as can be seen below, as our “type juggle” password isn’t greater than 10:

We don’t meet both conditions of the if statement, resulting in “Wrong password”

We can use PHP type juggling here to help us meet of requirements of the if statement and all we have to do is set the password to “11type juggle” … because 11 is greater than 10 … and PHP …

This time we match on both of the conditions for the if statement, resulting in “Correct password”

So what happened here? I mean really, not some smart ass answer of “…PHP … types … juggling”, well PHP tried to do that thing again where it helps us out based on the context we are using the variables in. It thinks we want to use $passwdguess as an integer in this instance due to it starting with a number and being used within the if greater than statement.

You may be thinking that it is a stretch to think someone would use the code above as an authentication method and as such it doesn’t demonstrate a real-world example. I’d say that is fair, so I have created a more realistic example below, you will note the password is stored within the if statement as a way of not having to set up a database etc, but the essence of the code is there:

<?php
session_start();
$errorMsg = "";
$validUser = $_SESSION["login"] === true;
if(isset($_POST["sub"])) {
  $validUser = $_POST["username"] == "admin" && md5($_POST["password"]) == md5("240610708");
  if(!$validUser) $errorMsg = "Invalid username or password.";
  else $_SESSION["login"] = true;
}
if($validUser) {
   echo("You win"); die();
}
?>
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html;charset=utf-8" />
  <title>Login</title>
</head>
<body>
  <form name="input" action="" method="post">
    <label for="username">Username:</label><input type="text" value="<?= $_POST["username"] ?>" id="username" name="username" />
    <label for="password">Password:</label><input type="password" value="" id="password" name="password" />
    <input type="submit" value="login" name="sub" />
    <div class="error"><?= $errorMsg ?></div>
  </form>
<p>The admin's password is 240610708, this site uses md5 to encrypt the password. Can you login with anything other than 240610708? <!-- QNKCDZO -->
</body>
</html>

So the app requires you to log in as the user admin, using a password of “240610708”. In the code, this password and the users entered password is hashed using the MD5 algorithm, if the passwords don’t match you aren’t logged in, if they do you are and get presented with a message of “You win”.

So a lot of people would assume that you can only log in to this application if you have the correct username and password, however, that isn’t true due to type juggling.

It isn’t by chance that the md5 hash of “240610708” starts with “0e” followed by digits. This is because PHP is interpreting this as the scientific notation for 0:

php -a
Interactive mode enabled

php > if ('0e1337' == '0') {
php {   echo "we are the same";
php { }
we are the same

This means all we need to do is find a plain text string that results in an md5 hash in the same format, these hashes have been coined as “magic hashes”, maybe you noticed it within the comment tags of the code above, “QNKCDZO”. We can confirm this with the PHP interactive shell:

php -a
Interactive mode enabled

php > if (md5("240610708") == md5("QNKCDZO")) {
php {   echo "Same but diff?";
php { }
Same but diff?

Attempting to log into the application with a password of “QNKCDZO” will result in the “You win” message being displayed.

Clipping a jugglers wings

To prevent these issues, we need to move away from loose to strict comparison. What does this mean? We should consider “==” as loose and “===” as strict

php -a
Interactive mode enabled

php > if (md5("240610708") == md5("QNKCDZO")) { echo "Same but diff?"; }
Same but diff?
php > if (md5("240610708") === md5("QNKCDZO")) { echo "Same but diff?"; }else{ echo "That's a no from us"; }
That's a no from us

Above we can see the difference between using loose and strict comparison, further details on the comparison operators can be seen here.

Fin!

So that’s pretty much the end of the post, writing this has helped to solidify my understanding of PHP type juggling issues (I hope) and may end up being of use to someone else. Any comments, questions or suggested corrections can be left in the comments, thanks.

*** EDIT ***

Just to be clear, the issues covered in this post are not the only places that type juggling is an issue. They are just two examples.

References

While putting this post together, I found a number of sources useful. They have been added below: