Critical Vulnerabilities Patched in Adning Advertising Plugin

On June 24, 2020, our Threat Intelligence team was made aware of a possible vulnerability in the Adning Advertising plugin, a premium plugin with over 8,000 customers. We eventually discovered 2 vulnerabilities, one of which was a critical vulnerability that allowed an unauthenticated attacker to upload arbitrary files, leading to Remote Code Execution(RCE), which could allow complete site takeover.

The next day, on June 25, 2020, we privately disclosed these vulnerabilities to the plugin’s author, Tunafish. A patched version was made available in less than 24 hours, on June 26, 2020. We strongly recommend updating to the latest version of this plugin, 1.5.6, immediately.

Wordfence Premium users received a firewall rule protecting against these vulnerabilities on June 25, 2020. Users still running the free version of Wordfence will receive this rule on July 25, 2020.

After monitoring attacks against this firewall rule, we determined that, although these vulnerabilities were being attacked in the wild, the attacks were extremely limited in scope and scale. As such we withheld details from public disclosure for a short period of time to allow users time to update and prevent more widespread exploitation.


Description: Unauthenticated Arbitrary File Upload leading to Remote Code Execution
Affected Plugin: Adning Advertising
Plugin Slug: angwp
Affected Versions: < 1.5.6
CVE ID: N/A
CVSS Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
CVSS score: 10.0(critical)
Patched Version: 1.5.6

One functionality of the Adning plugin is to allow users to upload banner images. In order to provide this functionality, it used an AJAX action, _ning_upload_image. Unfortunately this AJAX action was available with a nopriv_ hook, meaning that any visitor to the site could make use of it, even if they were not logged in. Additionally, the function called by this AJAX action also failed to make use of a capability check or a nonce check.

	public static function _ning_upload_image()
	{
		$_action = isset($_POST['action']) ? $_POST['action'] : '';
		$user_id = isset($_POST['uid']) ? $_POST['uid'] : 0;
		$banner_id = isset($_POST['bid']) ? $_POST['bid'] : 0;
		$max_upload_size = isset($_POST['max_upload_size']) ? $_POST['max_upload_size'] : 100;
		$upload = isset($_POST['upload']) ?  json_decode(stripslashes($_POST['upload']), true) : array();
		$valid_formats = isset($_POST['allowed_file_types']) ? explode(',', $_POST['allowed_file_types']) : array('jpg');
		if( in_array('jpg', $valid_formats) )
		{
			$valid_formats[] = 'jpeg';
		}
		
		//$max_file_size = 1024*100; //100 kb
		//$max_file_size = 1024000*15; // 15 MB (1 mb = 1000 kb)
		$max_file_size = 1024000*$max_upload_size;
		
		//$upload_path = $upload_dir.'/'.$upload_folder;
		//$upload_path = $upload_path.$upload_folder;
		$upload_path = $upload['dir'].$upload['folder'];
		$count = 0;

		// Create upload folder if not exists
		if(!is_dir($upload_path)) {
		    mkdir($upload_path, 0777, true);
		}

		if(!empty($_FILES['files'])) 
		{
			$upload_success = false;
			$upload_error = '';
			$uploaded_files = array();
			$unzip_error = array();

			// Loop $_FILES to execute all files
			foreach ($_FILES['files']['name'] as $f => $name) 
			{     
			    if ($_FILES['files']['error'][$f] == 4) 
			    {
			        continue; // Skip file if any error found
			    }	       
			    if ($_FILES['files']['error'][$f] == 0) 
			    {	           
			        if ($_FILES['files']['size'][$f] > $max_file_size) 
			        {
			            $upload_error = $name. " is too large!";
			            continue; // Skip large files
			        }
					elseif( !in_array(pathinfo($name, PATHINFO_EXTENSION), $valid_formats) )
					{
						$upload_error = $name." is not a valid format";
						continue; // Skip invalid file formats
					}
			        else
			        { 
			        	// No error found! Move uploaded files 
			            if(move_uploaded_file($_FILES["files"]["tmp_name"][$f], $upload_path.$name)){
			            	$count++; // Number of successfully uploaded file
							$src = $upload['src'].$upload['folder'].$name;

			            	// Copy image to banner folder
			            	/*if(!empty($banner_id))
			        		{
			        			if(!is_dir($upload_dir.'/'.$banner_folder)) {
								    mkdir($upload_dir.'/'.$banner_folder, 0777, true);
								}
			        			copy($path.$name, $upload_dir.'/'.$banner_folder.$name);
			        		}*/

			        		$uploaded_files[] = array(
			        			'name' => $name, 
								'size' => $_FILES['files']['size'][$f],
								'upload' => $upload,
								'path' => $upload_path.$name,
			        			'src' => $src,
			        			'grid_item' => '<div class="grid-item" data-src="'.$src.'" data-use="path"><img src="'.$src.'" /><div class="info_btn" data-info="'.basename($src).'"><svg viewBox="0 0 448 512" style="height:18px;border-radius:2px;"><path fill="currentColor" d="M400 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V80c0-26.51-21.49-48-48-48zm-176 86c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z" class=""></path></svg></div></div>',
			        			'uid'  => $user_id,
			        			'action'  => $_action
							);
							
							
							if( pathinfo($name, PATHINFO_EXTENSION) == 'zip')
							{
								$zipfile = array(
									'name' => $_FILES['files']['name'][$f],
									'type' => $_FILES['files']['type'][$f],
									'tmp_name' => $_FILES['files']['tmp_name'][$f],
									'error' => $_FILES['files']['error'][$f],
									'size' => $_FILES['files']['size'][$f],
								);
								
								$unzip_error = self::upload_and_unzip($zipfile, array('folder' => $upload['folder'], 'path' => $upload_path, 'src' => $upload['src']));
							}
						}
						else
						{
							$upload_error = is_writable($upload_path) ? 'Could not move files.' : 'Folder is not writable.';
						}
			        }
			    }
			}

			if(count($uploaded_files) > 0){
				$upload_success = true;
			}

			echo json_encode(array("chk" => $_FILES['files'], "unzip" => $unzip_error, "upload" => $upload, "success" => $upload_success, "files" => json_encode($uploaded_files), "error" => $upload_error));
		}else{
			echo 'no files found.';
		}
		exit;
	}

This function also allowed the user to supply the “allowed” file types. As such it was possible for an unauthenticated attacker to upload malicious code by sending a POST request to wp-admin/admin-ajax.php with the action parameter set to _ning_upload_image the allowed_file_types set to php, and a files parameter containing a malicious PHP file. Alternatively, an attacker could set the allowed_file_types to zip and upload a compressed archive containing a malicious PHP file, which would be unzipped after upload. It was also possible for an attacker to change the upload directory by manipulating the contents of the upload parameter – if the desired directory did not exist, the plugin would create it.


Description: Unauthenticated Arbitrary File Deletion via path traversal
Affected Plugin: Adning Advertising
Plugin Slug: angwp
Affected Versions: < 1.5.6
CVE ID: N/A
CVSS Vector: CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:H
CVSS score: 8.7(high)
Patched Version: 1.5.6

In order to delete any uploaded images, the plugin also registered another ajax action, _ning_remove_image, which also used a nopriv_ hook. As with the upload vulnerability, this function did not perform a capability check or a nonce check. As such it was possible for an unauthenticated attacker to delete arbitrary files using path traversal.

If an attacker were able to delete wp-config.php, the site would be reset, and an attacker could then set it up again and point it to a remote database under their control, effectively replacing the site’s content with their own content.

	public static function _ning_remove_image()
	{
		$upload = wp_upload_dir();
		$upload_dir = $upload['basedir'];
		$upload_url = $upload['baseurl'];
		$upload_folder = self::$upload_folder.$_POST['uid'].'/';	

		$path = $upload_dir.'/'.$upload_folder.basename($_POST['src']);
		$removed = 0;

		if(unlink($path)){
			$remove = 1;
		}
		echo $remove;

		exit;
	}

This attack might require an extra step of preparation, which is that the wp-content/uploads/path folder would need to exist. However, since the previously mentioned arbitrary file upload vulnerability allowed for directory creation, this was not a major obstacle. Once the directory was created, an attacker could send a POST request to wp-admin/admin-ajax.php with the action parameter set to _ning_remove_image, the uid parameter set to /../../.. and the src parameter set to wp-config.php.

Timeline

June 24, 2020 – Wordfence Threat Intelligence receives a report of a compromised website running the Adning plugin. During our investigation, we discovered two vulnerabilities.
June 25, 2020 – Firewall rule released for Premium Wordfence users. We make initial contact with plugin’s author and send full disclosure after receiving a response.
June 26, 2020 – Plugin’s author releases a patch.
July 25, 2020 – Firewall rule becomes available to Wordfence free users.

Conclusion

In today’s post, we discussed two vulnerabilities in the Adning Advertising plugin which could allow an attacker to completely take over a website. These flaws have been fully patched in version 1.5.6. If you are running this plugin, it is critical that you updated to this version as soon as possible. Sites running Wordfence Premium have been protected against these vulnerabilities since June 25, 2020, while sites still using the free version of Wordfence will receive the firewall rule on July 25, 2020.

Special Thanks to Tunafish, the author of the Adning Advertising plugin, for their excellent and timely response in releasing a patch.

The post Critical Vulnerabilities Patched in Adning Advertising Plugin appeared first on Wordfence.

More great articles

Wordfence Intelligence Weekly WordPress Vulnerability Report (November 27, 2023 to December 3, 2023)

Wordfence just launched its bug bounty program. Through December 20th 2023, all researchers will earn 6.25x our normal bounty rates…

Read Story

Wordfence Intelligence Weekly WordPress Vulnerability Report (June 12, 2023 to June 18, 2023)

Last week, there were 60 vulnerabilities disclosed in 52 WordPress Plugins and no WordPress themes that have been added to…

Read Story

WordPress Vulnerability & Patch Roundup March 2023

Vulnerability reports and responsible disclosures are essential for website security awareness and education. Automated attacks targeting known software vulnerabilities are…

Read Story

Emergency WordPress Help

One of our techs will get back to you within minutes.