Headline
CVE-2022-4949: Critical vulnerability in WordPress AdSanity plugin.
The AdSanity plugin for WordPress is vulnerable to arbitrary file uploads due to missing file type validation in the ‘ajax_upload’ function in versions up to, and including, 1.8.1. This makes it possible for authenticated attackers with Contributor+ level privileges to upload arbitrary files on the affected sites server which makes remote code execution possible.
WordPress AdSanity plugin is prone to a critical vulnerability affecting version 1.8.1 and below that could allow a low-privilege user to perform arbitrary file upload, remote code execution and stored cross-site scripting attacks.
Broken Access Control
CVSS v3.1: 9.9 (Critical)
When creating an ad, the plugin allows the upload of a ZIP file. That process is managed inside the “adsanity/views/html5-upload.php” script, by the adsanity_html5_upload AJAX action that loads the ajax_upload function:
/** * Handles the zip file upload via ajax */ public static function ajax_upload() {
// Check the nonce if ( ! array_key_exists( 'security’, $_POST ) || ! wp_verify_nonce( $_POST[‘security’], ‘adsanity-html5-upload’ ) ) { wp_send_json_error( ‘Security’ ); return; }
// Check that all of the parts are here if ( ! array_key_exists( 'file_upload’, $_FILES ) || ! array_key_exists( 'tmp_name’, $_FILES[‘file_upload’] ) || ! array_key_exists( 'type’, $_FILES[‘file_upload’] ) || ! array_key_exists( 'ad_id’, $_POST ) ) { wp_send_json_error( ‘Request is missing parts’ ); return; }
// Possible mime type for a zip file $allowed_mime_types = array( 'application/zip’, 'application/octet-stream’, 'application/x-zip-compressed’, 'multipart/x-zip’, );
if ( ! in_array( $_FILES[‘file_upload’][‘type’], $allowed_mime_types ) ) { wp_send_json_error( [ ‘message’ => esc_js( __( 'Invalid filetype.’, ‘adsanity’ ) ), ], 400 ); return; }
$upload_dir = wp_upload_dir(); $upload_base_dir = $upload_dir[‘basedir’]; $adsanity_dir = trailingslashit( $upload_base_dir ) . 'adsanity/’; $ad_id = $_POST[‘ad_id’]; $ad_dir = $adsanity_dir . $ad_id;
// Create the ad src for use in meta $ad_src = trailingslashit( trailingslashit( $upload_dir[‘baseurl’] ) . ‘adsanity/’ . $ad_id );
// Get the path to this ad $ad_path = trailingslashit( trailingslashit( $upload_dir[‘basedir’] ) . ‘adsanity/’ . $ad_id );
/** * Initialize WP_Filesystem * Used for unzip_file and rmdir */ global $wp_filesystem; WP_Filesystem();
// Check to see if there has already been an upload to this folder if ( is_dir( $ad_path ) ) { // Remove the folder $wp_filesystem->rmdir( $ad_path, true ); }
// Unzip the files $unzipped = unzip_file( $_FILES[‘file_upload’][‘tmp_name’], $ad_dir );
if ( ! $unzipped ) { wp_send_json_error( 'File upload error’, 500 ); return; }
// Scan the directory for this ad $scanned = scandir( $ad_path );
/** * Check to see if this only contains a directory * HTML5 ads can be uploaded as a zip file * of only files, or a zipped folder */ while ( 3 === count( $scanned ) && is_dir( $ad_path . trailingslashit( $scanned[2] ) ) && ! in_array( 'index.html’, $scanned ) ) { // Update the path and src with the new inner folder added $ad_path .= trailingslashit( $scanned[2] ); $ad_src .= trailingslashit( $scanned[2] ); // Re-scan $scanned = scandir( $ad_path ); }
// Check for the existence of an index.html file if ( ! in_array( 'index.html’, $scanned ) ) { wp_send_json_error( [ ‘message’ => esc_js( __( 'Zip file must contain an index.html file.’, ‘adsanity’ ) ), ], 400 ); }
update_post_meta( $ad_id, 'ad_src’, $ad_src );
wp_send_json_success( array( ‘src’ => $ad_src ) );
}
That function is used to upload and extract the content of a ZIP archive into the “wp-content/uploads/adsanity/{post_id}/” folder. It only has a security nonce, accessible to any user with Contributor or above privileges, and a simple check to ensure there’s an index.html file inside the archive. After the upload, the function will return the full path to the folder where the files were extracted:
{"success":true,"data":{"src":"http:\/\/example.com\/wp-content\/uploads\/adsanity\/407\/"}}
A low-privilege user such as a Contributor can upload any file into that folder:
If the blog has a .htaccess file to prevent PHP code execution inside the /uploads/ folder, the attacker can easily override that protection by uploading another .htaccess.
In the ads management section, the plugin injects inside a metabox an iframe that points to the directory where the files were extracted:
<?php // Get ad src global $post; $ad_src = get_post_meta( $post->ID, 'ad_src’, true ); if ( $ad_src ) { ?> <iframe src="<?php echo esc_attr( trailingslashit( $ad_src ) ); ?>" scrolling="no" frameborder="0"></iframe> <?php } ?>
The attacker can add an index.php script inside the ZIP archive. Its code will be loaded by the iframe instead of the index.html file, and executed inside the metabox each time a user accesses the ads manager in the backend:
Additionally, the attacker can upload files with JavaScript code too, which could be used to target the administrator reviewing the post:
Timeline
The issue was reported to the developer on January 13, 2022, and a new version 1.8.2 was released on January 14, 2022. Note that the new version doesn’t allow Contributor users to upload files but still allow Author+ users to do so, therefore if you have Author users registered on your blog, you may exercise extreme caution. Note also that if you’re using our web application firewall for WordPress, NinjaFirewall WP Edition (free) and NinjaFirewall WP+ Edition (premium), only the Admin (single site) and Superadmin (multisite) will be allowed to access the upload function, any other user will be blocked.
Stay informed about the latest vulnerabilities
- Running WordPress? You can get email notifications about vulnerabilities in the plugins or themes installed on your blog.
- On Twitter: @nintechnet
Related news
Gentoo Linux Security Advisory 202409-10 - Multiple vulnerabilities have been discovered in Xen, the worst of which could lead to privilege escalation. Versions greater than or equal to 4.17.4 are affected.
An attacker with local access to a system (either through a disk or external drive) can present a modified XFS partition to grub-legacy in such a way to exploit a memory corruption in grub’s XFS file system implementation.