Security Tip: Type Coercion in Broadcast Routes!

[Tip #104] It's easy for type juggling to sneak into authorisation callbacks, especially when types are ambiguous, and if you're not careful, you may be leaving a massive hole waiting to be exploited! 😱

Security Tip: Type Coercion in Broadcast Routes!
💡
We're digging into the weaknesses identified in the updated Laravel Security Audits Top 10 list for 2024. This week we're looking at #5 Missing or Insufficient Authorisation!

This week's security top could easily have fit into last week's In Depth: Common Authorisation Failures!, however I wanted to feature it specifically because of how subtle and dangerous this one is.

Take a good look at this code, is there anything wrong with it?

Broadcast::channel('users.{id}', function (User $user, $id) {
    return (bool) $user->id == $id;
});

routes/channels.php

I asked this question over on the various socials, so if you'd like to see other peoples thoughts or jump in on the game before reading on, go do that now at BlueSky, Mastodon, Twitter and LinkedIn.

Ok, are you ready for the answer?

I can see three issues with this code. The first two are comparatively minor, while the third is a massive problem. (Yes, I am going to draw this out!)

Issue #1: $id has no type hinting.

It is completely ambiguous what type $id is. The app uses numeric IDs, so it should be an integer, but it is also extracted from the string route name. So it could be a string... It's really not clear.

Either type hint it in the declaration, or in the conditional when you use it.

Broadcast::channel('users.{id}', function (User $user, int $id) {

Or you could follow a suggestion from my friend Sam Levy, who suggests type-hinting it as User $id, and you should get the whole model injected.

Broadcast::channel('users.{id}', function (User $user, User $id) {

Alongside this, you should ensure your User model returns $user->id as an integer as well. This is helpful for Issue #2...

Issue #2: That's a Loose Comparison!

The code uses == to compare the two values, allowing them to be coerced/juggled into different types! Given you're dealing with values which may be strings or integers, there is some ambiguity that could allow for coercion here.

$user->id === $id

That said, if you're on PHP 8.x (and you really should be) then the type juggling possibilities are very limited here. Comparing strings vs integers now convers to strings, which makes this pretty difficult to exploit - given your only input is delivered as a string.

Which is why I described issues #1 and #2 as "comparatively minor" issues, #3 however:

Issue #3: (bool) overrides the comparison into true == <value>!

The real issue here is that (bool) doesn't apply to the full result of the comparison, instead it applies to the left side of the comparison!

Let's add some brackets so it's incredibly obvious what's happening:

return ( (bool) $user->id ) == $id;

Since $user->id will always be a non-zero integer, it is casted to true:

return true == $id;

And since $id is also a non-zero integer, when referencing real user account, it is also true:

return true == true;

You can authenticate as any user when subscribing to private broadcast channel messages. 😱

For example, in the following screenshot I am logged in as User #12 and have been automatically subscribed to the private users.12 channel.

To subscribe to any other user's private channel, all I had to do was go into the browser console, type Echo.private('users.1');, and I'm in.

Subscribing to to other user's broadcast messages.

Yep, it's that easy...


If you found this security tip useful, subscribe to get weekly Security Tips straight to your inbox. Upgrade to a premium subscription for exclusive monthly In Depth articles, or drop a coin in the tip jar to show your support.

When was the last time you had a penetration test? Book a Laravel Security Audit and Penetration Test, or a budget-friendly Security Review!

You can also connect with me on Bluesky, or other socials, and check out Practical Laravel Security, my interactive course designed to boost your Laravel security skills.