Security Tip: Watch Out for Type Juggling

[Tip#26] Type Juggling is still very much a problem.

Security Tip: Watch Out for Type Juggling

PHP and type juggling…

In my opinion, type juggling is one of PHP’s strengths, but also one of PHP’s easily exploitable weaknesses.

The weakness is the result of using a loose comparison ( == and != ), rather than a strict comparison ( === and !== ), and if I can impart just one thing from this post, it’s a reminder to always use a strict comparison, unless you specifically need a loose comparison. You also need to be aware of which functions are strict and loose, and code accordingly.

Important note: If you’re comparing sensitive strings like API keys or passwords, you really should use the hash_equals() method. It ensures a type and timing safe comparison. I wrote about it here!

Being vulnerable to type juggling is a common argument used against PHP and it was such a big concern that PHP 8 introduced a backwards incompatible change that changed string-to-number comparisons so they would convert to strings and not numbers.

This should show you the before and after behaviour:

Comparison      PHP 7.4 and earlier    PHP 8.0+
0 == "0"        true                   true
0 == "0.0"      true                   true
0 == "foo"      true                   false
0 == ""         true                   false
42 == " 42"     true                   true
42 == "42foo"   true                   false

While this was a great security update to PHP, it didn’t solve the whole problem. Sure you can no longer easily juggle numbers and strings maliciously, but there are more inputs you can provide that you still can juggle with.

For example: Booleans!

The easiest way to exploit type juggling is through an API endpoint, because you’re not limited to passing everything in as a string. Instead, you can submit a Boolean directly to the API and the code will see it as one.

Let’s take a look at a super simple example:

Route::post('test', function (Request $request) {
    return [
        '$request->input' => $input = $request->input,
        "'string' == \$input"  => 'string' == $input,
        "'string' != \$input"  => 'string' != $input,
        "'string' === \$input" => 'string' === $input,
        "'string' !== \$input" => 'string' !== $input,
    ];
});

Using the above API route, let’s send some true and false values:

>>> Http::post('https://larasec/api/test', [
    'input' => true,
])->json();
=> [
     "$request->input" => true,
     "'string' == $input" => true,
     "'string' != $input" => false,
     "'string' === $input" => false,
     "'string' !== $input" => true,
   ]

>>> Http::post('https://larasec/api/test', [
    'input' => false,
])->json();
=> [
     "$request->input" => false,
     "'string' == $input" => false,
     "'string' != $input" => true,
     "'string' === $input" => false,
     "'string' !== $input" => true,
   ]

You’ll note that passing in the Boolean value true allows us to pretend we’ve passed in a valid string:

'string' == $input  => true
'string' != $input  => false

In this example, we’ve got an API checking a string value, but what if that string value is the API authentication key that provides access to the API?

We’ve bypassed the auth key by passing true instead… Full access granted, no API key required. 😱

I have found this exact vulnerability in a number of my security audits. An auth token is received by the app via a JSON payload, swapping it to `true` instantly gave me access. Every. Single. Time. I even did it via a cookie once. 😈

Do you do this in your app?

Type juggling is a problem, and it’s not just limited to Boolean values and API endpoints. Check your app to ensure you’re doing strict comparisons and validate & typecast inputs to ensure they are in the formats you need.


🕵️
When was your last security audit or penetration test? Book in a Laravel Security Audit and Penetration Test today!
🥷
Looking to dive deeper into Laravel security? Check out Practical Laravel Security, my hands-on security course that uses interactive hacking challenges to teach you about how vulnerabilities work, so you can avoid them in your own code!