CVE-2022-28960 (published 2022-05-19T21:15Z)

A PHP injection vulnerability in Spip before v3.2.8 allows attackers to execute arbitrary PHP code via the _oups parameter at /ecrire.

Severity: MEDIUM
CVSS v2 base score: 6.5 (AV:N/AC:L/Au:S/C:P/I:P/A:P)
CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-116
cwe_name: Improper Encoding or Escaping of Output
description: The product prepares a structured message for communication with another component, but encoding or escaping of the data is either missing or done incorrectly. As a result, the intended structure of the message is not preserved.
extended_description: 
            Improper encoding or escaping can allow attackers to change the commands that are sent to another component, inserting malicious commands instead.
            Most products follow a certain protocol that uses structured messages for communication between components, such as queries or commands. These structured messages can contain raw data interspersed with metadata or control information. For example, "GET /index.html HTTP/1.1" is a structured message containing a command ("GET") with a single argument ("/index.html") and metadata about which protocol version is being used ("HTTP/1.1").
            If an application uses attacker-supplied inputs to construct a structured message without properly encoding or escaping, then the attacker could insert special characters that will cause the data to be interpreted as control information or metadata. Consequently, the component that receives the output will perform the wrong operations, or otherwise interpret the data incorrectly.
         
         

Fix information:
repo_url: https://github.com/spip/SPIP
commit_details: repo_url: https://github.com/spip/SPIP
author: Cerdic
author_date: 2020-05-19 17:48:56+02:00
msg: Divers petites sanitization et une balise manquante #4494
repository: repo_url: https://github.com/spip/SPIP
repo_name: spip/spip
description: SPIP Core
repo_language: PHP
owner: spip
file_changes: filename: filtres.php
change_type: ModificationType.MODIFY
diff: @@ -403,6 +403,17 @@ function filtre_setenv(&$Pile, $val, $key, $continue = null) {
 	return $continue ? $val : '';
 }
 
+/**
+ * @param array $Pile
+ * @param array|string $keys
+ * @return string
+ */
+function filtre_sanitize_env(&$Pile, $keys) {
+	$Pile[0] = spip_sanitize_from_request($Pile[0], $keys);
+	return '';
+}
+
+
 /**
  * Filtre `debug` qui affiche un debug de la valeur en entrée
  *

programming_language: PHP
filename: precharger_objet.php
change_type: ModificationType.MODIFY
diff: @@ -50,7 +50,7 @@ function precharger_objet($type, $id_objet, $id_rubrique = 0, $lier_trad = 0, $c
 
 	// si l'objet existe deja, on retourne simplement ses valeurs
 	if (is_numeric($id_objet)) {
-		return sql_fetsel("*", $table, "$_id_objet=$id_objet");
+		return sql_fetsel("*", $table, "$_id_objet=".intval($id_objet));
 	}
 
 	// ici, on demande une creation.
@@ -133,7 +133,7 @@ function precharger_traduction_objet($type, $id_objet, $id_rubrique = 0, $lier_t
 	$_id_objet = id_table_objet($table);
 
 	// Recuperer les donnees de l'objet original
-	$row = sql_fetsel("*", $table, "$_id_objet=$lier_trad");
+	$row = sql_fetsel("*", $table, "$_id_objet=".intval($lier_trad));
 	if ($row) {
 		include_spip('inc/filtres');
 		$row[$champ_titre] = filtrer_entites(objet_T($type, 'info_nouvelle_traduction')) . ' ' . $row[$champ_titre];
@@ -172,12 +172,12 @@ function precharger_traduction_objet($type, $id_objet, $id_rubrique = 0, $lier_t
 					$id_parent = 0;
 				} else {
 					// on cherche une rubrique soeur dans la bonne langue
-					$row_rub = sql_fetsel("id_parent", "spip_rubriques", "id_rubrique=$id_rubrique");
+					$row_rub = sql_fetsel("id_parent", "spip_rubriques", "id_rubrique=".intval($id_rubrique));
 					$id_parent = $row_rub['id_parent'];
 				}
 
 				$row_rub = sql_fetsel("id_rubrique", "spip_rubriques",
-					"lang='" . $GLOBALS['spip_lang'] . "' AND id_parent=$id_parent");
+					"lang='" . $GLOBALS['spip_lang'] . "' AND id_parent=".intval($id_parent));
 				if ($row_rub) {
 					$row['id_rubrique'] = $row_rub['id_rubrique'];
 				}

programming_language: PHP
filename: utils.php
change_type: ModificationType.MODIFY
diff: @@ -446,6 +446,43 @@ function set_request($var, $val = null, $c = false) {
 	return false; # n'affecte pas $c
 }
 
+/**
+ * Sanitizer une valeur *SI* elle provient du GET ou POST
+ * Utile dans les squelettes pour les valeurs qu'on attrape dans le env,
+ * dont on veut permettre à un squelette de confiance appelant de fournir une valeur complexe
+ * mais qui doit etre nettoyee si elle provient de l'URL
+ *
+ * On peut sanitizer
+ * - une valeur simple : `$where = spip_sanitize_from_request($value, 'where')`
+ * - un tableau en partie : `$env = spip_sanitize_from_request($env, ['key1','key2'])`
+ * - un tableau complet : `$env = spip_sanitize_from_request($env, '*')`
+ *
+ * @param string|array $value
+ * @param string|array $key
+ * @param string $sanitize_function
+ * @return array|mixed|string
+ */
+function spip_sanitize_from_request($value, $key, $sanitize_function='entites_html') {
+	if (is_array($value)) {
+		if ($key=='*') {
+			$key = array_keys($value);
+		}
+		if (!is_array($key)) {
+			$key = [$key];
+		}
+		foreach ($key as $k) {
+			if (!empty($value[$k])) {
+				$value[$k] = spip_sanitize_from_request($value[$k], $k, $sanitize_function);
+			}
+		}
+		return $value;
+	}
+	// si la valeur vient des GET ou POST on la sanitize
+	if (!empty($value) and $value == _request($key)) {
+		$value = $sanitize_function($value);
+	}
+	return $value;
+}
 
 /**
  * Tester si une URL est absolue

programming_language: PHP
filename: aiguiller.php
change_type: ModificationType.MODIFY
diff: @@ -15,6 +15,13 @@ if (!defined('_ECRIRE_INC_VERSION')) {
 }
 
 function securiser_redirect_action($redirect) {
+	// cas d'un double urlencode : si un urldecode de l'url n'est pas secure, on retient ca comme redirect
+	if (strpos($redirect, '%') !== false) {
+		$r2 = urldecode($redirect);
+		if (($r3 = securiser_redirect_action($r2)) !== $r2) {
+			return $r3;
+		}
+	}
 	if ((tester_url_absolue($redirect) or preg_match(',^\w+:,',trim($redirect)))
 		and !defined('_AUTORISER_ACTION_ABS_REDIRECT')) {
 		// si l'url est une url du site, on la laisse passer sans rien faire

programming_language: PHP
filename: balises.php
change_type: ModificationType.MODIFY
diff: @@ -1691,6 +1691,19 @@ function balise_REM_dist($p) {
 	return $p;
 }
 
+/**
+ * Une balise #NULL quand on a besoin de passer un argument null sur l'appel d'un filtre ou formulaire
+ * (evite un #EVAL{null})
+ * @param $p
+ * @return mixed
+ */
+function balise_NULL_dist($p) {
+	$p->code = "null";
+	$p->interdire_scripts = false;
+
+	return $p;
+}
+
 
 /**
  * Compile la balise `#HTTP_HEADER` envoyant des entêtes de retour HTTP

programming_language: PHP
filename: criteres.php
change_type: ModificationType.MODIFY
diff: @@ -1557,11 +1557,11 @@ function critere_where_dist($idb, &$boucles, $crit) {
 	if (isset($crit->param[0])) {
 		$_where = calculer_liste($crit->param[0], $idb, $boucles, $boucle->id_parent);
 	} else {
-		$_where = '@$Pile[0]["where"]';
+		$_where = 'spip_sanitize_from_request(@$Pile[0]["where"],"where","vide")';
 	}
 
 	if ($crit->cond) {
-		$_where = "(($_where) ? ($_where) : '')";
+		$_where = "((\$zzw = $_where) ? \$zzw : '')";
 	}
 
 	if ($crit->not) {

programming_language: PHP
filename: editer_logo.php
change_type: ModificationType.MODIFY
diff: @@ -61,6 +61,7 @@ function formulaires_editer_logo_charger_dist($objet, $id_objet, $retour = '', $
 	if (!is_array($options)) {
 		$options = unserialize($options);
 	}
+	$options = spip_sanitize_from_request($options, '*');
 
 	if (!isset($options['titre'])) {
 		$balise_img = chercher_filtre('balise_img');

programming_language: PHP
filename: structure.html
change_type: ModificationType.MODIFY
diff: @@ -5,5 +5,6 @@
 <head>
 <INCLURE{fond=prive/squelettes/head/#ENV{type-page},env,espace_prive=1}>
 </head>
+[(#LISTE{titre,sinon}|sanitize_env)]
 <INCLURE{fond=prive/squelettes/body,env,espace_prive=1}>
 </html>

programming_language: HTML
repo_url: https://github.com/spip/SPIP
commit_details: repo_url: https://github.com/spip/SPIP
author: Cerdic
author_date: 2020-05-27 17:49:15+02:00
msg: _oups donc (aka un base64_encode ca fait pas de mal)
repository: repo_url: https://github.com/spip/SPIP
repo_name: spip/spip
description: SPIP Core
repo_language: PHP
owner: spip
file_changes: filename: editer_liens.php
change_type: ModificationType.MODIFY
diff: @@ -127,6 +127,17 @@ function formulaires_editer_liens_charger_dist($a, $b, $c, $options = array()) {
 		$skel_ajout = $table_source . '_roles_associer';
 	}
 
+	$oups = _request('_oups');
+	if (unserialize(base64_decode($oups))) {
+		// on est bon, rien a faire
+	}
+	elseif(unserialize($oups)) {
+		// il faut encoder
+		$oups = base64_encode($oups);
+	}
+	else {
+		$oups = '';
+	}
 	$valeurs = array(
 		'id' => "$table_source-$objet-$id_objet-$objet_lien", // identifiant unique pour les id du form
 		'_vue_liee' => $skel_vue,
@@ -145,7 +156,7 @@ function formulaires_editer_liens_charger_dist($a, $b, $c, $options = array()) {
 		'ordonner_lien' => '',
 		'desordonner_liens' => '',
 		'_roles' => $roles, # description des roles
-		'_oups' => _request('_oups'),
+		'_oups' => $oups,
 		'editable' => $editable,
 	);
 
@@ -221,6 +232,7 @@ function formulaires_editer_liens_traiter_dist($a, $b, $c, $options = array()) {
 		// annuler les suppressions du coup d'avant !
 		if (_request('annuler_oups')
 			and $oups = _request('_oups')
+			and $oups = base64_decode($oups)
 			and $oups = unserialize($oups)
 		) {
 			if ($oups_objets = charger_fonction("editer_liens_oups_{$table_source}_{$objet}_{$objet_lien}", 'action', true)) {
@@ -297,7 +309,7 @@ function formulaires_editer_liens_traiter_dist($a, $b, $c, $options = array()) {
 					}
 				}
 			}
-			set_request('_oups', $oups ? serialize($oups) : null);
+			set_request('_oups', $oups ? base64_encode(serialize($oups)) : null);
 		}
 
 		if ($ajouter) {

programming_language: PHP