Skip to main content

CVE-2023-50564 : analyse technique de l'upload arbitraire dans Pluck CMS

··2348 words·12 mins
Vulnerability Research CVE Pluck CMS Arbitrary File Upload PHP Web Security Detection Engineering
Rai2en
Author
Rai2en
Securing infrastructure, validating attack paths, and documenting the evidence.
Table of Contents
CVE Deep Dives - This article is part of a series.
Part : This Article

Résumé exécutif
#

CVE-2023-50564 est une vulnérabilité d’upload de fichier dangereux dans le mécanisme d’installation de modules de Pluck CMS 4.7.18. Un utilisateur déjà authentifié dans l’administration peut soumettre une archive ZIP contenant du PHP. L’application extrait ensuite son contenu sous data/modules/, un emplacement qui peut être servi et interprété par le serveur web. Le résultat est une exécution de code dans le contexte du processus PHP.

Le point essentiel n’est donc pas seulement « un ZIP est accepté ». La vulnérabilité apparaît parce que trois frontières de confiance sont confondues :

  1. une archive fournie par un utilisateur privilégié est considérée comme un module fiable ;
  2. son contenu est extrait sans contrôle des types de fichiers ;
  3. la destination d’extraction reste exécutable par PHP.

Position de cet article. La reproduction décrite plus bas utilise un fichier PHP bénin qui affiche un marqueur fixe. Elle doit être réalisée uniquement sur une instance locale ou explicitement autorisée.

ÉlémentValeur vérifiée
IdentifiantCVE-2023-50564
Produit documentéPluck CMS 4.7.18
FaiblesseCWE-434, upload non restreint d’un fichier dangereux
PrérequisSession d’administration authentifiée
Interaction utilisateurAucune après l’action de l’attaquant
CVSS v3.18.8 High
VecteurCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Route applicativeadmin.php?action=installmodule
Fichier internedata/inc/modules_install.php
Destinationdata/modules/

La fiche a été publiée le 14 décembre 2023. Le dossier NVD et le record CVE JSON confirment la description, le score et la classification CWE-434.

Ce que les sources publiques permettent réellement d’affirmer
#

Version affectée et statut du correctif
#

Le record CVE nomme explicitement Pluck CMS 4.7.18. Le NVD associe également son analyse à cette version. En revanche, le conteneur CNA du record CVE conserve des champs produit et version à n/a, et aucune note de sécurité officielle ne désigne clairement une première version corrigée.

Au 27 août 2026, la dernière version stable publiée dans le dépôt officiel est Pluck CMS 4.7.20. Une comparaison du code vulnérable en 4.7.18 avec le même composant en 4.7.20 montre que la branche ZIP accepte encore l’archive puis l’extrait sans filtrer les fichiers PHP.

Cela ne prouve pas à lui seul que toutes les versions ultérieures sont exploitables dans toutes les configurations. Cela signifie en revanche qu’il serait incorrect de présenter 4.7.19 ou 4.7.20 comme un correctif confirmé sans validation supplémentaire. Une mise à jour reste recommandée pour bénéficier des autres corrections, mais elle doit être complétée par le durcissement du répertoire de modules et par un test du comportement réel.

Exploitation observée et probabilité
#

Au moment de cette révision :

  • CVE-2023-50564 n’apparaît pas dans le catalogue CISA Known Exploited Vulnerabilities ;
  • la mesure EPSS de FIRST du 26 août 2026 est de 0,29069, au 98,021e percentile ;
  • l’enrichissement SSVC publié dans le record CVE indique Exploitation: none, Automatable: no et Technical Impact: total pour son instantané du 8 octobre 2024.

Ces indicateurs ne sont pas contradictoires. L’absence du KEV ne démontre pas l’absence d’exploitation, et EPSS n’est pas une mesure d’impact. Ici, l’authentification réduit l’automatisation à grande échelle, tandis que la compromission d’un compte d’administration rend l’impact potentiel très élevé.

Architecture du chemin vulnérable
#

Le fichier modules_install.php n’est pas censé être appelé directement. Il vérifie que la constante IN_PLUCK existe. Le véritable point d’entrée est admin.php, qui démarre la session, contrôle l’état d’authentification, puis inclut le composant lorsque l’action installmodule est demandée.

flowchart LR A[Administrateur authentifié] -->|POST multipart| B[admin.php?action=installmodule] B --> C{Session Pluck valide} C -->|Non| D[Redirection login.php] C -->|Oui| E[data/inc/modules_install.php] E --> F[Copie de l'archive] F --> G[Extraction sous data/modules] G --> H{Le serveur exécute PHP ici} H -->|Oui| I[Exécution de code] H -->|Non| J[Fichier stocké mais non exécuté]

Cette distinction corrige une imprécision fréquente : /inc/modules_install.php décrit le composant dans la fiche CVE, mais la route exploitable observée dans le code est admin.php?action=installmodule et le fichier se trouve dans le dépôt sous data/inc/modules_install.php.

Cause racine dans modules_install.php
#

La logique 4.7.18 réalise trois contrôles principaux : la présence de .zip ou .tar.gz dans le nom, une taille maximale de 2 000 000 octets, puis l’extraction. Ces contrôles ne répondent pas à la question importante : quels fichiers l’archive contient-elle et peuvent-ils être exécutés ?

Le comportement peut être résumé ainsi :

$dir = 'data/modules';
$filename = $_FILES['sendfile']['name'];

if (!strpos($filename, '.tar.gz') && !strpos($filename, '.zip')) {
    reject_upload();
}

copy($_FILES['sendfile']['tmp_name'], $dir . '/' . $filename);
$zip = new UnZIP($dir . '/' . $filename);
$zip->extract();

Ce fragment est volontairement réduit aux décisions de sécurité. Le fichier complet de Pluck 4.7.18 permet de vérifier le contexte exact.

1. Le nom de l’archive n’est pas une politique de contenu
#

strpos($filename, '.zip') cherche une sous-chaîne. Il ne valide ni une extension terminale exacte, ni le type MIME, ni la signature du format. Même une vérification d’extension parfaitement écrite resterait insuffisante : une archive ZIP légitime peut contenir un fichier module.php dangereux.

2. Aucun inventaire de l’archive n’est effectué
#

Avant l’extraction, le code ne parcourt pas les entrées pour refuser :

  • les extensions exécutables comme .php, .phtml, .phar ou les variantes propres à la pile déployée ;
  • les fichiers de configuration tels que .htaccess ou .user.ini ;
  • les chemins absolus ou les segments ../ ;
  • les liens symboliques et les noms ambigus ;
  • les archives démesurées après décompression.

La CVE documente l’upload dangereux et l’exécution de PHP. Cette absence de validation peut aussi créer d’autres risques, mais ils doivent être testés séparément avant d’être attribués à la CVE.

3. La destination est dans l’arborescence web
#

Le contenu est extrait sous data/modules/. Si la configuration Apache, Nginx ou PHP-FPM autorise l’exécution de PHP dans ce répertoire, une simple requête HTTP vers le fichier déposé transforme l’upload en exécution de code.

flowchart TD Z[Archive ZIP fournie] --> N[Contrôle du nom et de la taille] N --> X[Extraction automatique] X --> P[data/modules/nom/module.php] P --> W[Requête HTTP] W --> R[Interpréteur PHP] R --> O[Code exécuté avec les droits du service web] classDef trust fill:#13202d,stroke:#33e6cc,color:#f3f7fb classDef risk fill:#2a1520,stroke:#ff5d7a,color:#f3f7fb class Z,N trust class X,P,W,R,O risk

Chaîne d’exploitation, étape par étape
#

La chaîne réaliste comporte un prérequis souvent omis : l’attaquant doit d’abord obtenir une session d’administration Pluck valide. Cela peut résulter d’identifiants faibles, réutilisés, divulgués, d’un vol de session ou d’un autre maillon d’attaque.

  1. Authentification sur login.php et conservation du cookie de session.
  2. Préparation d’un module ZIP contenant au moins un fichier PHP.
  3. POST multipart vers admin.php?action=installmodule avec le champ sendfile.
  4. Copie et extraction de l’archive sous data/modules/.
  5. Requête HTTP vers le fichier PHP extrait.
  6. Exécution avec l’identité et les permissions du processus PHP.

La portée après exploitation dépend donc fortement de l’hôte : droits du compte de service, secrets accessibles, connectivité réseau, permissions d’écriture et isolation du serveur web.

Mon PoC GitHub comme support de validation
#

Rai2en/CVE-2023-50564_Pluck-v4.7.18_PoC

18 stars · 5 forks, relevés le 27 août 2026. Le dépôt fournit un client Python compact qui enchaîne la connexion, l’upload multipart et la requête de vérification.

Le PoC rend la chaîne observable dans un lab :

requests.Session()
  ├─ POST /login.php
  ├─ conserve le cookie de session
  ├─ POST /admin.php?action=installmodule
  │    └─ sendfile = payload.zip
  └─ GET /data/modules/payload/shell.php

Ce que le PoC démontre bien
#

  • la nécessité d’une session HTTP persistante ;
  • le point d’entrée réel admin.php?action=installmodule ;
  • le format multipart attendu par l’interface ;
  • le chemin entre installation du module et accessibilité du PHP extrait.

Limites à connaître avant de l’utiliser
#

Le script est volontairement simple. Pour une validation rigoureuse, plusieurs résultats doivent être interprétés avec prudence :

  • un statut HTTP 200 après le login ne prouve pas que l’authentification a réussi ;
  • un statut 200 après l’upload ne garantit pas que l’archive a été acceptée et extraite ;
  • le schéma HTTP, le chemin final et le nom payload sont supposés fixes ;
  • aucun délai maximal, contrôle de redirection ou message applicatif n’est validé ;
  • la requête finale est effectuée même si une étape précédente a échoué.

Pour transformer ce PoC en outil de contrôle défensif, il faudrait vérifier le contenu de la réponse, détecter explicitement la page de connexion, arrêter la chaîne au premier échec, prendre l’URL en argument et utiliser un marqueur bénin unique plutôt qu’un shell.

Reproduction sûre dans un lab isolé
#

L’objectif est de démontrer l’interprétation de PHP sans ouvrir de shell ni exécuter de commande système.

Préparation
#

  1. Déployer la release officielle Pluck 4.7.18 dans une VM ou un conteneur sans données réelles.
  2. Bloquer les connexions sortantes du lab.
  3. Activer les journaux d’accès et d’erreur du serveur web.
  4. Créer un compte d’administration dédié au test.
  5. Ne jamais exposer l’instance à Internet.

Créer ensuite une arborescence minimale :

payload/
└── marker.php

Le fichier marker.php ne doit contenir qu’un marqueur statique :

<?php
header('Content-Type: text/plain');
echo 'CVE-2023-50564-LAB-MARKER';

Créer l’archive puis utiliser l’interface d’administration ou le PoC GitHub en adaptant l’URL, les identifiants et le chemin de vérification à marker.php.

Le test est concluant uniquement si une requête vers le fichier extrait renvoie le marqueur fixe. Supprimer ensuite l’instance du lab, l’archive et les identifiants temporaires.

Détection : privilégier la séquence plutôt qu’un nom de fichier
#

Un IOC comme shell.php est trop fragile. Le nom de l’archive et celui du fichier interne sont contrôlés par l’opérateur. La détection doit corréler les événements.

sequenceDiagram participant C as Client participant W as Serveur web participant P as Pluck CMS participant F as Système de fichiers C->>W: POST /login.php C->>W: POST /admin.php?action=installmodule W->>P: Session + multipart ZIP P->>F: Création sous data/modules C->>W: GET /data/modules/.../*.php W->>F: Lecture du nouveau fichier W-->>C: Réponse générée par PHP

Signaux dans les journaux HTTP
#

Rechercher, pour une même adresse source ou une même session :

  • un POST vers /login.php ;
  • suivi d’un POST multipart vers /admin.php?action=installmodule ;
  • suivi d’une requête vers /data/modules/<répertoire>/<fichier>.php ;
  • des requêtes ultérieures avec des paramètres inhabituels ou une fréquence anormale.

Exemple de recherche locale sur un journal d’accès :

grep -E 'POST /(login\.php|admin\.php\?action=installmodule)|/data/modules/.+\.php' access.log

Cette expression est un point de départ. Elle doit être adaptée au format réel des journaux, à l’encodage de l’URL et à la position de la query string.

Signaux sur le système de fichiers
#

  • création récente de .php, .phtml, .phar, .user.ini ou .htaccess sous data/modules/ ;
  • archive ZIP créée puis supprimée rapidement dans le même répertoire ;
  • fichier appartenant au compte du serveur web à une heure inhabituelle ;
  • modification concomitante des paramètres de module ;
  • processus enfant ou connexion sortante initiée par PHP, Apache ou PHP-FPM.

Une vérification d’intégrité simple peut commencer par :

find /var/www/pluck/data/modules -type f \
  \( -iname '*.php' -o -iname '*.phtml' -o -iname '*.phar' -o -name '.user.ini' -o -name '.htaccess' \) \
  -printf '%TY-%Tm-%Td %TH:%TM:%TS %u %m %p\n'

Réponse à incident
#

Si un upload suspect est identifié :

  1. isoler l’hôte sans supprimer les fichiers ;
  2. préserver les journaux HTTP, PHP, système et reverse proxy ;
  3. relever les métadonnées et les empreintes des fichiers récemment créés ;
  4. rechercher les accès à data/modules/ et les processus enfants du service web ;
  5. révoquer les sessions Pluck et renouveler les identifiants d’administration ;
  6. examiner les secrets lisibles par le compte PHP ;
  7. reconstruire depuis une source saine si l’exécution de code est confirmée ;
  8. appliquer les contrôles de durcissement avant la remise en ligne.

La suppression du fichier visible ne suffit pas. Une exécution de code peut avoir créé d’autres mécanismes de persistance ou compromis des secrets externes.

Remédiation durable
#

1. Empêcher l’exécution dans les répertoires d’upload
#

C’est le contrôle compensatoire le plus important. Un module malveillant peut encore être stocké, mais il ne doit pas devenir du code exécutable par une simple requête HTTP.

Exemple Nginx à adapter à l’architecture :

location ~* ^/data/modules/.*\.(php|phtml|phar)$ {
    deny all;
    return 403;
}

Exemple Apache placé dans la configuration du VirtualHost :

<Directory "/var/www/pluck/data/modules">
    <FilesMatch "\.(php|phtml|phar)$">
        Require all denied
    </FilesMatch>
</Directory>

Il faut tester ces règles dans l’environnement réel. Une configuration PHP-FPM globale ou une règle de réécriture peut modifier leur effet.

2. Désactiver ou restreindre l’installation de modules
#

  • retirer la fonction si elle n’est pas utilisée ;
  • limiter l’administration à un réseau de gestion ou à un VPN ;
  • protéger les comptes par un secret unique et une couche d’authentification additionnelle en amont ;
  • journaliser chaque installation de module avec l’identité, l’adresse source et le hash de l’archive.

3. Corriger la chaîne de traitement des archives
#

Une correction applicative robuste doit :

  1. attribuer un nom serveur aléatoire à l’upload ;
  2. stocker l’archive hors du webroot ;
  3. ouvrir l’archive sans l’extraire ;
  4. normaliser chaque chemin et refuser les chemins absolus ou sortant de la destination ;
  5. refuser les liens symboliques et les fichiers de configuration ;
  6. appliquer une liste blanche stricte de fichiers nécessaires à un module ;
  7. limiter le nombre d’entrées et la taille totale décompressée ;
  8. extraire dans un répertoire temporaire non exécutable ;
  9. valider la structure du module ;
  10. déplacer atomiquement le contenu approuvé vers sa destination.
flowchart LR A[Upload] --> B[Stockage hors webroot] B --> C[Inventaire de l'archive] C --> D{Chemins et types autorisés} D -->|Non| E[Rejet + journalisation] D -->|Oui| F[Extraction en zone non exécutable] F --> G[Validation de la structure] G --> H[Installation atomique] H --> I[Répertoire servi sans exécution PHP]

4. Ne pas compter uniquement sur un WAF
#

La requête ressemble à une fonction d’administration légitime et le contenu dangereux se trouve dans une archive. Un WAF peut réduire l’exposition ou bloquer certains motifs, mais il ne remplace ni la validation récursive de l’archive ni la séparation entre stockage et exécution.

Ce que cette CVE enseigne
#

CVE-2023-50564 illustre un défaut de conception classique : un mécanisme d’extension transforme une donnée fournie par un utilisateur en code de confiance. L’authentification réduit le nombre d’attaquants possibles, mais elle ne rend pas sûre une primitive d’upload conduisant directement à l’exécution.

Les trois contrôles structurants sont simples à formuler :

  • ne jamais extraire une archive non vérifiée dans le webroot ;
  • ne jamais autoriser l’exécution dans un répertoire d’upload ;
  • ne jamais considérer une extension ou un type MIME comme une validation du contenu.

Références
#

CVE Deep Dives - This article is part of a series.
Part : This Article