Why so many WordPress plugins vulnerable?

WordPress Vulnerability Statistics Source: © The WPScan Team

The above graph shows recent statistics of WordPress vulnerability from WPScan Vulnerability Database summarized by Sucuri which is a world wide security company especially famous for analyzing vulnerability in WordPress.

Why so many vulnerabilities are there in WP plugins?

After reading the Sucuri Blog deeply and widely, I came to the conclusion that there are some kind of disuse and misuse of WordPress core functions.

I’d like to verify each vulnerability in this point of view.

XSS

Unfortunately, XSS is very popular in WordPress plugins. In many cases, this occur with insufficient validation of untrusted data which comes from the outside (including from the DB) and lack of an escape just before responding to the user agent. The later is a fundamental countermeasure.

I made a corresponding table between “XSS Prevention Cheat Sheet” in OWASP and “Data Validation” in Codex.

Context Code Sample Defense
HTML body <div>DATA</div> esc_html()
HTML attributes <input…value="DATA"> esc_attr(), sanitize_text_field()
GET parameter <a href="…?value=DATA">…</a> esc_url()
SRC/HREF attribute <iframe src="DATA" /> esc_url()
JavaScript Variable <script>foo('DATA');</script> esc_js()
CSS Value <div style="width:DATA;">…</div> N/A

You already know about this very well?

Sure, in case of Blubrry PowerPress <= 6.0, XSS had already taken into account. The following statistics shows before and after fixing XSS.

  Files Lines htmlspecialchars() esc_html() esc_attr()
before 42 36280 274 82 153
after 33 22807 160 53 131

In fact, those paches could be seen every where in their codes. Those were hard to track before and after. More over, htmlspecialchars() and esc_html()were mixed and used.

Speaking about esc_html(), the third parameter to htmlspecialchars() is specified more strictly than their codes. I could hardly say that’s OK or not, but using core functions is always OK.

I think this issue is caused by disuse and misuse of WordPress core functions.

Also it’s important to design codes to separate “Validating Input” in “Model” and “Escaping Output” in “View”. I’m not saying about MVC here. But codex says to use “Validating Sanitizing and Escaping User Data” along the context. So every developer should design the context at first.

SQL Injection

On August 2014, Sucuri reported about SQLi in Custom Contact Forms.

The following snippet from CCF (<= 5.1.0.3) dumps the set of SQL queries and downloads the previous one.

<?php
if (!is_admin()) { /* is front */
    ...
    $custom_contact_front = new CustomContactFormsFront();
    ...
} else { /* is admin */
    ...
    $custom_contact_admin = new CustomContactFormsAdmin();
    ...
    add_action('init', array($custom_contact_admin, 'adminInit'), 1);
}

function adminInit() {
    $this->downloadExportFile();
    $this->downloadCSVExportFile();
    $this->runImport();
}
?>

The function adminInit() was no doubt for the administrators. But this snippet had at least next five issues:

  1. Validate user role by is_admin().
  2. Lack of consideration of unexpected access route.
  3. Export and import without validating user privilege.
  4. Export raw SQL to the client.
  5. Import raw SQL from the client.

As a result, an attacker could easyly know the DB prefix defined in the wp-config.php with a certain attack vector and could inject some malicious SQL to exploit the site.

Vulnerability of Custom Contact Form

This was caused by misuse of is_admin() which is always true when someone access the admin area even without authentication.

To avoid this vulnerability, the developer must follow the SQL Injection Prevention Cheat Sheet like below:

  1. Validate user privilege by current_user_can() with manage_options.
  2. Validate nonce to limit the access route.
  3. Export and import validated and parameterized queries.
  4. Use stored procedures.
  5. Escape all user supplied input.

The action hook admin_init may cause similar misuse. For example, Sucuri reported RFI in MailPoet Plugin on July 1 2014, and RCE in Platform theme on Jan 21 2015.

Privilege Escalation

On February 3 2015, Sucuri disclosed PE in UpdraftPlus plugin <= 1.9.50.

Suppose a registered user hit a button on the dashboard to do something.

<form action="<?php echo admin_url( 'admin.php' ); ?>">
    <?php wp_nonce_field( 'foo-secret-nonce' ); ?>
    <input type="hidden" name="action" value="foo" />
    <input type="submit" value="Something to do" />
</form>

When wp-admin/?action=foo is requested, the function foo_handler will be triggered via the action hook admin_action_foo by following code:

<?php add_action( 'admin_action_foo', 'foo_handler' ); ?>

If the foo-secret-nonce is also used in a function for an administrator to do an important job and only check_admin_referer('foo-secret-nonce') is examined without validating user privilege, a PE will happen.

Exactly the same vulnerability in WPtouch (<= 3.4.2) was also disclosed on July 14, 2014.

This is caused by disuse of WordPress nonce.

CSRF

A cause of CSRF is simple.

For example, if you click “Save Changes” on the admin screen, the page transition happens after saving the setting data into the DB. In a sequence of these process, if no nonce is on the page before transition or no validation of nonce before saving data, CSRF immediately occurs.

In this case, only validation of user authentication or privilege at saving process is not enough to prevent this vulnerability because an administrator potencially click a malicious link with own authorized cookie.

Such disuse of nonce leads many plugins to CSRF.

File Inclusion

FI (or Arbitrary File Download) is a vulnerability that occurs by giving the user supplied input to some file system functions such as file_get_contents()without proper validation.

On September 2014, a vulnerability of Slider Revolution became a big topic. By requesting a certain functionality for an administrator, an attacker could download any files via a request to admin-ajax.php like this:

http://example.com/wp-admin/admin-ajax.php?action=show-me&file=../wp-config.php

This request was handled with:

  1. No validation of user privilege.
  2. No validation of nonce.
  3. No validation of given input.

As a result, the attacker could easily download wp-config.php without knowing the user name and password.

Vulnerability of Slider Revolution

We can find this kind of vulnerability in the old version of HD FLV Player. In this plugin, download.php was called directly regardless of WordPress context where we should follow its event driven programming style to use appropriate functions for validating a nonce and user privilege.

Conclusion

I’ve found a lot of disuse and misuse of WordPress core functions in Sucuri Blog and conclude the followings for developing WordPress plugins and themes emoji .

And I quite agree with James Golovich:

If you have the WordPress tools available to you then you should use them. Something like current_user_can() and a nonce should always be used.