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

Spip Web Framework v3.1.13 and below was discovered to contain multiple SQL injection vulnerabilities at /ecrire via the lier_trad and where parameters.

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-89
cwe_name: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
extended_description: 
            
            
         
         

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