

Discover more from Securing Laravel
Greetings my friends! Firstly, if you keep track of our schedule, you’ll have been expecting an In Depth email this week… this isn’t that. 😞 I’ve had to bump it to next week1, so I’ve got a security tip for y stou today instead. Today we’re covering Type Juggling, which may well prove to be an eye-opener for you!
Don’t forget to check out last week’s tip about Login Logging, it’s been quite popular, and I’ve added a link to example logging code I’ve used on one of my apps.
Type Juggling
PHP and type juggling. It’s one of PHP strengths2, 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 accordingly3.
UPDATE: 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 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 Before After
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. 😱
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.
Due to a combination of 2 conferences (I was speaking at NDC Melbourne), lots of travel, and school holidays, I haven’t been able to dedicate the time needed. Rather than produce something I’m not happy with, I’ve decided to send out a tip this week and do the In Depth next week.
In my not-too-humble opinion.
Type-casting is fantastic for ensuring loose comparison functions don’t open vulnerabilities.