Security Tip: Please Stop Hardcoding Admin Domains!

[Tip #99] Let me tell you a story about a time when a single missing character allowed me to escalate my privileges and gain admin access, despite all the protections designed to stop me! 😈

Security Tip: Please Stop Hardcoding Admin Domains!
πŸ’‘
We're digging into the weaknesses identified in the updated Laravel Security Audits Top 10 list for 2024. This week we're looking at #2 - Committed Credentials & Admin Emails!

Something I come across a lot when conducting security audits is hardcoded credentials and API keys. They'll usually be sitting directly in the code where they are used, scattered throughout the app and duplicated every time the app needs them. I'll also typically find hardcoded admin email addresses or admin wildcard domains in policies and gates too - all of which inside the code itself.

I've talked this issue a few times before, but since it's still sitting at #2 in my Top 10, a reminder is clearly needed, so let me tell you a story about a recent security audit I did...

One of the steps in my audit process is to check Service Providers for any interesting-looking code, such as hardcoded credentials and authorisation gates. As is pretty typical, this site did not disappoint, and I discovered the following authorisation gate in the Nova service provider.

Gate::define('viewNova', function ($user) {
    $allowedDomain = 'company.com';
    $allowedEmails = ['user@gmail.com'];

    if (str_ends_with($user->email, '@' . $allowedDomain)) {
        return true;
    }

    if (in_array($user->email, $allowedEmails)) {
        return true;
    }

    return false;
});

\App\Providers\NovaServiceProvider

As you would expect, this code got me rather excited for the possibilities - could I exploit this to access Nova?

The str_ends_with() method requires the email to end in @company.com, which means all I had to do was change my email address to something like hacker@company.com... in theory.

I headed over to my Profile edit page, tried to update my email address to hacker@company.com, and saw a very disappointing validation error:

The email address cannot end in @company.com.

I then proceeded to look for other forms where I could submit an email address, such as:

  • User Registration Page
  • Admin User Creation (tenant level, outside Nova)
  • Admin User Edit (tenant level, outside Nova)

All three (plus the profile edit) had the same validation rule blocking email addresses that ended in @company.com. 😭

I kept digging and started to look for other areas the User model was used, to see if there were other forms that allow for email to be set, and discovered this method on the User model itself:

public function isSystemAdmin(): bool
{
    return str_ends_with($this->email, 'company.com');
}

\App\Models\User

Which is very similar to the Nova gate, but with one massive difference: it's missing the @ prefix. Which means an email address of hacker@evilcompany.com will be considered a System Admin! πŸŽ‰

A few seconds of digging later and I find the UserIsSystemAdmin Middleware uses this method internally, which protects a bunch of non-Nova system admin controls. I'm in! 😈

Summary

This story really demonstrates the risks involved in hardcoding admin domains in your code.

While the Nova Authorisation Gate and Form Validation were functioning correctly, preventing emails with @company.com from being used, the fact that the company domain was duplicated within the code meant a subtle difference (in this case, a missing @) left the app vulnerable. Although I wasn't able to gain access to Nova directly, I was still able to escalate my access well beyond what my account was supposed to have.

My recommendation is:

Don't use domain wildcards for authorisation controls, define every admin manually, and either define your admin emails in a .env variable, or define your admins through a database flag.

This allows you to be intentional about who has admin access, easily review the list, and update it in a central location.


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.

Looking for a Laravel Security Audit / Penetration Test, or a budget-friendly Security Review? Feel free to reach out! You can also connect with me on Bluesky, or other socials. And don’t miss Practical Laravel Security, my interactive course designed to boost your Laravel security skills.