WordPress JSON API Security Options

Reading time: 5m

What’s the problem with WordPress JSON API?

Since the release of the JSON REST API in WordPress version 4.7.0, it’s become even easier to exploit people’s sites, without even having to look at the actual site.  At it’s default settings, it gladly tells anyone who asks anything about your site that the API has access to.

What can I do to fix it?

There are two ways you can go about fixing the problem.

Disable the WordPress JSON API completely

Simply put the following code into your theme’s functions.php file.  This will completely disable the JSON API, and any reference to it in the generated source code.  Note that this may disable future functionality which relies on the JSON API.

remove_action('wp_head', 'rest_output_link_wp_head', 10);
remove_action('template_redirect', 'rest_output_link_header', 11, 0);
remove_action('wp_head', 'wp_oembed_add_discovery_links', 10);
add_filter('rest_enabled', '_return_false');
add_filter('rest_jsonp_enabled', '_return_false');

Require authentication to use the WordPress JSON API

The other, less nuclear method will allow you to require authentication before executing a JSON API request.  This is much safer, and would require at the least a user account to bypass, though you can control the ability to register in WordPress already.
Updated 06/17/2021 – Call native \WP_Error in case functions.php is namespaced

add_filter('rest_authentication_errors',
  function($result) {
    if (!empty($result)) return $result; // Return empty results
    if (!is_user_logged_in()) return new \WP_Error('rest_not_logged_in','You are not currently logged in.',array('status'=>401)); // Don't return non-empty results unless logged in
    return $result;
  }
);

Other mitigation strategies

ModSecurity

If you just want to block JSON API requests outright, and don’t care about it being unusable, you can add a ModSecurity rule like this:

SecRule REQUEST_URI \
  "^/wp-json\/?.*$" \
  "phase:1,deny,log,noauditlog,status:403,id:'100121', \
  t:lowercase,t:removeNulls,t:urlDecode, \
  msg:'WordPress JSON REST API Filter'"

IP Filtering

If you’d rather keep it enabled, but restricted it to trusted IP addresses, you can do it this way as well:

<LocationMatch "^/wp-json\/?.*">
  <Limit GET POST PATCH PUT DELETE HEAD>
     Allow from 127.0.0.1
     Allow from YOUR.IP
     Deny from All
  </Limit>
</LocationMatch>

The drawback to this is that to use JetPack, you’ll have to add JetPack’s IP ranges for access to wp-json in the Allow lines above.

Comments 2

  • Thanks for this bit of code under ModSecurity – but it spits out the result and then blocks you with a 406. So I didn’t think it was working since it takes 5 hits in my settings to block an IP.

    I changed to Phase:1 and status:301

    This stops the REST API data from displaying. Not sure I needed to change Phase but it works amazing now.

    • Thanks Shawn! I’ve updated the post to reflect phase:1, that was my error. I’m blocking with 406 because you can then grep out blocks with code 406 from the error log to scrape hits. If you have integrated with CSF/LFD with banning for repeated 403 hits, then it should be changed to 403. 301 will work if you have a redirection to somewhere for that person to go. Most time it’s not worth the redirect when you can simply block. This rule (for me) catches a lot of scanners and bots

Leave a Reply

Your email address will not be published.