Security Tip: Privilege Escalation Through Domain Wildcards!
[Tip #80] It's incredibly common to find hardcoded domains used for identifying admins, however this also makes it trivial to escalate privileges to admin!
![Security Tip: Privilege Escalation Through Domain Wildcards!](/content/images/size/w1200/2024/05/Securing-Laravel---Highlight--1-.png)
One of the things I look for during my security audits are hardcoded domains used for identifying admins, because they can often be manipulated to allow for Privilege Escalation attacks. Sometimes incredibly easily too!
Let's take a look at an example I encountered recently:
class User extends Authenticatable
{
// ...
public function isAdmin(): bool
{
return Str::contains($this->email, '@securinglaravel.com');
}
}
While this looks great at first glance, there are two major weaknesses that make this incredibly easy to exploit.
#1 - No Email Verification
The User
class is missing the Illuminate\Contracts\Auth\MustVerifyEmail
contract, and email verification hasn't been implemented elsewhere manually. This allows an attacker to change their email address to something like hacker@securinglaravel.com
and immediately gain admin powers, without requiring access to this email account.
Enabling verification and using the verified
middleware to protect routes would prevent the attacker from gaining access to admin powers, as they wouldn't be able to receive the verification email.
Check out the Email Verification documentation for details on how this works.
However, there is a way to defeat email verification with this example too...
#2 - String Contains?
Hopefully you noticed the use of Str::contains()
instead of Str::endsWith()
? Well, that allows you to get creative with your domain names. Such as:
hacker@securinglaravel.com.evilhacker.dev
Which is a legitimate email address the attacker can control and receive Email Verification emails at, allowing them to verify their account and gain admin abilities.
Str::contains()
actually came from GitHub's Copilot. I used it to generate the demo code above, expecting it to use something like Str::endsWith()
and instead it used Str::contains()
!It may seem obviously vulnerable when you're looking at the code with a security lens, but when you're in the middle of a coding session, you could easily look at that code and just see working code - not vulnerable code.
My Recommendation?
I recommend either using a database column (or separate admins table), or a config list of full email addresses to indicate admin accounts. That way your admins list is specific - you can see every user who should have admin powers, and if it's in the DB or in .env
, you can easily change it as staffing changes.
Hardcoding domains (or raw email addresses) directly in your code may come back to bite you in the future. I know this because I've successfully abused hardcoded domains in a number of my security audits. 😈