6 Sep 2024

WordPress Plugin Security Review: Download Monitor

For our 45th security review of a WordPress plugin based on the voting of our customers, we reviewed the plugin Download Monitor.

If you are not yet a customer of the service, once you sign up for the service as a paying customer, you can start suggesting and voting on plugins to get security reviews. For those already using the service that haven’t already suggested and voted for plugins to receive a review, you can start doing that here. You can use our tool for doing limited automated security checks of plugins to see if plugins you are using have possible issues that would make them good candidates to get a review. You can also order a review of a plugin separately from our service.

The review was done on version 5.0.6 of Download Monitor. We checked for the following issues during it as part of our standard review:

  • Insecure file upload handling (this is the cause of the most exploited type of vulnerability, arbitrary file upload)
  • Deserialization of untrusted data
  • Security issues with functions accessible through WordPress’ AJAX functionality (those have and continued to be a common source of disclosed vulnerabilities)
  • Security issues with functions accessible through WordPress’ REST API (those have started to be a source of disclosed vulnerabilities)
  • Persistent cross-site scripting (XSS) vulnerabilities in the frontend portions of the plugin and in the admin portions accessible to users with the Author role or below
  • Cross-site request forgery (CSRF) vulnerabilities in the admin portion of the plugin
  • SQL injection vulnerabilities (the code that handles requests to the database)
  • Reflected cross-site scripting (XSS) vulnerabilities
  • Security issues with functions accessible through any of the plugin’s shortcodes
  • Security issues with functions accessible through any of the plugin’s blocks
  • Security issues with functions accessible through the admin_action action
  • Security issues with functions accessible through the admin_init action
  • Security issues with functions accessible through the admin_post action
  • Security issues with import/export functionality
  • Security issues with usage of the is_admin() function
  • Security issues with usage of the add_option(), delete_option(), and update_option() functions
  • Security issues with usage of the update_user_meta() and wp_update_user() functions
  • Security with usage of determine_current_user filter
  • Security issues with usage of the wp_set_current_user(), wp_set_auth_cookie() and wc_set_customer_auth_cookie() functions
  • Security issues with usage of the reset_password() and wp_set_password() functions
  • Security issues with usage of the extract() function
  • Lack of IP address validation
  • Proper usage of sanitize_callback when using register_setting() to register settings
  • Existence of register_uninstall_hook or uninstall.php file that removes any WordPress options and database tables added by the plugin
  • CSV injection
  • Host header injection vulnerabilities
  • Lack of protection against unintended direct access of PHP files
  • Insecure and unwarranted requests to third-party websites
  • Any additional possible issues identified by our Plugin Security Checker

Results

We found the plugin contained vulnerabilities and plenty of places where security could be improved, which are detailed below.

We contacted the developer about the results through the website for the plugin last Friday, but we have yet to hear back from them and there hasn’t been a new version released. In line with our reasonable disclosure policy, we are disclosing the issues now, as the developer hadn’t responded to let us know they would address the issues within a month. That lack of response isn’t all that surprising as they have yet to fix an incompletely fixed vulnerability we notified them of that in June of last year.

“Export download” Privilege Escalation

The underlying function for the plugin’s “Export download” feature, which exports information about the download, was accessible by anyone who could create WordPress posts instead only those that can create downloads. That occurred with the function dlm_export_download() in the file /src/Admin/class-dlm-admin-debug.php.

That was incorrectly fixed in version 5.0.9, which was released while we were working on the review. The fix checks for the wrong capability, but a nonce check was added that is limited to the intended users.

Privilege Escalation

The function actions_handler() and bulk_actions_handler() in the file /src/Admin/DownloadPaths/class-dlm-downloads-path.php lack a capability check or a nonce check to prevent CSRF. As those run during admin_init() they are accessible to anyone, so they are vulnerable. Those handle actions “related to download paths.”

Cross-Site Request Forgery (CRSR)/Restricted File Upload

As we notified the developer over a year ago, the function upload_file() in the file /src/Admin/WritePanels.php doesn’t check for a nonce to prevent CSRF. So an attacker could cause someone who can upload files through the plugin to do so without intending it.

Cross-Site Request Forgery (CSRF)

The function dlm_ml_do_bulk() in the file /src/Admin/class-dlm-media-library.php doesn’t check for a nonce to prevent CSRF. That is also true for the function handle_form_submission() in the file /src/Admin/class-dlm-network-settings.php and the function catch_hide_message() in /src/LegacyUpgrader/Message.php.

Lack of IP address validation

The function get_visitor_ip() in the file /src/Utils.php currently doesn’t properly vet the IP address value. Both $_SERVER[“HTTP_X_REAL_IP”] and $_SERVER[“HTTP_CF_CONNECTING_IP”] are user input, so if the website is not running behind a proxy that uses those, an attacker can set those to any value. The value isn’t a text field, so sanitize_text_field() isn’t the appropriate way to handle that. Instead, the value should validate using like this:

filter_var($ip, FILTER_VALIDATE_IP ) )

Lack of Capability Check

In numerous AJAX accessible functions, the plugin doesn’t include a capability check to properly limit access to the function. That occurs with the following functions:

  • ajax() in /includes/admin/class-dlm-beta-testers.php
  • ajax() in /includes/admin/class-dlm-review.php
  • count_log_entries(), update_log_table_db(), alter_download_log_table(), and clear_offset() in /src/Admin/class-dlm-db-upgrader.php
  • ajax_duplicate_download() in /src/Admin/CustomActions.php
  • handle_extensions() and handle_master_license() in /src/Admin/Extensions.php
  • save_reports_setting() in /src/Admin/Reports/class-dlm-reports.php
  • ajax_handle_api_key_actions() and ajax_search_users() in /src/KeyGeneration/class-dlm-key-generation.php
  • handle_settings_lazy_select(), dismiss_notice(), save_attachment_meta(), upgrade_download_category(), and enable_shop() in /src/AjaxHandler.php

Several of those functions do a nonce check for a nonce provided to low level WordPress users (dlm_ajax_nonce), so there is a vulnerability with those, as non-intended WordPress users have access.

Improper SQL Sanitization/Escaping

In the functions prepare_items() and retrieve() in the file /src/KeyGeneration/admin/class-dlm-api-keys-table.php, the plugin is bringing user input in to an SQL query using the sanitize_text_field(), which doesn’t sanitize user input for SQL statements:

182
183
$search_field = sanitize_text_field( $_GET['s'] );
$query        .= " AND  users.user_login LIKE '%{$search_field}%' ";

In the function prep_where_statement() in the file /src/Logs/WordPressLogItemRepository.php, the esc_sql() function is used in situation there the value being escaped is not in quote marks, either it is in backticks or nothings surrounding it, so it is not producing effective escaping. The same is true of the functions prep_where_statement() and retrieve() in the file /src/Shop/Order/WordPressRepository.php.

Lack of sanitize_callback when using register_setting() to register settings

In the files /src/Admin/DownloadPaths/class-dlm-downloads-path.php and /src/Admin/Settings/Settings.php, the plugin registers settings using register_setting(), but doesn’t set a sanitize_callback to sanitize the values of the settings.

Permissive Option Update

The function save_reports_settings() in the file /src/Admin/Reports/class-dlm-reports.php, allows any WordPress option to be changed, while it is only intended to “Save reports settings”.

Permissive Sanitization

In the files /src/RestAPI/class-dlm-download-rest.php and /src/RestAPI/class-dlm-version-rest.php, user input that is restricted to a string isn’t being further sanitized or validated. For example, this line doesn’t restrict the value to being a URL:

253
$version->set_mirrors( isset( $params['url'] ) ? array( $params['url'] ) : array() );

PHP Function filter_input() Use Without a Filter

In the function prepareFormField() in the file /src/Dependencies/PayPalHttp/Serializer/Multipart.php, the function filter_var() is used without a filter, so it doesn’t do any filtering.

Missing sanitize_file_name() Usage

The function get_template_part() in the file /src/TemplateHandler.php doesn’t sanitize/validate the $name value. That is at least user input when passed by the function render_download_button() in the file /src/Gutenberg.php. The function sanitize_file_name() is used at least one place before the value is passed to get_template_part().

extract() Usage

The plugin isn’t using the function extract() in the plugin. The WordPress PHP Coding Standards say to not use that. The PHP documentation warns not to use it with user input, which is what the plugin does with shortcode attributes. It would be better to split out setting values to variables and doing sanitization/validation when doing that.

Lack of Protection Against PHP Object Injection

In the function set_licensed_extensions() in the file /src/Admin/Extensions.php and the function get_user_data() in the file /src/Admin/Reports/class-dlm-reports.php, the plugin is using the unserialize() function without specifying not to unserialize objects, which would prevent the possibility of PHP object injection.

Lack of Proper Uninstallation

The plugin doesn’t implement one of WordPress’ methods for properly handling uninstalling the plugin.

Lack of Protection Against Direct Access to PHP Files

Some of the plugin’s .php files do not have code at the beginning of the files to restrict direct access to them. We didn’t see anything that could be exploited in the files without the restriction in place, but adding the code the plugin already has in other files would make things more secure:

if ( ! defined( 'ABSPATH' ) ) {
	exit;
} // Exit if accessed directly

Leave a Reply

Your email address will not be published.