Securing the WP REST API
I think many WordPress users probably underestimate the amount of data that is made available via the REST API. Just about everything is available to anyone or anything that asks for it: posts, pages, categories, tags, comments, taxonomies, media, users, settings, and more. For most of these types of data, public access is useful. For example, if you have a JSON-powered news reader, it can basically replicate your entire site structure virtually anywhere. But that easy access invites potential abuse. Just like with RSS feeds, RESTfully delivered JSON content is easily scraped and used for spam, phishing, plagiarism, adsense, and other foul things.
User Data = Public Domain?
Beyond content theft, plagiarism, and such, the REST API opens the door to another potential security slash privacy concern over user data. By default every WordPress site delivers a significant amount of user data to anyone or anything that asks for it. For any user (of any role) that is the author of at least one post, their personal information is openly available to literally everyone.
So exactly which user data are exposed via the REST API? As explained in the documentation, the /users
endpoint delivers basically everything except for user email addresses and passwords. Everything else — ID, Name, Website, Description, URL, Metadata and more — all public domain thanks to REST API.
To give you a more concrete example of the data that is shared publicly via the REST API, consider the following URL:
https://digwp.com/wp-json/wp/v2/users/3
Here we are invoking the REST API by calling a specific user endpoint (i.e., user ID = 3). Requesting that URL in a browser, the following data are returned:
{
"id": 3,
"name": "Jeff Starr",
"url": "https:\/\/perishablepress.com",
"description": "Jeff Starr is a professional web developer and book author with over 15 years of experience...",
"link": "https:\/\/digwp.com\/author\/jeffstarr\/",
"slug": "jeffstarr",
"avatar_urls": {
"24":"https:\/\/secure.gravatar.com\/avatar\/...",
"48":"https:\/\/secure.gravatar.com\/avatar\/...",
"96":"https:\/\/secure.gravatar.com\/avatar\/..."
},
"meta": [],
"_links": {
"self": [{"href":"https:\/\/digwp.com\/wp-json\/wp\/v2\/users\/3"}],
"collection": [{"href":"https:\/\/digwp.com\/wp-json\/wp\/v2\/users"}]
}
},
This same information also is available at other endpoints, for example:
https://digwp.com/wp-json/wp/v2/users
There you will find the same amount of information provided for every qualified user. Note that you can try this on your own WordPress-powered site. Simply replace digwp.com
with your own domain name, and remember to include the subdirectory path if WordPress is installed in a subdirectory.
What’s the Risk?
So WP REST API and security. For everything except the user data, the main risks basically are the same as for RSS feeds. Scrapers and content thieves are savvy enough to steal your content regardless of format. If you make it easy for people to steal your content, they will. So whether they’re grabbing the data via RSS or JSON format, content is content, and the REST API makes it easier than ever for anyone and anything to manipulate your site’s content, categories, tags, meta, and much more. Is that acceptable? Totally your call.
Now for user data, we enter a whole new level of risk. With user data, the information is personal, so there is a potential privacy risk. Even worse, for every user, their “Name” by default is their “Display Name”, which defaults to the registered Username unless otherwise specified. This means that your site’s registered usernames are publicly available, so there is a potential security risk.
Privacy Risk
For the privacy risk, perhaps it is a non-issue for most WordPress sites. But for the percentage of sites that must abide by an official privacy policy or other company rules and regulations (GDPR, anyone?), publicly sharing information about every qualified user is gonna be a problem. Or maybe your site needs to keep all author information private for legal or political reasons (like at a news reporting or government site). In many such cases REST’s default functionality may present serious privacy risk. As someone said somewhere on social media1:
A lot of institutions use WordPress for their staff or students or even patients/clients. They probably have no idea this is exposed and they also probably have some level of security policy that doesn’t allow names to be listed publicly. I think it’s a problem and should be opt-in.
Security Risk
For the security risk, the significance and extent of the issue is up for debate1,2,3. In general, bad actors require at least two things to gain access to your site4:
- Username
- Password
And thanks to the WP REST API, they now have half of what they need. So the REST API introduces a security vulnerability by making it easier for attackers to brute-force their way into your site5,6. Instead of having to guess the correct username AND password, now they just have to guess the password. Which unfortunately for many user accounts is just too ridiculously easy to do.
Another on-topic post from social media1:
Its one-half of the puzzle in acquiring unauthorized credentials. If you’re trying to follow best practices, you don’t expose sensitive data. If you’ve worked to remove all leakage of user names from your site, this just re-exposed all that data.
How to Secure the REST API
So at this point, you should have a pretty good understanding of how the WordPress REST API works and why it can be considered a privacy and/or security risk for probably a vast percentage of WordPress sites. Now you get to decide whether or not it is necessary to take action and secure your site against unsafe data exposure. Fortunately, there are a couple of easy ways to lock it down using a WordPress plugin. Here are a couple of free options:
- Disable REST API — Disable REST completely for all non-logged users
- REST API Toolbox — Disable only the REST
users
endpoint
Full disclosure, the first option listed here, Disable WP REST API, is one of my own plugins. It is designed to be super lightweight and effective. That in mind, either of these plugins is gonna do the job to protect against unwanted REST exposure. If you know of other/better techniques, share ’em in the comments.
WordPress Team On Point
The WordPress team is aware of this potential privacy/security risk and already has taken steps to lock it down. For example, before the WP 4.7.1 update, the REST API exposed sensitive data for ALL registered users, regardless of whether or not they are credited as Author for any post(s).
So thanks to improvements made in version 4.7.1, WordPress now displays user data ONLY for users (of any role) who are credited as author for registered post types. This important step helps to reduce user data exposure, and tells us that the WP team is actively working to keep WordPress as safe and secure as possible. Hopefully they will take further steps to eliminate unnecessary exposure of sensitive user information.
Closing Thoughts
As simple as it is to properly address the fundamental vulnerabilities inherent in the WP REST API, unfortunately most WordPress users will remain blissfully unaware and do nothing. This is why the REST API should disable the public view
of most if not all user data.
Sensitive information should be exposed only to authenticated users. Disabling exposure of user data by default helps to protect the vast majority of WordPress users, and of course developers always will be savvy enough to enable the user data endpoints if/when needed. It’s a win win! :)
Footnotes
Here are some related materials and resources FYI:
- 1 Quotes taken from this FB thread
- 2 Why Showing the Username is Not a Security Risk
- 3 Why the REST API User Endpoint Still isn’t Fixed
- 4 A good reason to use super strong passwords and/or 3+factor authentication
- 5 Brute-Force Login Drip Attack
- 6 Protect Against WordPress Brute Force Amplification Attack
11 responses
-
A useful, and timely, article Jeff.
A shame that the WP team don’t see this as more of an issue. Not least I have noticed an increase in the number of brute force attacks using my not-published admin username on several sites.
-
Hi Jeff! Thanks for this post. I was looking for something similar to protect JSON.
I imagine to create an (or more) api key (somewhere inside wp-admin) and use this key in the endpoint, like:https://digwp.com/wp-json/wp/v2/users/“apikey”
So, only the endpoint with the right api key will access the content through WP REST API.
Do you know any plugin to handle this?
Any security concerns using this “api key”?
-
It’s also a good reason why I created a “drop-in” solution for protecting the wp-login.php file: It requires the use of a valid email as the Login Name, all because THAT is not published in the extra info of any posts (at least, not with my current template anyway).
Another point:
I am seeing things like this in my access logs –
POST /wp-json/ HTTP/1.1
So, I used an htaccess trick to effectively block those kinds of attacks. Plus, my custom firewall application is also “keyed in” to detect and mark such attacks. (At one time, they even filled in a few fields with XML statements and properties – in an attempt to request other important info.)
I just wish the WP crew left REST API in plugin form, rather than hard-code it into the WP core! – Not all of us use REST API anyway, it just becomes more “code bloat” to us.
– Jim
-
Good article. I’ve read of very popular plugin (contact form 7) breaking if you disable the REST API. Have you run into these concerns? Where disabling becomes a big hassle? Even Gutenberg relies heavily on the API, but I guess if you just block it for logged out users it would still work…..
-
Thanks Jeff Starr for this great post keep it up
-
I enjoy that all these blogs mention privacy issues… but nobody mentions that you can add an endpoint that edits the database, and it is available publicly by default.
why not focus on how to set authorization privileges rather than just disabling the api?