This blog post is about the Porto Theme’s plugin vulnerability. If you’re a Porto Theme user, please update the plugin to at least version 2.12.1.
You can sign up for the Patchstack Community plan to be notified about vulnerabilities as soon as they become disclosed.
For plugin developers, we have security audit services and Threat Intelligence Feed API for hosting companies.
About the Porto Theme’s Plugin
The plugin Porto Theme – Functionality (premium version) is a required plugin for the Porto theme. The theme itself is estimated to have more than 95,000 currently active installations. The Porto theme is known as the more popular premium multipurpose & WooCommerce Theme. This plugin is developed by p-themes.
Porto theme is an ultimate business & WooCommerce WordPress theme that is suitable for any business and WooCommerce site. Porto provides plenty of elements and powerful features that can configure all we want. Porto provides ultimate WooCommerce features with exclusive skins, layouts, and features.
The security vulnerability
This plugin suffers from an unauthenticated SQL injection vulnerability. This vulnerability allows any unauthenticated user to perform SQL injection. The described vulnerability was fixed in version 2.12.1 and assigned CVE-2023-48738.
Check this vulnerability in the Patchstack vulnerability database.
Unauthenticated SQL Injection
The underlying vulnerable code exists in the bulk_delete_critical
function:
/**
* Bulk delete the critical CSS.
*
* @since 2.3.0
*/
public function bulk_delete_critical() {
if ( ! isset( $_GET['post'] ) ) {
$this->redirect_critical_wizard();
}
$page_ids = wp_unslash( $_GET['post'] );
foreach ( $page_ids as $key => $value ) {
if ( 'homepage' == $value ) {
unset( $page_ids[ $key ] );
update_option( 'homepage_critical', '' );
break;
}
}
// Delete critical css
global $wpdb;
$page_ids = sanitize_text_field( implode( ',', $page_ids ) );
$wpdb->query( $wpdb->prepare( 'UPDATE ' . $wpdb->postmeta . " SET meta_value = '' WHERE meta_id IN ($page_ids)" ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$this->redirect_critical_wizard();
}
This function handles the process of deletion of critical CSS, which is a feature that helps the user reduce the rendering time of the CSS file. The function can be called from the table_actions
function:
/**
* The Table Actions
*
* @since 2.3.0
*/
public function table_actions() {
$action = '';
if ( isset( $_REQUEST['action'] ) ) {
if ( -1 !== $_REQUEST['action'] && '-1' !== $_REQUEST['action'] ) {
$action = sanitize_text_field( wp_unslash( $_REQUEST['action'] ) );
}
}
if ( isset( $_REQUEST['action2'] ) ) {
if ( -1 !== $_REQUEST['action2'] && '-1' !== $_REQUEST['action2'] ) {
$action = sanitize_text_field( wp_unslash( $_REQUEST['action2'] ) );
}
}
if ( ! empty( $action ) ) {
if ( 'porto_bulk_delete_critical' == $action ) {
$this->bulk_delete_critical();
} elseif ( 'delete_css' == $action ) {
$this->delete_css();
}
return false;
}
if ( ( isset( $_REQUEST['page'] ) && false !== strpos( $_REQUEST['page'], 'porto' ) ) && ( isset( $_REQUEST['action2'] ) || isset( $_REQUEST['action'] ) ) ) {
$referer = wp_get_referer();
if ( $referer ) {
wp_safe_redirect( $referer );
die;
}
}
return false;
}
The table_actions
is just a function that handles some process on the critical CSS feature. The function actually is called from the init
function:
/**
* The init function.
*
* @since 2.3.0
*/
public function init() {
global $porto_settings_optimize;
if ( defined( 'PORTO_VERSION' ) && ! empty( $porto_settings_optimize['critical_css'] ) && is_admin() ) {
add_action( 'admin_menu', array( $this, 'add_admin_menus' ) );
$this->table_actions();
}
}
This init
function is hooked to the WordPress built-in init
hook which is just a hook that runs after WordPress has finished loading but before any headers are sent. Given the condition, any unauthenticated user is able to execute the init
function and eventually call bulk_delete_critical
through the table_actions
function. This is possible because there is no permission and nonce validation on the table_actions
or bulk_delete_critical
functions.
Notice that in the bulk_delete_critical
function we can inject our SQL injection payload via the $page_ids
variable.
Note that this vulnerability can be reproduced with an Unauthenticated user with the condition that the Critical CSS feature is enabled on the plugin settings.
The patch
The patch includes an implementation of permission and nonce validation on the table_actions
function. For the vulnerable variable, force mapping the $page_ids
variable to an integer value should be enough to prevent SQL injection. The patch can be seen below:
Conclusion
Always secure the SQL process in plugins or themes with proper function and implementation. Both are important since the usage of proper functions to prevent SQL Injection like esc_sql()
and $wpdb->prepare
alone are not enough to prevent SQL Injection if the usage implementation is not proper.
For a value that is intended to only contain an integer value, we recommend to implement intval
to the variable so that it contains only valid integer values.
Timeline
1 August, 2023
We found the vulnerability and reached out to the vendor.
23 November, 2023
Published the vulnerabilities to the Patchstack vulnerability database (No reply from vendor).
25 November, 2023
Porto Theme – Functionality plugin version 2.12.1 released to patch the reported issue.
20 December, 2023
Security advisory article publicly released.
Help us make the Internet a safer place
Making the WordPress ecosystem more secure is a team effort, and we believe that plugin developers and security researchers should work together.
- If you’re a plugin developer, join our mVDP program that makes it easier to report, manage and address vulnerabilities in your software.
- If you’re a security researcher, join Patchstack Alliance to report vulnerabilities & earn rewards.