Security Tip: Don't Hardcode Admin Emails
[Tip#17] It's easy to forget to update the admins list when it changes...
Hardcoded admin emails (or usernames) are something I’ve seen in almost every codebase I’ve worked on and/or audited. That’s really not surprising though, since the official documentation itself basically tells you to do it like this. However when it comes to security, it’s a terrible idea...
Let’s take Laravel Nova as an example. The Nova documentation tells you to add a Gate definition into your app/Providers/NovaServiceProvider.php
to define admin users.
Consider this Gate:
Gate::define('viewNova', function ($user) {
return in_array($user->email, [
'frodo@example.com',
'samwise@example.com',
'meriadoc@example.com',
'peregrin@example.com',
'fredegar@example.com',
]);
});
It looks simple and easy to update, but it’s 2 levels deep inside a system class, so it’s super easy to forget about. If one of the admins leaves the company (or loses admin access), there is a good chance their email address will be left in the list and retain admin access.
If their account is compromised or given to someone else, that admin access will be available when it shouldn’t be. This potentially opens up admin access to a hacker or someone with malicious intentions. In this example, Nova gives full access to modify the database - which gives the hacker full access to do whatever they want.
Also, if you’re using multiple packages with their own authorization lists, it’s easy to forget some of them when adding/removing admins, leaving lingering access in some areas.
The worst offender is putting the admin list in your javascript. This is sent to the browser, so anyone who discovers it has full access to your list of admins. It’s then trivial to look up credential stuffing lists to find potentially working passwords for a site admin account. 😱
What To Do Instead
There are some really simple ways to avoid hardcoding admin emails. I usually either store admin permissions in the database, or define emails in configuration files. It doesn’t really matter how you do it as long as it’s not hardcoded, and is easy to change and review securely.
Here are two example solutions:
1. Define an admin
flag on the user models:
Gate::define('viewNova', function ($user) {
return $user->admin;
});
This also solves the Javascript problem, as the user model data passed to the browser can contain the admin flag too.
2. Store the admins list in .env:
// .env
APP_ADMINS="frodo@example.com,samwise@example.com,meriadoc@example.com,peregrin@example.com,fredegar@example.com"
// config/app.php
'admins' => explode(',', env('APP_ADMINS')),
// NovaServiceProvider
Gate::define('viewNova', function ($user) {
return in_array($user->email, config('app.admins'));
});
Found this security tip helpful? Don't forget to subscribe to receive new Security Tips each week, and upgrade to a premium subscription to receive monthly In Depth articles, or toss a coin in the tip jar.
Reach out if you're looking for a Laravel Security Audit and Penetration Test or a budget-friendly Security Review, and find me on the various socials through Pinkary. Finally, don't forget to check out Practical Laravel Security, my interactive security course.