Update (2022-02-26): the tool is now public: https://github.com/kazet/wpgarlic

WordPress plugins expose a number of interfaces, such as:

  • AJAX endpoints (/wp-admin/admin-ajax.php)
  • Admin menu pages (/wp-admin/admin.php?page=...)
  • PHP files (in the /wp-content/plugins/ directory),
  • REST routes (/wp-json/...).

These interfaces have a consistent trust boundary: we know where the untrusted input goes and can detect what operations are executed on that input.

For instance, if you visit a .php file, provide appropriate parameters, and cause a file to be removed, you know that it is a vulnerability. You know what parameters you control and what ones you don’t – for example, you may redirect a logged-in admin to an admin menu page with arbitrary GET parameters, but you don’t control their cookies.

Therefore it is possible to semi-automatically scan for multiple classes of vulnerabilities in all WordPress plugins.

I have written a tool that:

  • executes each AJAX endpoint, menu page, REST route, or file multiple times,
  • injects payloads into the GET, POST, etc. arrays or REST parameters (more on how it’s done in the next section),
  • analyses the outputs with an ugly pile of regular expressions1 to detect:
    • calls to WordPress functions (such as wp_delete_post),
    • crashes (“No such file or directory”, “You have an error in your SQL syntax”, …),
    • XSS (echoing a known payload containing " or <),
    • etc.

This method is transferable to other CMS plugin ecosystems but not directly e.g. to Python packages. If a Python package allows you to remove arbitrary files, it may or may not be a vulnerability depending on the package role and your particular setup.

Injecting parameters

In PHP, the _GET, _POST, _SERVER, _COOKIE, and _REQUEST arrays contain various request parameters (e.g. GET and POST data, cookies, server configuration, and headers). I have replaced them with mock objects that allow access to any key - and with defined probability return a payload from a predefined payload list.

A simple mock $_POST array could be created using the following code:

<?php

class Mock implements ArrayAccess {
    function offsetGet($offset) {
        return "payload";
    }

    function offsetExists($offset) {
        return true;
    }

    function offsetSet($offset, $value) { }

    function offsetUnset($offset) { }
}

$_POST = new Mock();

echo $_POST["parameter_name"];

The above snippet will print payload.

Let’s assume that the $_REQUEST array has been mocked in a way similar to the one described above and that an AJAX route is handled by the following function:

public function delete_saved_block() {
	$block_id = (int) sanitize_text_field($_REQUEST['block_id']);
	$deleted_block = wp_delete_post($block_id);
	wp_send_json_success($deleted_block);
}

When $_REQUEST['block_id'] gets accessed, the mock will return a payload, thus allowing to detect that wp_delete_post was called on an attacker-controlled value.

This approach allowed to easily inject payloads even if the parameter name was hard to guess – the tool didn’t distinguish between id and secret_parameter_65e3c14a1d.

Some of the keys needed to be excluded manually (for example $_SERVER['HTTP_AUTHORIZATION'] or $_GET['doing_wp_cron']) because their values were handled by WordPress, and providing them caused plugin code to not be reached.

Besides, with some probability, a random type of array was returned instead of a string payload:

  • a singleton array with a string payload,
  • recursively, an object that allows access to any key,
  • a singleton array: random payload → random payload.

Detecting vulnerabilities

The tool contained checks to detect:

  • various kinds of crashes,
  • potentially dangerous operations,
  • information leaks.

Some of these checks led to a large number of CVEs (such as the XSS checks), some didn’t (e.g. the checks for syntax errors designed to catch eval() on untrusted code).

XSS

To detect XSS, checks were implemented that detected payloads being echoed back (or echoed back with escaping that didn’t prevent XSS, such as prefixing " with \).

Crashes

The following types of crashes were detected:

  • fopen() / file_get_contents() / require() / require_once() / include() / include_once() errors and “No such file or directory” or “failed to open stream” error messages,
  • unlink() error messages,
  • crashes related to call_user_func(),
  • SQL error messages,
  • unserialize() errors,
  • parse / syntax errors to detect eval() calls,
  • “command not found” error message,
  • simplexml_load_string() error messages2.

Information leaks

The output was analysed to observe whether known user e-mails or file names are displayed.

WordPress operations

WordPress has been instrumented to detect:

  • calls to maybe_unserialize,
  • calls to update_option/update_site_option/delete_option,
  • calls to wp_insert_user,
  • calls to wp_insert_post/wp_update_post/wp_delete_post,
  • calls to wp_mail,
  • calls to query (this one yielded an especially large number of false positives that needed additional filtering),
  • calls to get_users (this one has been added after accidentally discovering CVE-2021-25110 where an attacker can leak arbitrary user e-mails via a crafted user search query).

Additional checks

After fuzzing, the admin panel, the homepage, and the post pages were crawled to find occurrences of known payloads. That allowed for instance to detect CVE-2021-24975 in social-networks-auto-poster-facebook-twitter-g.

Update (2022-02-26): additionally, any attempts to access uploaded files are logged, so that they may be checked manually.

Changes to PHP

Patched equality

I have patched PHP so that equality comparison between any value and a known payload returned true with 1/3 probability. Forgive me about this one.

With this patch, I was able to detect vulnerabilities such as:

if ($_GET['action'] == 'please-remove-post') {
    wp_delete_post($_GET['id']);
}

Unfortunately, this resulted in a large number of false positives as well. The false positives were e.g. in the form of:

if (in_array($order, array("ASC", "DESC"), true)) {
    query("(...) ORDER BY $order");
}

The only solution for this problem I have used was browsing through these false positives and cursing. Further research can lead to coming up with other solutions.

Other changes

Besides, I have patched the PHP interpreter so that:

  • When base64_decode was performed on a known payload, this payload was returned again,
  • When json_decode was performed on a known payload, an object was returned that returns payloads when any key was accessed. These were the same objects that served as e.g. $_GET arrays,
  • when a redirect was performed, relevant information was displayed so that Open Redirect vulnerabilities could be detected.

Testing

A test-driven approach was critical during development. Tests checked that the tool would find a known vulnerability. For example, I could write a test to check that:

when fuzzing the wp_ajax_heateor_sss_import_config endpoint of the sassy-social-share plugin in version 3.3.23, the tool should detect that maybe_unserialize() gets called on an attacker-controlled payload.

False positives vs false negatives

This approach yielded a large number of false positives. It was a deliberate decision because I wanted to sort through multiple reports instead of missing vulnerabilities.

An alternative could be to write additional filtering logic. For example, there were multiple reports where an HTML payload was echoed back – but when checking them, I’ve observed that a correct JSON Content-Type header is added. This was one of the cases that could be checked automatically.

Other

Fuzzing was performed inside Docker containers, re-created for every plugin.

It was important to disconnect the network, because a lot of plugins call other web services, and I wanted to avoid sending random payloads there.

I found it also helpful to separate plugin fuzzing and analysis of the outputs. Because the outputs were analyzed by a lot of regular expressions, bugs happened. Therefore a relatively quick rescan allowed to speed up development.

Automating the fuzzing Added: 2022-02-16

Scanning thousands of plugins would not be possible without automating the job. Fortunately, WordPress plugins use consistent interfaces to integrate with WordPress, for example:

  • all REST routes are collected in a central registry, accessible via: rest_get_server()->get_routes(),
  • AJAX actions are created by adding a hook with a name starting with wp_ajax_,
  • there exists one registry with all admin menu pages.

Therefore all REST routes, AJAX actions, and menu actions can be enumerated in the same way regardless of which plugin is scanned. Of course, all PHP files can be easily listed as well.

All plugins can be installed in the same way: I have used WP-CLI – a tool that allows to install, activate, deactivate or delete a plugin from the command-line. The list of plugins could be downloaded automatically from the WordPress plugin registry API.

All of the above techniques made it possible to create a tool that doesn’t require any plugin-specific code.

Results Last updated: 2022-07-12

Because of time constraints, I have focused only on the most popular plugins. As of this moment, the following bugs found by the tool have already been fixed and published:

ID Plugin CVE Number of active installations Type Link
1 woocommerce CVE-2022-0775 5,000,000 Arbitrary comment deletion WPScan
2 updraftplus CVE-2021-25022 3,000,000 Reflected XSS WPScan
3 code-snippets CVE-2021-25008 500,000 Reflected XSS WPScan
4 woocommerce-pdf-invoices-packing-slips CVE-2021-24991 300,000 Reflected XSS WPScan
5 ad-inserter CVE-2022-0288 200,000 Reflected XSS WPScan
6 caldera-forms CVE-2022-0879 200,000 Reflected XSS WPScan
7 complianz-gdpr CVE-2022-0193 200,000 Reflected XSS WPScan
8 custom-facebook-feed CVE-2021-25065 200,000 Reflected XSS WPScan
9 favicon-by-realfavicongenerator CVE-2022-0471 200,000 Reflected XSS WPScan
10 loginpress CVE-2022-0347 200,000 Reflected XSS WPScan
11 popup-builder CVE-2022-0479 200,000 Reflected XSS WPScan
12 use-any-font CVE-2021-24977 200,000 Arbitrary CSS append + stored XSS WPScan
13 white-label-cms CVE-2022-0422 200,000 Reflected XSS WPScan
14 wp-cerber CVE-2022-0429 200,000 Stored XSS WPScan
15 wp-gdpr-compliance CVE-2022-0147 200,000 Reflected XSS WPScan
16 capability-manager-enhanced CVE-2021-25032 100,000 Arbitrary settings update WPScan
17 chaty CVE-2021-25016 100,000 Reflected XSS WPScan
18 cmp-coming-soon-maintenance CVE-2022-0188 100,000 Possibility to add arbitrary CSS WPScan
19 download-manager CVE-2021-24969 100,000 Stored XSS WPScan
20 download-manager CVE-2021-25069 100,000 Reflected XSS WPScan
21 email-subscribers CVE-2022-0439 100,000 Blind SQL Injection WPScan
22 learnpress CVE-2022-0271 100,000 Reflected XSS WPScan
23 menu-image CVE-2022-0450 100,000 Stored XSS WPScan
24 modern-events-calendar-lite CVE-2021-24925 100,000 Reflected XSS WPScan
25 modern-events-calendar-lite CVE-2021-24946 100,000 Blind SQL injection WPScan
26 modern-events-calendar-lite CVE-2021-25046 100,000 Stored XSS WPScan
27 paid-memberships-pro CVE-2021-25114 100,000 Blind SQL Injection WPScan
28 squirrly-seo CVE-2021-25019 100,000 Reflected XSS WPScan
29 ti-woocommerce-wishlist CVE-2022-0412 100,000 Blind SQL Injection WPScan
30 webp-converter-for-media CVE-2021-25074 100,000 Open redirect WPScan
31 woocommerce-products-filter CVE-2021-25085 100,000 Reflected XSS WPScan
32 wpvivid-backuprestore CVE-2021-24994 100,000 Stored XSS WPScan
33 wpvivid-backuprestore CVE-2022-0531 100,000 Reflected XSS WPScan
34 advanced-cf7-db CVE-2021-24905 90,000 Arbitrary file removal WPScan
35 kingcomposer CVE-2021-25048 90,000 Stored XSS WPScan
36 kingcomposer CVE-2022-0165 90,000 Open redirect WPScan
37 social-networks-auto-poster-facebook-twitter-g CVE-2021-24975 90,000 Stored XSS WPScan
38 social-networks-auto-poster-facebook-twitter-g CVE-2021-25072 90,000 CSRF post removal WPScan
39 themify-portfolio-post CVE-2022-0200 80,000 Reflected XSS (logged-in POST 3) WPScan
40 woo-product-feed-pro CVE-2021-24974 80,000 Stored XSS WPScan
41 woo-product-feed-pro CVE-2022-0426 80,000 Reflected XSS (logged-in POST 3) WPScan
42 www-xml-sitemap-generator-org CVE-2022-0346 70,000 Reflected XSS and RCE WPScan
43 booking CVE-2021-25040 60,000 Reflected XSS WPScan
44 interactive-3d-flipbook-powered-physics-engine CVE-2022-0423 60,000 Stored XSS WPScan
45 mappress-google-maps-for-wordpress CVE-2022-0208 60,000 Reflected XSS WPScan
46 permalink-manager CVE-2022-0201 60,000 Reflected XSS WPScan
47 post-grid CVE-2022-0447 60,000 Reflected XSS (logged-in POST 3) WPScan
48 powerpack-lite-for-elementor CVE-2021-25027 60,000 Reflected XSS WPScan
49 real-cookie-banner CVE-2022-0445 60,000 CSRF settings reset and deleting all GDPR consents WPScan
50 wd-instagram-feed CVE-2021-25047 60,000 Reflected XSS WPScan
51 woocommerce-currency-switcher CVE-2021-25043 60,000 Reflected XSS WPScan
52 woocommerce-currency-switcher CVE-2022-0234 60,000 Reflected XSS WPScan
53 wp-responsive-menu CVE-2021-24971 60,000 Stored XSS WPScan
54 wp-rss-aggregator CVE-2021-24988 60,000 Stored XSS WPScan
55 wp-rss-aggregator CVE-2022-0189 60,000 Reflected XSS (logged-in POST 3) WPScan
56 ditty-news-ticker CVE-2022-0533 50,000 Reflected XSS WPScan
57 event-tickets CVE-2021-25028 50,000 Open redirect WPScan
58 nimble-builder CVE-2022-0314 50,000 Reflected XSS WPScan
59 simple-membership CVE-2022-0328 50,000 CSRF member deletion WPScan
60 super-socializer CVE-2021-24987 50,000 Reflected XSS WPScan
61 bnfw CVE-2022-0345 40,000 E-mail leak WPScan
62 thirstyaffiliates CVE-2022-0398 40,000 Arbitrary affiliate link creation WPScan
63 tutor CVE-2021-25017 40,000 Reflected XSS WPScan
64 advanced-cron-manager CVE-2021-25084 30,000 Arbitrary cron configuration change WPScan
65 contact-form-7-skins CVE-2021-25063 30,000 Reflected XSS WPScan
66 content-egg CVE-2022-0428 30,000 Reflected XSS (logged-in POST 3) WPScan
67 easy-paypal-donation CVE-2021-24989 30,000 CSRF post removal WPScan
68 futurio-extra CVE-2021-25110 30,000 E-mail leak WPScan
69 google-pagespeed-insights CVE-2022-0431 30,000 Reflected XSS (logged-in POST 3) WPScan
70 insight-core CVE-2021-24950 30,000 Stored XSS + object injection WPScan
71 lead-form-builder CVE-2021-24967 30,000 Stored XSS WPScan
72 master-addons CVE-2022-0327 30,000 Reflected XSS WPScan
73 meks-easy-instagram-widget CVE-2021-24958 30,000 Stored XSS WPScan
74 my-calendar CVE-2021-24927 30,000 Reflected XSS WPScan
75 notificationx CVE-2022-0349 30,000 Blind SQL Injection WPScan
76 photo-gallery CVE-2022-0169 30,000 SQL Injection WPScan
77 protect-wp-admin CVE-2021-24906 30,000 Disabling of plugin security features WPScan
78 pz-linkcard CVE-2021-25012 30,000 Reflected XSS WPScan
79 site-reviews CVE-2021-24973 30,000 Stored XSS WPScan
80 ultimate-faqs CVE-2021-24968 30,000 Possibility to add arbitrary FAQs WPScan
81 video-conferencing-with-zoom-api CVE-2022-0384 30,000 E-mail leak WPScan
82 woo-smart-wishlist CVE-2022-0397 30,000 Reflected XSS (logged-in POST 3) WPScan
83 wp-user-frontend CVE-2021-25076 30,000 SQL injection in admin panel leading to reflected XSS WPScan
84 xcloner-backup-and-restore CVE-2022-0444 30,000 Resetting settings, including encryption key WPScan
85 ad-invalid-click-protector CVE-2022-0190 20,000 SQL injection WPScan
86 ad-invalid-click-protector CVE-2022-0191 20,000 CSRF ban removal WPScan
87 advanced-product-labels-for-woocommerce CVE-2022-0399 20,000 Reflected XSS (logged-in POST 3) WPScan
88 asgaros-forum CVE-2022-0411 20,000 Blind SQL Injection WPScan
89 bwp-google-xml-sitemaps CVE-2022-0230 20,000 Stored XSS WPScan
90 crazy-bone CVE-2022-0385 20,000 Stored XSS WPScan
91 event-calendar-wd CVE-2021-25024 20,000 XSS WPScan
92 event-calendar-wd CVE-2021-25025 20,000 Possibility to add arbitrary events WPScan
93 float-menu CVE-2022-0313 20,000 CSRF menu deletion WPScan
94 gmap-embed CVE-2021-25011 20,000 Arbitrary post removal, plugin settings update WPScan
95 gmap-embed CVE-2021-25081 20,000 Arbitrary post removal, plugin settings update via CSRF WPScan
96 image-hover-effects-ultimate CVE-2021-25031 20,000 Reflected XSS WPScan
97 material-design-for-contact-form-7 CVE-2022-0404 20,000 DoS WPScan
98 miniorange-2-factor-authentication CVE-2022-0229 20,000 DoS WPScan
99 mycred CVE-2021-25015 20,000 Reflected XSS WPScan
100 mycred CVE-2022-0287 20,000 E-mail leak WPScan
101 mycred CVE-2022-0363 20,000 Arbitrary post creation WPScan
102 mystickyelements CVE-2022-0148 20,000 Reflected XSS WPScan
103 navz-photo-gallery CVE-2021-24909 20,000 Reflected XSS WPScan
104 newstatpress CVE-2022-0206 20,000 Reflected XSS WPScan
105 page-views-count CVE-2022-0434 20,000 SQL injection WPScan
106 restaurant-reservations CVE-2021-24965 20,000 Stored XSS WPScan
107 woocommerce-product-addon CVE-2021-25018 20,000 Stored XSS WPScan
108 wp-accessiblity-helper CVE-2022-0150 20,000 Reflected XSS WPScan
109 wp-stats-manager CVE-2021-24750 20,000 SQL injection WPScan
110 wp-stats-manager CVE-2021-25042 20,000 Stored XSS WPScan
111 wp-stats-manager CVE-2022-0410 20,000 Blind SQL Injection WPScan
112 wplegalpages CVE-2021-25106 20,000 Stored XSS WPScan
113 advanced-page-visit-counter CVE-2021-24957 10,000 Blind SQL injection WPScan
114 advanced-page-visit-counter CVE-2021-25086 10,000 Stored XSS WPScan
115 affiliates-manager CVE-2021-25078 10,000 Stored XSS WPScan
116 akismet-privacy-policies CVE-2021-25071 10,000 Reflected XSS WPScan
117 ari-fancy-lightbox CVE-2022-0161 10,000 Reflected XSS WPScan
118 business-profile CVE-2021-25060 10,000 Stored XSS WPScan
119 coming-soon-page CVE-2022-0164 10,000 Sending any e-mail to all subscribers WPScan
120 coming-soon-page CVE-2022-0199 10,000 Sending any e-mail to all subscribers via CSRF WPScan
121 dropdown-menu-widget CVE-2021-25113 10,000 Stored XSS WPScan
122 duplicate-page-or-post CVE-2021-25075 10,000 Stored XSS WPScan
123 easy-pricing-tables CVE-2021-25098 10,000 CSRF post removal WPScan
124 english-wp-admin CVE-2021-25111 10,000 Open redirect WPScan
125 ibtana-visual-editor CVE-2021-25014 10,000 Stored XSS WPScan
126 ip2location-country-blocker CVE-2021-25095 10,000 Banning arbitrary countries WPScan
127 ip2location-country-blocker CVE-2021-25096 10,000 Ban circumvention WPScan
128 ip2location-country-blocker CVE-2021-25108 10,000 Banning countries via CSRF WPScan
129 link-library CVE-2021-25091 10,000 Reflected XSS WPScan
130 link-library CVE-2021-25092 10,000 CSRF settings reset WPScan
131 link-library CVE-2021-25093 10,000 Arbitrary link removal WPScan
132 modal-window CVE-2021-25051 10,000 CSRF RCE WPScan
133 page-builder-add CVE-2021-25067 10,000 Reflected XSS WPScan
134 portfolio-wp CVE-2021-25090 10,000 Stored XSS WPScan
135 powerpack-addon-for-beaver-builder CVE-2022-0176 10,000 Reflected XSS WPScan
136 qubely CVE-2021-25013 10,000 Arbitrary post removal WPScan
137 rearrange-woocommerce-products CVE-2021-24928 10,000 SQL injection WPScan
138 registrations-for-the-events-calendar CVE-2021-24943 10,000 SQL injection WPScan
139 registrations-for-the-events-calendar CVE-2021-25083 10,000 Reflected XSS WPScan
140 secure-copy-content-protection CVE-2021-24931 10,000 SQL injection WPScan
141 smart-forms CVE-2022-0163 10,000 Downloading form data WPScan
142 spider-event-calendar CVE-2022-0212 10,000 Reflected XSS WPScan
143 stopbadbots CVE-2021-25070 10,000 Blind SQL injection WPScan
144 ultimate-product-catalogue CVE-2021-24993 10,000 Possibility to add arbitrary products WPScan
145 whmcs-bridge CVE-2021-25112 10,000 Reflected XSS WPScan
146 wicked-folders CVE-2021-24919 10,000 SQL injection WPScan
147 woo-orders-tracking CVE-2021-25062 10,000 Reflected XSS WPScan
148 woocommerce-exporter CVE-2022-0149 10,000 Reflected XSS WPScan
149 woocommerce-store-toolkit CVE-2021-25077 10,000 Reflected XSS WPScan
150 wp-booking-system CVE-2021-25061 10,000 Reflected XSS WPScan
151 wp-coder CVE-2021-25053 10,000 CSRF RCE WPScan
152 wp-photo-album-plus CVE-2021-25115 10,000 Stored XSS WPScan
153 wp125 CVE-2021-25073 10,000 CSRF ad deletion WPScan
154 wpcargo CVE-2021-25003 10,000 RCE WPScan
155 events-made-easy CVE-2021-25030 7,000 SQL injection WPScan
156 likebtn-like-button CVE-2021-24945 7,000 Sensitive data exposure WPScan
157 likebtn-like-button CVE-2022-0745 7,000 Arbitrary e-mail sending WPScan
158 wp-email-users CVE-2021-24959 7,000 SQL injection + object injection WPScan
159 responsive-vector-maps CVE-2021-24947 6,000 Arbitrary file read WPScan
160 button-generation CVE-2021-25052 5,000 CSRF RCE WPScan

Not all of the vulnerabilities were found directly by the fuzzer. For example, CVE-2021-25096 was found accidentally when writing a PoC for CVE-2021-25095. For some other vulnerabilities, the tool alerts were only part of the vulnerability information – for example, the tool notified that a WordPress option can get updated by any user - and finding the consequences (whether it can lead e.g. to stored XSS) required manual work.

Findings worth mentioning

I won’t make fun of any particular plugin author, however, I think some findings are worth sharing.

is_admin

The WordPress is_admin() function, as you may probably have guessed:

Determines whether the current request is for an administrative interface page.

(from https://developer.wordpress.org/reference/functions/is_admin/)

The documentation warns as well, that it:

Does not check if the user is an administrator; use current_user_can() for checking roles and capabilities.

As you may probably have guessed, it was a source of a couple of vulnerabilities in the form of:

if (is_admin()) {
    /* dangerous action */
}

REST route URLs

Let’s consider the following code:

register_rest_route((...), '/(...)/(?P<id>[\d]+)', array(
    array(
        'methods' => WP_REST_Server::READABLE,
        'callback' => array($this, 'callback'),
        'permission_callback' => '__return_true',
    ),
));

/* ... */

function callback($request) {
    $id = $request['id'];
}

What ID values could be passed to the handler?

The correct answer is: all of them – just use /?rest_route=/(...)/1&id=hehehe.

get_users()

Some plugins allow searching for users by providing a part of an e-mail address. That allows to leak any user’s e-mail using the following steps:

  • Bruteforcing the first letter of the domain name (searching for @a, @b, etc., and checking when the user’s name appears in search results).
  • Remembering the first letter and using it to guess the second letter. Let’s assume the user’s e-mail domain name starts with g. You can then brute force the second letter (@ga, @gb, …).
  • Repeating the above steps for the rest of the e-mail address.

Because of that, I have added a check that alerts when get_users() gets called. Unfortunately, besides finding vulnerabilities of this type, it led to numerous false positives as well.

XSS protection

Don’t do the following:

if (/* potential XSS in $parameter detected */) die('Invalid parameter: ' . $parameter);

Several XSS vulnerabilities were also caused by debugging helpers in the form of:


echo "<!--";

var_dump($_POST);

echo "-->";

CAPTCHA verification

Don’t do this:

if (isset($_POST['captcha'])) {
    /* verify captcha */
}

/* do action that should be CAPTCHA-protected */

I have observed this pattern multiple times, both for CAPTCHAs and nonces.

Conclusions

This was just a proof-of-concept to check whether automatic techniques are a viable method to find WordPress plugin bugs. I am sure it can be improved by e.g.:

  • adding checks to detect other types of dangerous operations,
  • attempting to decrease the number of false positives without a large loss of true positives. The percentage of false positives was one of the main obstacles in this project.

This technique can also be implemented for other plugin ecosystems.

Many of the vulnerabilities I found were easily preventable by modern software engineering practices. In many WordPress plugins, HTML is built using an error-prone pile of echo statements, instead of a template language. Similarly, AJAX endpoints are by default available for all logged-in users or all not logged-in users, instead of requiring the developer to provide a fixed allowlist of roles or permissions (so that they would have to explicitly mark a route as available to all logged-in users). Introducing techniques that make it harder to make mistakes and promoting their use is, unfortunately, something only the WordPress team, not plugin developers, can do.

Footnotes

  1. In retrospect, using a pile of regular expressions to detect crashes in the output wasn’t the best idea. Now I would try to do this differently. 

  2. In retrospect, it is obvious that it doesn’t cover all ways to load XML. This should have been done differently. 

  3. This type of reflected XSS requires cookies to be sent with a POST request, therefore would be harder to exploit due to the SameSite-by-default behavior.  2 3 4 5 6 7 8