Research: Auditing WordPress Plugins

A summarized post about security research of WordPress plugins and the explorational audit spree which followed.

We at cyllective always strive to improve and expand our knowledge in our field of expertise. It was during a recent audit where we (once more) came into contact with WordPress. Even after the audit had passed, it did peak the interest of one of our security engineers (@_cydave).

As part of our work also involves giving back to the community, we decided to dedicate a fair amount of time into the research on WordPress plugins and we are pleased to present an insight into our research.

By now you’ve most likely heard about the pleasure of building a small website or cool blog, but it should be quickly and without hassle, oodles and oodles of beautiful designs, powerful features and the freedom to “build anything you want” - to most people, WordPress comes to mind.

But not everybody has heard about the associated security nightmares that the vast plugin and theme ecosystem entails. As the individual plugin maintainers usage of secure code practices highly varies.

Wether you’ve experienced the horrors of copy-pasta’d vulnerable components before (yes, we are looking at you TimThumb 👀, uploader 👀) or had to explain to that one friend of yours, not to play Pokémon trainer with plugins and themes on the WordPress marketplace - we all have been touched by the grace of WordPress in some shape or form.

WordPress Plugin Market Place

This blog post summarizes our explorational WordPress plugin security research, which we’ve been working on for around three months. Our initial goals for this research were not set in stone, beyond getting in some practice for improving our secure code review / auditing routine and to learn more about WordPress plugins in the process.

The First Step

The first few hours of our research were spent reading through WordPress’s Plugin Handbook in order to get a basic understanding of their structure (hook all the things - Actions and Filters) and available security measures (sanitize_* and esc_* functions, nonce creation and validation, etc.).

Aside from the “official” resources, we dug around exploit-db for additional insight into attacking / auditing WordPress plugins. The following papers provided us with valuable pointers:

Equipped with an ever growing scratchpad of notes, references and rabbit holes to chase, we decided to start digging.

Plugins, Plugins everywhere

The oodles and oodles of plugins, merely a few clicks away to get lost in reading, tracing function calls and keeping a keen eye out for sources and sinks - a sheer bottomless ball pit of good fun - we just couldn’t decide on where to start…

We had been scraping and indexing plugin metadata as well as the plugin’s files, version numbers, relative filepaths and checksums for quite a while. The data harvesting process was painfully slow, due to the transfer speeds of WordPress’ SVN server, but provided us with valuable information that we were then able to incorperated in our research in the form of our own database.

Initially, we started picking a fixed set of randomly selected plugins from our database, without any regards to the release date, popularity of the plugin or it’s use case. We also limited ourselves to a maximum of thirty minutes per plugin to audit, so we could expose ourselves to varying levels of code quality, structure and security awareness of the plugin developers.

After around one week of downloading, installing, auditing, uninstalling of random plugins, we uncovered an unauthenticated SQL injection vulnerability. We’ve also uncovered a series of local file inclusion and remote code execution vulnerabilities in severely outdated plugins, which just happened to be part of our random pool. The fact that we invested quite a bit of time in auditing plugins that had not received updates in the past 5 years didn’t sit well with us. We wouldn’t benefit all too much if we would continue on with our current plugin selection criteria. By auditing severly outdated plugins the WordPress ecosystem woulnd’t benefit that much either, as they weren’t relevant and supported anymore anyways.

Fortunately, we’ve been scraping plugin metadata for quite a while and could utilize the release- and last updated date fields to filter out severly outdated plugins. Out of curiosity, we plotted the number of plugins by the date they were last updated on and grouped them by year:

Plugins last updated date grouped by year

(click to enlarge)

We decided to exclude plugins that did not receive updates in the last two years, so we wouldn’t be investing time in auditing “dead” software. This brought the capacity of our random pool down to roughly 20’000 plugins.

We continued auditing plugins left and right for a few more days and ended up finding two additional unauthenticated SQL injection vulnerabilities in two separate plugins. After having audited roughly one thousand plugins at this point, we developed a slight obsession (or hyper focus) on SQL injection vulnerabilities. Because SQL injection vulnerabilities are rather easy to spot, we began experimenting with a bunch of RegEx patterns, which would help us in discovering SQL injections more rapidly. But as expected, searching for vulnerabilities purely based on RegEx patterns, turned out to be suboptimal. Especially the time loss, caused by the high rate of false positives (and false negatives), motivated us to come up with a better approach.

Optimizing Our Process

Recalling that most plugins on the marketplace have a set of tags associated with them, inspired us to experiment with a similar approach, with the goal of finding more “relevant” plugins. Instead of using tags, which describe the plugin’s use cases, we chose to search the plugin’s source code for WordPress specific library calls and then enrich our database with this information in the form of tags.

Because we were mostly interested in finding SQL injection vulnerabilities (ideally, unauthenticated ones), we would look out for a combination of specific behavior:

  • Interaction with the WordPress database: references to $wpdb->
  • String interpolation in SQL-like strings:
    • ['"].+?\s(WHERE|where)\s*\w+\s*=\s*\$\w+.+?['"]
    • ["'](SELECT|select|DELETE|delete)\s+.*?(FROM|from)\s+.+?(WHERE|where).+?(\$\w+).+(['"]|;)
  • Optional: Security measures relating to sanitization attempts: esc_sql
  • Exposure of unauthenticated endpoints: references to add_action("wp_ajax_nopriv_

With our new approach in mind, some more RegEx patterns and yet another Python script at the ready, we re-lived the radical internet speeds of the 90s, by downloading all of the WordPress plugins via their SVN mirror once again. For each downloaded plugin we recursively grepped for specific patterns in the source code and once a match was found, we would attach a tag, describing the pattern that was used, to the plugin.

After days of downloading and grepping we ended up with tagged plugins:

Excerpt of tagged plugins

Using the tags (stored in the “notes” column in the screenshot above) as an additional filter, we were able to reduce the pool of targets to audit down to roughly 5’000 plugins.

In the first few days of analysing a subset of the new targets, we encountered 2 more SQL injection vulnerabilities and as the audit-spree continued, we would occasionally find up to 4 or 5 vulnerabilities a day. To us, the fact that we were finding vulnerabilities more frequently, compared to our initial approach, felt like a clear indicator that we were on the right track. 😄

On each day that we discovered one or more vulnerabilities, we would responsibly disclose them with the help of the fine folks at WPScan (WPScan being a CNA of WordPress CVE numbers). Instead of keeping track of all the back-and-forth communication across two or more parties (the plugin developers, the WordPress plugin team and a selected CNA) all while correctly timing, escalating and taking further steps by yourself, one can simply use their vulnerability submission form and let the WPScan team take care of all the necessary steps for you.

Thank you, WPScan and team, for providing us with such a smooth process! 👍

Findings & Conclusion

Having exhausted the pool of 5’000 plugins after around two months, we decided to conclude our explorational research. We ended up finding and reporting a total of 35 vulnerabilities, all of which could have been exploited by unauthenticated attackers.

SubmissionCVE IDVulnerabilityPlugin
2022-02-16CVE-2022-0656LFIudraw
2022-02-16CVE-2022-0657SQL Injection5-stars-rating-funnel
2022-02-16CVE-2022-0658SQL Injectioncommonsbooking
2022-02-17CVE-2022-0679LFI / RCEnarnoo-distributor
2022-02-18CVE-2022-0693SQL Injectionmaster-elements
2022-02-18CVE-2022-0694SQL Injectionadvanced-booking-calendar
2022-02-23CVE-2022-0739SQL Injectionbookingpress-appointment-booking
2022-02-23CVE-2022-0747SQL Injectioninfographic-and-list-builder-ilist
2022-02-24CVE-2022-0760SQL Injectionsimple-link-directory
2022-02-26CVE-2022-0769SQL Injectionusers-ultra
2022-02-26CVE-2022-0771SQL Injectionsitesupercharger
2022-02-26CVE-2022-0773Stored XSSsearchiq
2022-02-26CVE-2022-0780SQL Injectiondocumentor-lite
2022-02-27CVE-2022-0781SQL Injectionnirweb-support
2022-02-27CVE-2022-0782SQL Injectionnd-donations
2022-02-27CVE-2022-0783SQL Injectionmultiple-shipping-address-woocommerce
2022-02-27CVE-2022-0784SQL Injectionwp-experiments-free
2022-02-27CVE-2022-0785SQL Injectiondaily-prayer-time-for-mosques
2022-02-27CVE-2022-0786SQL Injectionkivicare-clinic-management-system
2022-02-28CVE-2022-0787SQL Injectionwp-limit-failed-login-attempts
2022-02-28CVE-2022-0788SQL Injectionwp-fundraising-donation
2022-03-01CVE-2022-0814SQL Injectionubigeo-peru
2022-03-01CVE-2022-0817SQL Injectionbadgeos
2022-03-01CVE-2022-0818Stored XSSwoo-coupon-usage
2022-03-01CVE-2022-0826LFI (Plugin’s Backup)church-admin
2022-03-02CVE-2022-0827SQL Injectionwp-video-gallery-free
2022-03-02CVE-2022-0833SQL Injectionbestbooks
2022-03-02CVE-2022-0836SQL Injectionsema-api
2022-03-03CVE-2022-0846SQL Injectionspeakout
2022-03-04CVE-2022-0867SQL Injectionarprice-responsive-pricing-table
2022-03-04CVE-2022-0948SQL Injectionpersonal-dictionary
2022-03-04CVE-2022-0949SQL Injectionwp-contacts-manager
2022-03-11CVE-2022-0952SQL Injectionwoc-order-alert
2022-03-11CVE-2022-1013SQL Injectionstopbadbots
2022-03-11CVE-2022-1014Privilege Escalationsitemap-by-click5


Further reading