This site uses cookies in accordance with our Privacy Policy.
2.3: How to Prevent Authentication Bypass Vulnerabilities
Authentication bypass vulnerabilities are one of the less common vulnerabilities we see, but they are also one of the easiest to accidentally create as a WordPress plugin author. So we thought it would be useful to include a short lesson on common pitfalls that lead to these kinds of vulnerabilities.
Beware of is_admin()
There is a function in the WordPress API called is_admin(). If you are a developer who is new to WordPress development, it’s easy to assume that this function checks if a site visitor is an admin. That is not what this function does.
The is_admin() function actually checks if the administrator panel is being displayed. It is a badly named function and we have seen multiple developers make the mistake of using this function to try to verify if a site visitor has access to an administrative function.
A common mistake is to create code that looks like the following:
if(is_admin()){ //Incorrect check to see if admin add_action('wp_ajax_your_function', 'yourClass::yourFunction'); }
The is_admin() function is incorrectly used above to check if a visitor is an admin. That is not what it does. If yourClass::yourFunction() performs an operation that should only be accessible to an administrator, then this is an authentication bypass vulnerability.
How to Correctly Check Permissions
To correctly check if a user should have access to a function, it is important to understand a bit about WordPress permissions. When you create a WordPress user, they have a certain role. There are six predefined roles in WordPress:
- Super Admin
- Administrator
- Editor
- Author
- Contributor
- Subscriber
The above roles are in descending order of how much access they have. These roles have what WordPress calls ‘capabilities’ or things they can do. On a single WordPress site, the ‘super admin’ and ‘administrator’ role are the same. ‘super admin’ has some extra capabilities on a WordPress multi-site installation.
The table below gives you an idea of what each WordPress role is able to do.
The API is fully documented on WordPress.org.
To check if a user is an administrator on a single-site WordPress installation, you can change our above code to the following:
if(current_user_can('manage_options')){ //INCORRECT on multi-site. See below. add_action('wp_ajax_your_function', 'yourClass::yourFunction'); }
In the above code we check if the user has the ‘manage_options’ capability. Only administrators and super admins in WordPress have this capability.
There is a problem with the above code. If your plugin is installed on WordPress multi-site, both administrators and super-admins have the ‘manage_options’ capability. If you are providing a function that only Administrators can access on single-site WordPress and only Super Admin’s can access on WordPress multi-site, you can change the code to the following:
$hasAccess = false; if(is_multisite()){ if(current_user_can('manage_network')){ $hasAccess = true; } } else { if(current_user_can('manage_options')){ $hasAccess = true; } } if($hasAccess){ add_action('wp_ajax_your_function', 'yourClass::yourFunction'); }
To be extra safe, you may want to add these checks into a function called hasAccess() that you define. Then you should do the check before you define the privileged action and do the check again inside the function that performs the privileged operation. While this may not seem performant, it will protect you if you decide to use the privileged function elsewhere in the code.
The check we do above checks if the current user can ‘manage_network’ if it’s a multi-site installation. Only a Super Admin on multi-site can manage_network. If it’s a single-site, it checks if the user can manage_options, which only Administrators on single-site can do. This ensures that only Super Admin’s on multi-site and Administrators on single-site can call your privileged function.
Conclusion
Now that you know how to correctly check permissions in your WordPress plugin or theme, you can create a function in your code that checks permissions correctly. Then you can liberally sprinkle that function around your code wherever you perform a privileged operation to ensure that only users with the correct access level have permission to perform your privileged operations.