
CVE-2026-58025 — RCE par désérialisation dans MediaWiki via l'importation d'entrées de journal. LogEntryBase::extractParams() unserialize() des log_params contrôlés par l'utilisateur. CVSS 9.8 | CWE-502 | MediaWiki < 1.43.9, < 1.44.6, < 1.45.4, < 1.46.0
CVE-2026-58025 est une vulnérabilité critique (CVSS 9.8) de désérialisation de données non fiables dans MediaWiki (Fondation Wikimedia). La méthode LogEntryBase::extractParams() appelait unserialize() sur des paramètres d'entrée de journal contrôlés par l'utilisateur sans restreindre les classes PHP pouvant être instanciées. Un attaquant disposant du droit importupload ou import (par défaut : groupe sysop) pouvait créer un fichier d'import XML malveillant contenant des objets PHP sérialisés dans les champs <params>, déclenchant l'instanciation arbitraire d'objets et une éventuelle exécution de code à distance via des chaînes de gadgets.
| Branche de version | Vulnérable | Corrigé |
|---|---|---|
| 1.43.x | < 1.43.9 | 1.43.9 |
| 1.44.x | < 1.44.6 | 1.44.6 |
| 1.45.x | < 1.45.4 | 1.45.4 |
| 1.46.x | < 1.46.0 | 1.46.0 |
Conditions requises : le droit
importouimportupload(par défaut : groupesysop). L'attaquant doit pouvoir téléverser ou importer un fichier XML vers Special:Import de MediaWiki.
LogEntryBase::extractParams() dans includes/Logging/LogEntryBase.php appelait unserialize() directement sur le blob log_params sans aucune restriction de classe :
// includes/Logging/LogEntryBase.php (BEFORE fix)
public static function extractParams( $blob ) {
return unserialize( $blob ); // ← user-controlled, no allowed_classes restriction
}
Cette méthode est appelée depuis plusieurs emplacements :
| Fichier | Ligne | Contexte |
|---|---|---|
includes/Logging/DatabaseLogEntry.php | 187 | getParameters() — lecture des entrées de journal depuis la base de données |
includes/RecentChanges/RecentChange.php | 781 | parseParams() — analyse des modifications récentes |
includes/Import/WikiRevision.php | 638 | importLogItem() — importation des entrées de journal depuis le XML |
maintenance/purgeChangedFiles.php | 182 | Analyse des journaux par le script de maintenance |
tests/phpunit/maintenance/DumpAsserter.php | 541 | Aide aux tests |
Le principal vecteur d'attaque est l'import XML via Special:Import :
import/importupload) crée un fichier d'export XML MediaWiki malveillant<logitem> avec un champ <params> contenant une charge utile sérialisée PHPWikiImporter::processLogItem() → WikiRevision::importLogItem() stocke l'entrée de journalDatabaseLogEntry::getParameters(), RecentChange::parseParams() ou l'affichage du journal), LogEntryBase::extractParams() appelle unserialize() sur le blob contrôlé par l'attaquantPOST /wiki/Special:Import
Content-Type: multipart/form-data
action=submit
xmlimportfile=<malicious.xml>
source=file
catname=
prefix=
loginComment=
malicious.xml:
<mediawiki ...>
<logitem>
<type>test</type>
<action>test</action>
<params>a:1:{s:3:"foo";O:8:"GadgetClass":N:{...}}</params>
</logitem>
</mediawiki>
→ WikiImporter::handleLogItem()
→ WikiImporter::processLogItem()
→ WikiRevision::importLogItem() → stores to DB
→ Later: DatabaseLogEntry::getParameters()
→ LogEntryBase::extractParams( $blob ) → unserialize( $blob )
→ PHP instantiates GadgetClass object → __wakeup() / __destruct() gadget chain
→ RCE as www-data
Le champ <params> de l'import XML accepte des chaînes sérialisées PHP standard. Une entrée sûre (bénigne) :
a:1:{s:3:"foo";s:3:"bar";}
Une entrée malveillante contenant un objet sérialisé :
a:1:{s:3:"foo";O:8:"stdClass":0:{}}
Lorsque unserialize() est appelé sans restriction allowed_classes, l'objet est entièrement instancié. Avec une chaîne de gadgets disponible (par exemple, depuis des bibliothèques installées via Composer), cela débouche sur une RCE.
Commit : 60f154d4618063ac4d5832285fc246b8fcd7c72c
Auteur : Bartosz Dziewoński
Date : 2026-06-29
Le correctif met en œuvre plusieurs couches de défense :
unserialize() restreint avec allowed_classes// includes/Logging/LogEntryBase.php (AFTER fix)
public static function extractParams( $blob, ?string $logType = null ) {
$attribute = ExtensionRegistry::getInstance()->getAttribute( 'LogParamsAllowedClasses' );
if ( $logType && array_key_exists( $logType, $attribute ) && is_array( $attribute[$logType] ) ) {
$allowedClasses = $attribute[$logType];
} else {
$allowedClasses = false; // no classes allowed
}
$result = @unserialize( $blob, [ 'allowed_classes' => $allowedClasses ] );
if ( $result !== false && !is_array( $result ) ) {
return false;
}
return $result;
}
containsUnsafeParams()Détecte les instances __PHP_Incomplete_Class (objets qui ont été bloqués par allowed_classes) :
public static function containsUnsafeParams( array $params ): bool {
$result = false;
$params = [ $params ];
array_walk_recursive( $params, static function ( $val ) use ( &$result ) {
if ( $val instanceof \__PHP_Incomplete_Class ) {
$result = true;
}
} );
return $result;
}
WikiImporter::processLogItem()Nouvelle vérification de permission logentryimport (non accordée à personne par défaut) :
private function processLogItem( $logInfo ) {
if ( !$this->performer->authorizeAction( 'logentryimport' ) ) {
$this->notice( 'permissionserrorstext-withaction-noreason', ... );
return false;
}
// ...
}
WikiRevision::importLogItem() — rejet des paramètres non sûrsif ( LogEntryBase::containsUnsafeParams(
LogEntryBase::extractParams( $this->params, "{$this->type}/{$this->action}" ),
) ) {
wfDebug( __METHOD__ . ": skipping {$this->type}/{$this->action} with unsafe params" );
return false;
}
UnsafeLogFormatter — affichage sécurisé des entrées malveillantes existantesLa nouvelle classe UnsafeLogFormatter remplace le formateur normal pour les entrées contenant des objets bloqués, affichant un message de substitution au lieu de tenter de formater les données dangereuses.
La même recherche (T422244) a également identifié que le type de paramètre raw de LogFormatter permettait une sortie HTML brute à partir de paramètres de journal contrôlés par l'utilisateur. Cela a été corrigé dans un commit associé en remplaçant Message::rawParam() par Message::plaintextParam().
L'exploit.py inclus génère un fichier d'import XML MediaWiki malveillant contenant des objets PHP sérialisés dans les paramètres des entrées de journal. Il peut également établir une empreinte (fingerprint) des instances MediaWiki et tenter l'import via l'API.
# Generate malicious XML payload
python exploit.py --generate --output payload.xml
# Fingerprint target
python exploit.py -u https://target.com --check