A technique to semi-automatically discover new vulnerabilities in WordPress plugins

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