Wordpress - How bad is it if I write AJAX functions using wp-load.php?

It's incredibly bad, and a major security hole, you're going to have to face these issues:

  • Your endpoint will always work, even if your plugin is deactivated, or you switch themes
  • Your endpoint will be active on all sites on a multisite install, not just those you intended it for, which can lead the endpoint being used in places it isn't intended
  • The file will be fragile, moving your plugins folder, or putting the plugin in mu-plugins will cause the endpoint to give 500 fatal errors
  • You will need to implement all of the authentication and validation yourself
  • Caching plugins will be unable to interact with your endpoint, slowing things down for common requests
  • Said endpoint can accept anything and it could return anything

It would be an understatement to say that standalone endpoints in themes and plugins are a security risk, be it an AJAX handler or a form handler, or even a standalone file for images ( timthumb did this and even the original devs have disowned it due to its security reputation )

You could use the WP AJAX API, which is better, but requires you to implement any auth or sanitising checks yourself, and has some quirks. It also provides no structure, as with a standard endpoint it can receive anything and return anything ( if it returns at all ).

Instead, use the REST API with register_rest_route, it's easier to use, has fewer quirks, and handles authentication for you. It also gives you friendlier URLs and namespacing, e.g.:

function tomjn_rest_test() {
        return "moomins";
}
add_action( 'rest_api_init', function () {
        register_rest_route( 'tomjn/v1', '/test/', array(
                'methods' => 'GET',
                'callback' => 'tomjn_rest_test'
        ) );
} );

You can see this at:

https://tomjn.com/wp-json/tomjn/v1/test

With this I can do a number of things:

  • specify that the user must be logged in and exactly what they need to be able to do this
  • specify the arguments
    • specify the type and sanitiser for each argument separately
    • specify if they're required or not
  • specify if an endpoint is for reading writing or both

All of which are handled by code written for core. Of course you can implement all of this yourself by hand in each WP AJAX handler, but this is much more concise and tested ( and there are few in the WP community experienced enough to do this properly ), e.g. to make my endpoint above only available for admins:

register_rest_route( 'tomjn/v1', '/test/', array(
    'methods' => 'GET',
    'callback' => 'tomjn_rest_test',
    'permission_callback' => function( $req ) {
        return current_user_can('manage_options');
    }
) );

As a bonus, it makes certain best practices much easier to use, such as returning data not HTML, uses standard HTTP GET PUT POST etc, and returns a JSON response that can be validated

What's so bad about an endpoint being always active?

Earlier I mentioned that a native endpoint is active, even if the plugin it's inside is disabled. The only way to turn it off is via internal checks, or by deleting the file. Why is this bad though?

The crux of this, is that what might be appropriate in one circumstance, may not be in another.

For example, scenario 1:

An admin installs a plugin that allows users to login and register on the frontend without refreshing the page. To do this the plugin has an endpoint to create users. However, the admin realises that registration isn't what they wanted, only login, and deactivates the plugin. The files are still there however, and users can still load the endpoint to create new users.

Scenario 2:

A plugin is being used on a multisite install to send emails to the administrator of a blog via a contact form. This is all working as expected, but there are other blogs on the site who have no interest in this functionality. By changing the domain used for the blog, an attacker can send emails to the admins of these other blogs by loading the endpoint used by the contact form in the context of the other blogs on the network, sending emails to anybody who has an admin role anywhere on the multisite installation

Scenario 3:

A plugin has a useful debug feature that allows users to change their emails, but for security reasons you've turned this feature off. However, the endpoint that handles the email change form is a standalone file, anybody who knows how the form is built can still change their email


The main downside of writing a custom file endpoint is that your file would not know where wp-load.php is in general case. Whenever you are in WP environment you have the context provided. If you are trying to find where WP core from arbitrary PHP file it's not going to work for all possible configurations out there.

So the biggest downside of this technique is that it cannot be distributed in public extensions.