Author Archives: J.D. Grimes

Arbitrary SQL and PHP execution in WordPress plugin via shortcode

I keep an eye on vulnerability reports for WordPress plugins, always trying to learn more about plugin security. As a plugin developer myself, it is important to learn about pitfalls so that I don't make the same mistakes in my own plugins.

A while ago I saw a report for a plugin that provided a shortcode for executing SQL queries. Providing a feature like that in a shortcode is problematic, because any user can execute a shortcode on the site, as long as they are logged in.

The details of why that is possible were first explained (to my knowledge) in this vulnerability report put out by Securi. The short story is that WordPress core includes an Ajax callback for the parse-media-shortcode action that does just what its name sounds like: it parses shortcodes (and not just media shortcodes either). Because that action is not protected by any capability checks, it is possible for any logged in user to pass a shortcode to it in the shortcode parameter, and it will be executed.

In the case of the plugin offering a shortcode that ran an SQL query, this meant that any user of the site could run any SQL query that they wanted to. Like this, for example:

[[sql]SELECT user_email, user_pass FROM wp_users[/sql]]

For some reason, I clicked through the link in that report to see if the plugin was still available on WordPress.org. It wasn't, but the plugin directory automatically provided helpful results for similar plugins.

Plugin directory search results for "sql-shortcode".

The first result was the EZ SQL Reports Shortcode Widget and DB Backup plugin. A look at the plugin's page revealed this:

There is also an shortcode for the wpdb::get_var function that you can use to display a single value from your database. For example, this will display the number of users on your site:
[sqlgetvar]SELECT COUNT(*) FROM wp_users[/sqlgetvar]

Uh oh. Here we have another plugin that allows arbitrary SQL to be run via a shortcode.

To make sure that things were actually as bad as they seemed, I took a look at the code. As it turns out, things weren't as bad as they seemed. They were worse.

function ELISQLREPORTS_get_var($attr, $SQL = "") {
global $wpdb;
if (!is_array($attr)) {
if (strlen($attr) > 0 && strlen($SQL) == 0)
$SQL = $attr;
$attr = array("column_offset"=>0, "row_offset"=>0);
} elseif (isset($attr["query"]))
$SQL = $attr["query"];
if (!(isset($attr["column_offset"]) && is_numeric($attr["column_offset"])))
$attr["column_offset"] = 0;
if (!(isset($attr["row_offset"]) && is_numeric($attr["row_offset"])))
$attr["row_offset"] = 0;
$var = $wpdb->get_var(ELISQLREPORTS_eval($SQL), $attr["column_offset"], $attr["row_offset"]);
if (isset($_GET["get_var"]) && ($_GET["get_var"] == "debug") && !$var && $wpdb->last_error)
return $wpdb->last_error;
else
return $var;
}
add_shortcode("sqlgetvar", "ELISQLREPORTS_get_var");

Yes, the ELISQLREPORTS_eval() function is as bad as it sounds.

function ELISQLREPORTS_eval($SQL) {
global $current_user, $wpdb;
if (@preg_match_all('/<\?php[\s]*(.+?)[\s]*\?>/i', $SQL, $found)) {
if (isset($found[1]) && is_array($found[1]) && count($found[1])) {
foreach ($found[1] as $php_code)
eval("\$found[2][] = $php_code;");
$SQL = $wpdb->prepare(preg_replace('/<\?php[\s]*(.+?)[\s]*\?>/i', '%s', $SQL), $found[2]);
}
}
return $SQL;
}

Not only is there the potential for arbitrary SQL execution, but the shortcode also provides another special feature: it allows arbitrary PHP to be embedded as well! (Although notice the care to try and prevent SQLi by using $wpdb->prepare() 😃.)

Remember, shortcodes can be executed not just by anybody who can edit a post (which would include Contributors), but also by even Subscriber-level users using the admin-ajax.php trick explained out above. This means that any user on a site can use the shortcode provided by this plugin to execute arbitrary SQL and PHP in the context of the site.

A PoC using the example shortcode:

<form action="http://src.wordpress-develop.dev/wp-admin/admin-ajax.php" method="post">
<input name="action" value="parse-media-shortcode"/>
<input name="shortcode" value="[sqlgetvar]SELECT COUNT(*) FROM wp_users[/sqlgetvar]"/>
<input type="submit" value="Execute SQL!"/>
</form>

Log in as a subscriber, submit the form, and viola!

The user count from my test site returned with the normal results of the parse-media-shortcode action.

The next step was to try and contact the plugin developer. First, I looked at the plugin's page in the plugin directory, to see if there was a contact method mentioned anywhere. I then went to the plugin author's profile to see if they had a contact listed there. They did have a website listed, but I couldn't find any means to contact them through that.

The only remaining option appeared to be to attempt to contact them through a direct message on WordPress's Slack. When that didn't get a response, I later remembered to also check in the plugin's code, as often an email address is included in the copyright notice. Fortunately, that was the case, so I sent an email to that address.

Within 24 hours I received a response, and soon after version 4.17.38 was released. It mitigates the issue by only allowing SQL to be used in the shortcode that matches one of the SQL reports created using the report creation feature offered by the plugin. A note regarding this was added to the plugin readme:

 Note: because of a recently discovered exploit in the WordPress shortcode functionality it is now required that an admin user create an SQL Report with the exact query that will be used in the sqlgetvar shotcode, otherwise any subscriber could white their own shortcode query.

This means that you can no longer execute arbitrary SQL. If you provide a query not already available as an SQL report, you'll just get the error "This SQL Query has not been allowed by an Administrator."

Attempting to execute arbitrary SQL now results in an error message.

Because SQL reports can only be created by a user with the 'activate_plugins' capability, this should sufficiently mitigate the issue.

The big takeaway here is that you need to be very careful what you make possible via a shortcode. Plugin authors have been able to get by with some things in shortcodes that shouldn't really be there, because historically at least shortcode execution was usually only possible for Contributor-level users and up. But with the introduction of wp_ajax_parse_media_shortcode() in WordPress 4.0, vulnerabilities in shortcodes are now exposed to even Subscriber-level users, making any site that allows user registration immediately exploitable if a shortcode is vulnerable.

This is a point where the plugin community needs some education, and where perhaps core should even consider placing more restrictions on this Ajax action to mitigate these issues (though vulnerabilities like these in shortcodes would still be exposed to anybody who could edit posts). I did bring this up with the security team, and they said that they do have plans to add more restrictions to the parse-media-shortcode Ajax callback in the future.

But until then, remember: Anything that a shortcode can do, any logged in user can do.

Protect Internet Freedom — Oppose “Net Neutrality”

Many internet sites are banding together this week in support of so-called "net neutrality"—the idea that internet service providers should not be able to discriminate between different internet content.

This sounds good; after all, none of us want to see particular viewpoints or content sources that we support or consume being blocked. But the truth is, that rather than supporting a freer, more vibrant internet, net neutrality actually does the opposite.

Until a few years ago when the FCC classified the internet as a Title II utility, ISPs were free to serve content as they wished. If consumers didn't like the service that they were getting, they had the freedom to decide to either put up with it or switch to a different ISP that offered the service that they wanted. Just like we do with services in every other part of our lives.

Consumers also had the freedom to seek out ISPs that provided service specially tailored to their specific needs, based on what kinds of content they did or did not want to consume, and what kind of devices they used. This meant that ISPs could offer plans that blocked unwanted content, or optimized the serving of a particular kind of content. That way, consumers could get the sort of plan that they needed, instead of having to pay for a generic plan that treats all content equally—even content that a particular consumer doesn't need or want to see.

In other words, net neutrality actually limits consumer freedom, by limiting what kinds of internet service plans are allowed to be available for a user to purchase. It means that consumers would no longer be allowed to seek out plans tailored specifically to their needs. Everybody would be forced to have the exact same generic treatment of all content equally.

Of course, even without net neutrality, ISPs are free to offer plans that treat all content equally, if this is the experience that consumers want. Those people who support net neutrality can put their money where their mouth is, and only purchase service from providers that don't discriminate between different kinds of content—no government intervention necessary.

And with all of many big cable companies' shortcomings, at least we have the freedom to take our money and go elsewhere for service if we don't like a particular ISP. But you can't just decide you don't like how some government bureaucrats are are handling things and shop elsewhere—unless you want to leave the country.

And once the internet is classified as a Title II utility, it is just the first step to even more government intervention and regulation of the internet—which in the end could undermine the very principles of net neutrality that those supporting Title II classification are aiming for.

And no, net neutrality won't sock it to "big cable". Instead, while big cable will weather the storm, it will force some niche ISPs out of business. And it will make it harder to compete with the big cable companies in the future, because there will be one less way for smaller companies to differentiate. By compressing the market, it will actually make it more difficult to challenge the large cable companies when we're dissatisfied with their service.

In short, supporting "net neutrality" is not unlike saying that Google shouldn't be able to discriminate between different content in its search results on any basis whatsoever; that it should have to toss out its algorithms; that all search engines should have to treat all content the same as every other search engine. Of course, that would defeat the purpose of their existence, because they'd be forced to comply with an arbitrary principle instead of simply providing the experience that offers the best results for their users.

Net neutrality would take away our freedom to get what we want out of the internet, the way that we want it. Instead, we'd all have to see the internet through the very same lenses.

Most of us don't want our ISPs discriminating in particular ways—great, so don't buy internet service from any that do. And if you can't find an ISP that does exactly what you want, the good news is, that at least for now, it is still a free country, and you or somebody else can create one to fill that need.

That is, it is still a free country, as long as we continue to stand against net neutrality. After that, you'll no longer have that freedom. And innovation, and thus the Internet's vibrancy and diversity, will suffer as a result.

Why WordPress should stop user-focused development

WordPress prides itself on being user-focused, and its development process is focused largely on building new user features. However, it is time to change this. WordPress needs to stop user feature-focused development.

Now, I'm sure that many users are thinking that I'm just some snobby developer who doesn't care about users. Actually, though I am a developer, I am also a user myself, of course, and I'm drawing on both of these experiences to come to my conclusion.

I'm not saying that I think WordPress should stop being user-focused, in the sense of trying to achieve the best possible user experience. I think that the WordPress core devs, despite the flack that they sometimes receive from other devs (like myself, even!), are usually right in the way that they put users first in many decisions that they make.

However, I think that it is no longer possible to truly put users first if WordPress is building user facing features.

WordPress is no longer blogging software. WordPress is no longer even a CMS. WordPress is no longer really any one thing. It is many things to many people, which is why it is easy to argue over which of these things it is. Is it a CMS, or blogging software, an app platform, or a framework? It is used for all of these things, and probably more.

This is to say, that WordPress no longer has the privilege of deciding what it wants to be. It isn't just a little kid who can pick what he'll be when he grows up. Instead, WordPress is at the point where it is ultimately the users who are deciding what it is. Many people use it to build their blogs; many more use it as a CMS; still others use it as a back-end for a web-app. WordPress core no longer has one single purpose; it is many things to many people.

So when user-facing features are developed in core, there are inevitably problems. Some people like them; others hate them. Some people need them for their blog; others think that they are just wasted time and superfluous "bloggy" features in their CMS. And vice versa.

There is no way for core developers to avoid this conundrum when developing many user-facing features, and it will only get worse as WordPress attempts to push from 25 to 50% of the web. There is a danger of fostering both grumbling majorities and vocal disgruntled minorities.

But there is a way to avoid this, by side-stepping the problem completely: just stop developing user-facing features in core.

In fact, go a step beyond that, and begin removing user-facing features from core.

Now, obviously, that is only half a plan. User-facing features still need to be developed, of course.

And in fact, they already are. WordPress.org hosts a directory of almost 50,000 plugins that add the features that users need (and probably a few that they really don't). WordPress has always fostered the extension mentality, and the idea that many features belong in plugins, and not in core. But in the development cycle, it is still often user focused. We feel compelled to include user-facing features in each release, and to market them to users via the About screen on update. WordPress is still being built and is serving updates as if it was just blogging software, or a primarily UI-focused product.

But it doesn't have to be. It is largely a throwback to the past that is just what both developers and users are used to. And it is time to change.

We all know that plugins are really WordPress's secret super-power. And they are here to save the day!

Nobody builds a WordPress site without plugins. The first thing one does when setting up their site is to begin installing plugins, and maybe a theme with plugin-like features.

WordPress isn't becoming a framework, or plugin platform. It already is one. And has been for some time. And and I think it is time that we acknowledge and fully embrace that. Or else we'll never get to 50% of the web. Or if we do, it may be as "the menace" that everybody uses, but nobody is really satisfied with.

So let's start developing WordPress as a plugin framework. Let's stop it with all of the user-facing features being built into core. In fact, let's start turning existing features into plugins and moving them out of core. Let's stop forcing the core developers to make increasingly impossible decisions about what WordPress is, and let the users choose. Let's democratize WordPress.

Let's start making WordPress's development plugin developer focused, instead of user-facing feature focused. This will improve the lives of plugin developers, meaning more and better plugins. And that will improve the lives of users, by improving the choices that they have available to make WordPress whatever they want it to be. That way, everybody has the WordPress that they want. And that way, everybody wins.


This post was created with the new Gutenberg editor, that has sparked debate and complaints over what core developers should be focused on.

Mac popup notification for a PHP error

Receive a notification when PHP errors are logged on Mac

I have PHP configured to log all of its errors to a single log file. I always have Terminal open and tail watching this file. However, sometimes I don’t realize that new errors have been logged right away, which is annoying. To overcome this, I thought it would be nice if I could get a popup notification each time there was an error, just like many Mac apps do. I found this thread on SO, which helped me solve my problem.

If you have Homebrew installed, all you need to do is run:

$ brew install terminal-notifier
$ brew install fswatch

Then add this function to your .bash_profile:

notify-php-logs() {
	fswatch -0 ~/zebug.log | xargs -0 -n 1 \
		terminal-notifier -title "PHP Error" \
			-message "New errors in zebug.log" \
			-group "php-errors" \
			-activate "com.apple.Terminal"
}

Then run:

$ notify-php-logs >/dev/null 2>&1 &
$ tail -f ~/zebug.log

You’ll want to replace ~/zebug.log with the path of the log file that you want to listen to, of course.

Now whenever there is a PHP error, you will get a notification, and when you click on it the Terminal app will be brought to the front so you can see the error.

The Law of Software Thermodynamics

I just found this quote, and I wanted to save it for reference:

There’s this little law of thermodynamics that says energy is neither created nor destroy[ed], it just changes form. This law almost holds true for complexity in software development – except that we can and do create complexity in our systems. And it seems that once we create the complexity, it never seems to get destroyed. It only gets moved around.

Source: Backbone.js: Getting The Model For A Clicked Element by Derick Bailey