July 8

Critical Vulnerabilities Patched inside Adning Advertising Plugin

WordPress Vulnerabilities


This post was originally published on this site

On June 24, 2020, our Threat Cleverness team was made alert to a possible vulnerability within the Adning Marketing plugin, reduced plugin with over 8,000 customers. We ultimately discovered 2 vulnerabilities, among which was a crucial vulnerability that permitted an unauthenticated attacker to upload arbitrary data files, leading to Remote Program code Execution(RCE), that could allow complete web site takeover.

The following day, on June 25, 2020, we privately disclosed these vulnerabilities to the plugin’s writer, Tunafish. A patched edition was offered in less than a day, on June 26, 2020. We strongly suggest updating to the most recent version of the plugin, 1.5.6, immediately.

Wordfence Premium customers received a firewall principle avoiding these vulnerabilities on June 25, 2020. Users nevertheless running the free edition of Wordfence will receive this guideline on July 25, 2020.

After monitoring attacks from this firewall principle, we determined that, although these vulnerabilities were being attacked in the open, the attacks were extremely limited in scope and scale. Therefore we withheld information from open public disclosure for a brief period of time and energy to allow users time and energy to update and prevent even more widespread exploitation.

Explanation: Unauthenticated Arbitrary Document Upload resulting in Remote Program code Execution
Affected Plugin: Adning Advertising
Plugin Slug: angwp
Impacted Versions: < 1.5.6
CVSS rating: 10.0(essential)
Patched Edition: 1.5.6

One functionality of the Adning plugin would be to allow users to upload banner images. To be able to provide this functionality, it used an AJAX action, _ning_upload_image. Unfortunately this AJAX action was available with a nopriv_ hook, and therefore any visitor to the website could make usage of it, even if these were not logged in. Additionally, the event called by this AJAX action also didn’t utilize a capability check or perhaps 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 or even exists
		    mkdir($upload_path, 0777, true);

			$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 big!";
			            continue; // Skip large files
					elseif( !in_array(pathinfo($name, PATHINFO_EXTENSION), $valid_formats) )
						$upload_error = $name." isn't a valid format";
						continue; // Skip invalid file formats
			        	// No error found! Move uploaded files 
			            if(move_uploaded_file($_FILES["files"]["tmp_name"][$f], $upload_path.$name))
			            	$count++; // Amount of successfully uploaded file
							$src = $upload['src'].$upload['folder'].$name;

			            	// Copy image to 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']));
							$upload_error = is_writable($upload_path) ? 'Could not move files.' : 'Folder isn't 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));
			echo 'no files found.';

This function also allowed an individual to provide the “allowed” file types. Therefore it was easy 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 may be unzipped after upload. It had been also easy for an attacker to improve the upload directory by manipulating the contents of the upload parameter – if the required directory didn’t 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
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. Much like the upload vulnerability, this function didn’t perform capability check or perhaps a nonce check. Therefore it was easy for an unauthenticated attacker to delete arbitrary files using path traversal.

If an attacker could actually delete wp-config.php, the website will be reset, and an attacker could then arrange it again and point it to a remote database under their control, effectively replacing the site’s quite happy with their very 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;

			$remove = 1;
		echo $remove;


This attack may need a supplementary step of preparation, that is that the wp-content/uploads/path folder would have to exist. However, because the earlier mentioned arbitrary file upload vulnerability allowed for directory creation, this is not just a major obstacle. After the directory was made, 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.


June 24, 2020 – Wordfence Threat Intelligence receives a written 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 connection with plugin’s author and send full disclosure after finding a response.
June 26, 2020 – Plugin’s author releases a patch.
July 25, 2020 – Firewall rule becomes open to Wordfence free users.


In today’s post, we discussed two vulnerabilities in the Adning Advertising plugin that could allow an attacker to totally take over an internet site. These flaws have already been fully patched in version 1.5.6. If you’re running this plugin, it is important that you updated to the version at the earliest opportunity. Sites running Wordfence Premium have already been protected against these vulnerabilities since June 25, 2020, while sites still utilizing the free version of Wordfence will have the firewall rule on July 25, 2020.

Special Because of Tunafish, the writer of the Adning Advertising plugin, because of their excellent and timely response in releasing a patch.

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

About the author 

WP Maintain Support Protect

You may also like

Who Attacked SolarWinds and just why WordPress Users Have to know

Who Attacked SolarWinds and just why WordPress Users Have to know

SolarWinds and offer Chain Attacks: Could this happen to WordPress?

SolarWinds and offer Chain Attacks: Could this happen to WordPress?

WordPress Hardening: 18 Methods to Harden Security of one’s Website

WordPress Hardening: 18 Methods to Harden Security of one’s Website
{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

Subscribe to our newsletter now!