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.
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.
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.
This article was written by Ramuel Gall, a former Wordfence Senior Security Researcher.
Comments
9:06 am
developers binary skills - level master
//$max_file_size = 1024*100; //100 kb
//$max_file_size = 1024000*15; // 15 MB (1 mb = 1000 kb)
10:30 am
thank you wordfence! best plugin ever. saved my website.
8:58 am
Ilove this plugin , its best security plugin thank you wordfence!
7:32 pm
nic info i like it thank you Sir