My question is: When I write an AJAX function I can write it using a separate file. I can include wp-load.php
to that file and use WP core functions. I know this is wrong. Can you tell me how bad is it? Is it a big security risk?
What security risks and disadvantages do I have ?
-
2@ialocin Because you can prevent other (slow, buggy) plugins from running during your request.– fuxia ♦Oct 27, 2016 at 13:52
-
@toscho Why would you even use them then? I know that's probably an overly optimistic outlook on things.– Nicolai GrossherrOct 27, 2016 at 13:54
-
@ialocin I learned about it recently and I developed a theme for a client of mine using that wrong way. I was wondering– Dhanuka NuwanOct 27, 2016 at 13:58
-
1Guys, why is no one telling OP about the AJAX API? That's what he's asking about!– TheDeadMedicOct 27, 2016 at 14:04
-
2codex.wordpress.org/AJAX_in_Plugins– TheDeadMedicOct 27, 2016 at 14:05
2 Answers
It's incredibly bad, a major security hole, and easily avoided.
For a safer route, use a REST API endpoints or the old admin-ajax.php
API. Both of these are officially supported, documented, and bring other benefits such as pretty URLs to make requests too, support in debugging plugins such as query monitor, etc.
Why Is It Bad
If you use a standalone file for AJAX or form submission with wp-load.php
you're going to have to face some of these issues and more:
- 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
- There's no way for security code to intercept the requests
- The non-standard entry point means HTAccess files get bypassed
- Other WP code will be unaware it's an AJAX request
- 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 - Said endpoint can accept anything and it could return anything
- your webhosts security might flag it as malicious, most malware installs standalone PHP files once it's broken into your site
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 )
Better/Safer Alternatives
Admin AJAX
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 ).
REST API endpoints
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( $request ) {
return "moomins";
}
add_action( 'rest_api_init', function () {
register_rest_route( 'tomjn/v1', '/test/', [
'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
Scenario 4:
A standalone file is being used as a part of a theme, and time goes by. People forget about the file and it doesn't get updated, then it gets exploited. this was a widespread problem with libraries such as
timthumb.php
-
4I don't follow security reasoning. There is nothing that prevents from using normal WP authentication in custom endpoint file. Native ajax endpoint is no less vulnerable to security issues and there were plenty of vulnerabilities in plugins using it over years just as well.– RarstOct 27, 2016 at 15:24
-
By native AJAX endpoint do you mean a WP AJAX endpoint or a standalone endpoint in a file? Of course plugins that use AJAX might have other vulnerabilities, that's not relevant here unless you can demonstrate software free of bugs– Tom J Nowell ♦Oct 27, 2016 at 15:29
-
I mean that nothing in endpoint doing custom core load prevents native authentication (and everything else) from being used. Of course it's more involved to code (being custom), but there is nothing inherently insecure about it. It's like saying that native
nopriv
endpoint is "major security hole".– RarstOct 27, 2016 at 15:37 -
1will have to agree with @Rarst, an additional end point is a security risk, what technology is used is not very relevant. The REST routes can be unsecure as any other thing. Oct 27, 2016 at 15:37
-
2register_rest_route allows you to use an authentication path that relies on core internals, so long as you specify the roles and capabilities necessary you don't need to write that part of the code, which is far more reliable than making sure everyone who uses AJAX is able to write it themselves. In theory yes, they should all be equal, but that assumes a developer proficiency that I doubt even us in these comments can be trusted with, not to mention the other issues with native file based endpoints working for any site on a network not just the ones it's activated on– Tom J Nowell ♦Oct 27, 2016 at 16:17
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.