Blog cybersécurité

B2J Contact est une extension Joomla qui sert à créer des composants de contacts personnalisés. Elle est très utilisée dans les déploiements Joomla. Nous sommes tombés sur cette extension dans le cadre d'un audit de sécurité et nous avons décidé de l'examiner plus en détail. Nous avons donc installé une copie du module localement sur l'une de nos machines pour faire nos tests.

TL;DR

Nous avons découvert deux vulnérabilités qui permettent à un attaquant non authentifié d'exécuter des commandes sur le système. Ces vulnérabilités (CVE-2017-5214 et CVE-2017-5215) ont été signalées à l'éditeur et sont maintenant corrigées.

Upload de fichier

L'un des paramètres de configuration de B2j Contact définit l'activation du mode d'upload de fichiers. Cette fonctionnalité permet aux utilisateurs de joindre des fichiers à un message envoyé via le formulaire de contact. Il s'agit de l'une des fonctionnalités phares du formulaire de contact de B2j et elle semble être activée dans la plupart des déploiements. Pour l'analyste de sécurité, il est toujours judicieux d'explorer en détail les mécanismes d'upload du point de vue d'un attaquant potentiel.

Accès direct au fichier

Dans notre instance de test Joomla, nous avons commencé par uploader un fichier pour voir où il aboutit sur le serveur. Étonnamment, le fichier est uploadé vers le répertoire publié, à l'emplacement suivant:

/components/com_b2jcontact/uploads/586cea8e6352b-navixia.jpg

Le fichier est sauvegardé sur le serveur avec un autre nom, peut-être pour éviter un accès direct ou pour ne pas écraser des fichiers qui auraient des noms identiques.

Nous avons inspecté le code pour trouver la fonction responsable de générer ce nouveau nom de fichier.

$file = JRequest::getVar('b2jstdupload', NULL, 'files', 'array');
        if (!$this->Submitted || !$file || $file['error'] == UPLOAD_ERR_NO_FILE) return true;
        $upload_directory = JPATH_SITE . "/components/" . $GLOBALS["com_name"] . "/uploads/";
        ...
        $filename = JFile::makeSafe($file['name']);
        $filename = uniqid() . "-" . $filename;
        $dest = $upload_directory . $filename; 

On peut voir que le nom final du fichier est la combinaison de uniqid() avec le nom de fichier original. En regardant la définition de uniqid(), on voit que le résultat est dérivé de la fonction PHP microtime.

Comme son nom le suggère explicitement, la fonction microtime() donne l'heure actuelle en microsecondes. Il s'agit donc bien d'une valeur prévisible, ce qui est par conséquent aussi le cas de uniqid().

A l'aide d'un script, nous avons vérifié qu'il est possible de prévoir quel sera le résultat d'uniqid() en fonction de l'heure actuelle. Les deux valeurs ne sont pas exactement les mêmes en raison de la non-concurrence des deux fonctions:

<?                                                                    
  $m = microtime(true);  
  $u = uniqid();     
  $predicted_u = sprintf("%8x%05x\n",floor($m),($m-floor($m))*1000000); 

  print('predicted:' . $predicted_u);                             
  print('uniqid   :' . $u);                              
?>

predicted :586cea8e6352b
uniqid :586cea8e63614

predicted :586cea901f00a
uniqid :586cea901f06c

Autrement dit, cela signifie que si nous uploadons un fichier nommé navixia.png, il sera accessible à l'adresse suivante:

[BASE_URL]/components/com_b2jcontact/uploads/xxxxxxxxxxxx-navixia.png

où xxxxxxxxxxxx peut être deviné à l'aide d'un nombre raisonnable d'essais liés à la différence entre l'horloge du serveur et l'heure actuelle.

Pour uploader un fichier et parvenir à y accéder directement, on peut ensuite procéder comme suit :

  • Uploader le fichier et enregistrer l'heure T1 à laquelle l'upload a été effectué
  • Calculer le résultat uniqid() prévu pour l'heure T1
  • Essayer d'accéder au fichier en utilisant ce résultat et le nom du fichier d'origine
  • En cas d'échec, rajouter 1 à T1 et réessayer.

Pour couvrir tous les cas de figure, on peut aussi essayer de diminuer T1 au cas où l'horloge du server serait en retard sur l'heure réelle.

Validation du type de fichier

Alors que B2j semble vérifier le type MIME lors d'un upload, il ne semble étrangement pas contrôler l'extension. Il est donc possible d'uploader un fichier avec une extension exécutable sur le serveur web (php, cgi,…)

Les types MIME sont définis par les premiers bits d'un fichier. Il s'avère donc que l'on peut modifier le contenu du fichier sans modifier la validité du type MIME.

Exécution de code

La combinaison des deux vulnérabilités est évidente: il est possible d'uploader un fichier PHP que son type MIME identifie comme une image, mais qui contient du code exécutable. Il suffit ensuite de trouver son nom puis d'y accéder via l'URL correspondante pour exécuter le code.

Chronologie

  • 2017-01-03: Prise de contact avec le vendeur
  • 2017-01-04: Envoi de nos recommandations et de la démonstration de la vulnérabilité à Codextrous
  • 2017-01-05: Confirmation de la vulnérabilité par le vendeur
  • 2017-01-14: Publication du patch correctif 2.1.13 par le vendeur
  • 2017-05-05: Publication de la vulnérabilité

Solution

Il suffit de mettre à jour B2j Contact vers la version 2.1.13 ou suivantes.