One of the biggest recent attacks against sites developed in WordPress originated from an existing vulnerability in the REST API. The bug came to WordPress by introducing the core REST API endpoints in version 4.7 and continued through 4.7.1. The security flaw allowed an attacker to change the content of any article.
Worst of all, accentuating the seriousness of the problem, is that the attack itself is as simple as downloading a script, pointing to a domain and, if the WordPress version matches, using the REST API to exchange the content of any article.
How to solve the problem and be more secure
- Update WordPress!
- Block external access to the REST API
- Ensure automatic updates
At the end of the article, I will explain what the REST API is and how these attacks work. Until then, I will show you how to protect the REST API.
Block external access to the REST API
There are two ways to do this: via server or plugins. The best solution is at server level. But if you do not have server admin access, use the plugins.
Server
I advise you to do server level for two very simple reasons. Less resources used and earlier. Typically, if the security is done by a plugin, the request pass through the server, PHP, WordPress and finally parsed by the plugin.
So we will cut the request as soon as possible. If you do not have fail2ban yet, there is this tutorial for VPS by Linode. Requirements are two essential tools on any server:
- fail2ban
- iptables
Protect
To protect, I will use a simple rule in nginx:
location ~ / wp-json / wp / v2 / allow; deny all; try_files $ uri $ uri / /index.php?$args; }
By default, the server responds with 403, but we will respond 404 to confuse it a bit more. There is an advantage to this thinking, called Obscurity as a Layer. There is a fantastic presentation in DEFCON 2013 about how you can fool bots by returning random or simply wrong codes.
To return 404 Not found, instead of 403 Forbidden, this line must also be included in the server {} block.
error_page 403 =404 /404.html;
We should do the same for xmlrpc.php, with "location ~ xmlrpc \ .php"
Block
To block the request we have to have some kind of signal. Let's use fail2ban and iptables for this task. The idea is to analyze the logs of your nginx, search the IP, compare with the last two days and, if you find at least two requests, ban for a year.
/etc/fail2ban/jail.local [wp-json] enabled = true port = http, https filter = wp-json action = iptables-multiport [name = WordPressJSON, port = "http, https"] wp-json [name = WordPressJSON] logpath = /var/log/nginx/*access.log/var/log/nginx/*access.log.1 maxretry = 2 findtime = 172800 bantime = 31539000
This action is responsible for creating the ” f2b-WordPressJSON “ chain list , adding an IP to the list of banned IPs.
Next we will create a new action to write the IP and write in the logs. To keep the list without duplicates, when this action is started, we clean and sort the IPs.
/etc/fail2ban/action.d/wp-json.conf [Definition] actionstart = touch /var/log/fail2ban/wordpress-json.log printf %% b "\ n" "/var/log/fail2ban/wordpress-json.log cat /etc/fail2ban/persistent.bans | awk '/ ^ fail2ban- / {print $ 2}' | while read IP; of iptables -I fail2ban- 1 -s $ IP -j ; done actionstop = echo ""> /var/log/fail2ban/wordpress-json.log actioncheck = actionban = printf %% b "+ : \ n" "/var/log/fail2ban/wordpress-json.log actionunban = printf %% b "- : \ n" "/var/log/fail2ban/wordpress-json.log [Init] init =
This filter looks in the logs by lines with IP and “wp-json”. We can further detail failregex if there are many false positives. The failregex depends on the structure of the logs.
/etc/fail2ban/filter.d/wp-json.conf [INCLUDES] before = common.conf [Definition] _daemon = wordpress failregex = ^. * wp-json. * $ ignoreregex =
Plugins
If you can not use the server or prefer to use plugins you also have options. Let’s go to them.
- Block REST API
- Disable json API
This plugin blocks requests to the REST API and is extra-light.
Cerber Security
This plugin was not parsed but promises to block REST API (and xmlrpc).
Wordfence
Not parsed, but promises to only block this endpoint, not the entire REST API.
No plugins, code removed from stackoverflow.com
The use of the REST API is legitimate, if it does not have security holes. To block wp-json / wp / v2 / users / users /, the following code has been tested:
add_filter ('rest_endpoints', function ($ endpoints) { if (isset ($ endpoints ['/ wp / v2 / users'])) { unset ($ endpoints ['/ wp / v2 / users']); } });
Disable the REST API fully
We may think that is not necessary, but the reality is that WordPress already depends on it and will increasingly replace other already existing APIs. So I do not recommend disabling it.
To ensure automatic updates
Although automatic updates, which were introduced in version 3.7, are enabled by default, a site may not be updated. Updates are automatic if they are between smaller versions, that is, 4.6 to 4.6.1, or 4.6.1 to 4.6.2.
If you leave a new base version, 4.7 for example, manual updating is required.
To update translations
add_filter ('auto_update_translation', '__return_true');
To update the plugins
add_filter ('auto_update_plugin', '__return_true');
To update the themes
add_filter ('auto_update_theme', '__return_true');
To upgrade the core, including major and minor versions:
define ('WP_AUTO_UPDATE_CORE', true);
Either way, it is important to always confirm that the site is up to date.
REST API
What is the REST API? In fact the full title is JSON REST API. Lets do it by steps:
JSON
The sites display the HTML information in the browser, but in the REST API, the information is grouped in JSON.
JSON is JavaScript Object Notation. It is therefore only a different, but more organized, machine-oriented format.
REST
It means Representational State Transfer, and implies an organizational structure of the data and allows basic CRUD actions: Create, Read, Update and Delete.
For example, we can read the list of all users with GET / wp-json / wp / v2 / users.
Or create a new post with POST / wp-json / wp / v2 / posts.
Or simply have a complete view of all the routes and actions (GET, POST, DELETE, etc) with the following
endpoint: GET / wp-json / wp / v2 /
In the same way that we organize articles by categories and sub-pages, the REST API information is well structured. We already know where we can see users, so to find information about the user with id 33, just add 33 to the endpoint: GET / wp-json / wp / v2 / users / 33.
API
It means Application Programming Interface. Represents the bridge between the REST structure and the PHP code of WordPress. Each action has an equivalent function in the WordPress code.
All together, there is a structured mapping of the backoffice in some important functionalities. To further these notions, consult the documentation.
Attack
The attack itself is incredibly simple. Based on what I showed above, we can understand what happens:
If we do a GET action in the / wp-json / wp / v2 / posts / 123 path, the API returns the content of article 123.
If we do POST / wp-json / wp / v2 / posts / 123, API tells us that we are not allowed to change article 123 and rightly so.
But what if we do POST / wp-json / wp / v2 / posts / 123aaaa, will it make a difference?
Obviously WordPress will not find an article with ID 123aaaa, because all IDs are just numeric. But internally, WordPress will try to convert 123aaaa to number 123 and try to compare if the ID exists and if it has permissions.
To realize where the flaw is we have to realize that for WordPress, both of the following conditions must
occur:
1- Does 123 exist? yea;
2 – have permissions to change article 123? not
and therefore can change? do not.
But if the id does not exist, it is not necessary to check if the user has permissions to change an article that does not exist. In this case, WordPress does not validate the second condition and gives no error.
That is, for WordPress, you can continue to run the rest of the code because there was no error!
This is sufficient to grant permission to the request and let the content of the article change. In fact, it is a bit more complex, as you can see in the analysis made by Sucuri.
That is why so far the attacks have been left to stay by just changing the content of articles, but still can access other parts of the site. But with the combination of some plugins is possible!