VidraCrypt: Zero-Knowledge-Dateifreigabe im Browser
(Aktualisiert: )
This page is also available in English.
Bei einem Pentest muss ich Kunden regelmäßig Dinge schicken, die niemand gerne im Postfach liegen hat: den finalen Bericht, extrahierte Zugangsdaten, einen Dump als Nachweis für eine Schwachstelle. E-Mail und generische Filesharing-Tools eignen sich dafür schlecht. Deshalb habe ich VidraCrypt (öffnet in neuem Tab) gebaut, ein kleines Open-Source-Tool, das Dateien vollständig im Browser entschlüsselt, ohne dass der Server jemals Passphrase oder Klartext zu sehen bekommt.
Das Problem beim Versand sensibler Dateien
Pentest-Deliverables gehören zu den sensibelsten Dokumenten, die ein Kunde je erhält. Ein Bericht beschreibt im Detail, wie man in das Netzwerk eindringt. Ein extrahierter Credential-Dump bleibt nutzbar, solange die Passwörter nicht geändert wurden. Wird das per normaler E-Mail oder über einen generischen Filesharing-Dienst verschickt, muss man jedem Mailserver, jedem Spamfilter und jedem Cloud-Anbieter dazwischen vertrauen.
Manche Kunden haben bereits PGP oder S/MIME eingerichtet, das hilft beim Transport. Das größere Problem liegt aber meist gar nicht beim Transport: Sobald ein Bericht im Postfach landet, bleibt er dort meist einfach liegen. Weitergeleitet in anderen Threads, aufs Handy synchronisiert, vom Mail-Provider gesichert, noch Jahre später auffindbar, wenn alle Beteiligten längst vergessen haben, dass es ihn überhaupt gibt. Ein Dokument, das im Detail beschreibt, wie man das Netzwerk eines Kunden kompromittiert, sollte nicht zu einem dauerhaften, vergessenen Anhang im Postfach werden. Ich wollte einen Weg, eine Datei zu übergeben, die für niemanden außer dem vorgesehenen Empfänger lesbar ist, und die nicht standardmäßig als dauerhafte Klartextkopie in einem E-Mail-Archiv liegen bleibt.
Wie VidraCrypt funktioniert
VidraCrypt besteht aus einer statischen Webseite und einem kleinen Verschlüsselungsskript. Es gibt kein Backend, keine Datenbank und kein Benutzerkonto:
- Die Verschlüsselung läuft auf meinem Rechner mit age (öffnet in neuem Tab) im Passphrase-Modus, mit einer
diceware-Passphrase (sechs Wörter, Standardmethode). Das Skriptadd-to-files.shübernimmt das: Es erzeugt für die verschlüsselte Datei immer einen zufälligen UUID-Dateinamen (damit der ursprüngliche Dateiname nie durchsickert) und schreibt einen Eintrag in einen lokalen Metadaten-Index. - Die Entschlüsselung läuft im Browser des Kunden mit typage (öffnet in neuem Tab), einer JavaScript/WASM-Implementierung von
age. Der Kunde öffnet einen Link, gibt die Passphrase ein, die ich ihm über einen anderen Kanal mitgeteilt habe (Signal, Telefonat), und die entschlüsselte Datei wird direkt aus seinem eigenen Browser heruntergeladen.
Die verschlüsselte Datei selbst kann überall gehostet werden: ein Cloudflare-Pages-Bucket, S3, GitHub Pages. VidraCrypt ist das egal, da sie per einfachem fetch()-Aufruf abgeholt und clientseitig entschlüsselt wird.
Der URL-Fragment-Trick
Der Link sieht so aus:
https://your-host/app/#get=<base64url-encoded-file-url>
Der interessante Teil ist das #. Alles nach einem # in einer URL ist ein Fragment, und Browser schicken das Fragment nie als Teil der HTTP-Anfrage an den Server. Es taucht nie in einem Webserver-Access-Log, einem CDN-Log oder einem Proxy dazwischen auf. Es existiert ausschließlich innerhalb des Browsers.
Das bedeutet: Die URL, die auf die verschlüsselte Datei zeigt, und damit auch, welche Datei ein bestimmter Kunde erhalten hat, taucht in keinem Server-Log entlang des Weges auf. decrypt.js liest das Fragment clientseitig aus, dekodiert den base64url-String zurück in die eigentliche Datei-URL, validiert sie und holt erst danach den Ciphertext ab.
Diese Validierung ist wichtig: Die App akzeptiert nur https://-URLs ohne eingebettete Zugangsdaten, ohne benutzerdefinierten Port und mit einem Pfad, der auf eine UUID endet. Das schließt den naheliegenden Missbrauchsfall aus, die Entschlüsselungsseite als offenen Redirector oder SSRF-Proxy für beliebige URLs zu missbrauchen.
Absicherung
Da das gesamte Sicherheitsmodell darauf beruht, dass der Browser genau dieses JavaScript und nichts anderes ausführt, setzt die App konsequent auf Browser-Security-Header:
- Eine
Content-Security-Policymitdefault-src 'none'und einscript-src, das auf die exakten SRI-Hashes vonage-0.3.0.jsunddecrypt.jsgepinnt ist. Nichts anderes darf geladen oder ausgeführt werden. Cross-Origin-Opener-PolicyundCross-Origin-Embedder-Policy, um die Seite von anderen Fenstern zu isolieren.Cache-Control: no-storeundClear-Site-Data: "*", damit von der Sitzung im Browser danach nichts zurückbleibt.require-trusted-types-for 'script', um die Angriffsfläche für DOM-XSS zu verringern.
Die eingebundene Abhängigkeit age-0.3.0.js wird mit dokumentiertem Checksum in der README ausgeliefert, sodass jeder, der VidraCrypt selbst betreibt, vor dem Ausliefern prüfen kann, ob sie manipuliert wurde.
Ein Punkt sei hier offen angesprochen: Das alles setzt voraus, dass der Server, der die App ausliefert, selbst vertrauenswürdig ist. Würde jemand den Host vollständig kompromittieren und index.html zusammen mit dem Content-Security-Policy-Header umschreiben, könnte er ein eigenes Skript (mitsamt passendem SRI-Hash) einschleusen und zum Beispiel die Passphrase beim Eintippen mitloggen. Das ist ein ziemlich unwahrscheinlicher Angriff, wer bereits diesen Zugriff hat, hat meist einfachere Optionen als auf eine Passphrase zu warten, aber es ist eine reale Einschränkung, die man benennen sollte: Im Web gibt es kein echtes Code-Signing wie bei nativen Apps, wo der Signaturschlüssel unabhängig vom Verteilungsweg ist. Content-Hashes über die CSP sind das nächstliegende Äquivalent, und die schützen vor Manipulation durch Dritte an dem, was ausgeliefert wird (ein kompromittierter CDN-Cache, eine bösartige Browser-Erweiterung, eingeschleuste Skripte von Drittanbietern), nicht aber davor, dass der Ursprungsserver selbst kompromittiert ist.
Anwendung
Eine Datei für einen Kunden verschlüsseln:
./add-to-files.sh --target-dir "encrypted-files" --base-url "https://files.example.com/bucket" "my-report.zip"
Das gibt die Passphrase aus (über einen separaten Kanal teilen) sowie einen fertigen #get=...-Link. Die verschlüsselte Datei dorthin hochladen, wohin die Base-URL zeigt, dem Kunden Link und Passphrase schicken, und er kann sie im Browser entschlüsseln, ohne irgendetwas zu installieren.
Es gibt eine Live-Demo (öffnet in neuem Tab) mit dem Test-Passwort 123, wenn du den Ablauf selbst ausprobieren willst.
Grenzen
VidraCrypt soll keine Ende-zu-Ende-verschlüsselte Messaging-Lösung für extrem hohe Bedrohungsszenarien ersetzen. Die Passphrase muss den Kunden trotzdem irgendwie erreichen, und ist dieser Kanal kompromittiert, ist es auch die Datei. Außerdem wird vorausgesetzt, dass Browser und Gerät des Kunden nicht bereits kompromittiert sind, was für jede browserbasierte Kryptografie gilt.
Umgekehrt gilt aber auch: Eine für sich allein durchgesickerte Passphrase reicht nicht aus, um an die Datei zu kommen. Die verschlüsselte Datei liegt hinter einer zufälligen UUID, die sich nicht erraten lässt, ein Angreifer braucht also zusätzlich den Link, der über einen anderen Kanal als die Passphrase verschickt wird. Gut gelöst ist damit das alltägliche Problem, eine sensible Datei zum Kunden zu bringen, ohne dass irgendwo außer auf dessen eigenem Rechner eine Kopie des Klartexts oder der Passphrase liegen bleibt.
Zugehörige Blog Post
martin@vidrasec.com | +43 670 3081275 (öffnet in neuem Tab) | +43 670 3081275 | Termin auswählen (öffnet in neuem Tab) |