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!

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!

A Privilege Escalation attack is where an attacker is able to escalate their access privileges, such as a standard user gaining admin abilities. They can occur in a variety of forms and sometimes rely on stringing smaller vulnerabilities together to complete the attack.

Let's take a look at an example I encountered recently:

class User extends Authenticatable
    // ...

    public function isAdmin(): bool
        return Str::contains($this->email, '');

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 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:

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.

The 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. 😈

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!