From 7f1f40853efe0eb12db9cc4a30ea8524ac00f423 Mon Sep 17 00:00:00 2001 From: rugk Date: Sat, 13 Jun 2026 22:01:35 +0200 Subject: [PATCH] fix: prevent browsers from rendering unsafe attachments like HTML in a new tab We do not want to santitize them with DOMPurify, as an attached file should be returned exactly as it has been attached by the creator. And sharing HTML files is IMHO a legitimate use case. However, opening these in a new tab can result in (JS) code execution. Yet again, opening a "bigger" preview for some file types like images or videos is also a valid use case, which also should not be broken. That's why this is not done in alll cases, but some mime type filtering still exists. I also looked into whether a blocklist (for HTML and SVG) would make sense, but really you cannot possibly define an exhausive list of "unsafe" mime types: https://security.stackexchange.com/a/167853/91425 That's why I use a quite strict allowlist approach here. I skimmed https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types/Common_types for what could be common use cases/mime types, people _may_ actually want to get rendered and this is the result. Thus I am quite confident this patch does not introduce such a big "breaking change" (in UI behaviour), but people best do not notice it. --- js/privatebin.js | 33 +++++++++++++++++++++++++++++++-- lib/Configuration.php | 2 +- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/js/privatebin.js b/js/privatebin.js index 17c45b94..c8f184e8 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -2994,11 +2994,18 @@ jQuery.PrivateBin = (function($) { const mimeType = me.getAttachmentMimeType(attachmentData); + // We explicitly do _not_ use the original mime type for the download link + // to always force a download instead of potentially dangerous browser rendering/parsing/interpretation + let safeMimeType = 'application/octet-stream'; + if (me.isSafeMimeType(mimeType)) { + safeMimeType = mimeType; + } + // extract data and convert to binary const rawData = attachmentData.substring(base64Start); const decodedData = rawData.length > 0 ? atob(rawData) : ''; - let blobUrl = getBlobUrl(decodedData, mimeType); + let blobUrl = getBlobUrl(decodedData, safeMimeType); attachmentLink.attr('href', blobUrl); if (typeof fileName !== 'undefined') { @@ -3028,6 +3035,28 @@ jQuery.PrivateBin = (function($) { me.handleBlobAttachmentPreview($attachmentPreview, blobUrl, mimeType); }; + + /** + * Evaluates whether this is known a safe mime type. + * + * This means, the media can safely be displayed and e.g. no XSS should be possible. + * + * @name AttachmentViewer.isSafeMimeType + * @function + * @param {string} + * @returns {bool} + */ + me.isSafeMimeType = function(mimeType) { + return ( + mimeType.startsWith('image/') && + !mimeType.includes('svg') + ) || + mimeType.startsWith('video/') || + mimeType.startsWith('audio/') || + mimeType.endsWith('/pdf') || + mimeType === 'text/plain'; + } + /** * displays the attachment * @@ -6064,4 +6093,4 @@ jQuery.PrivateBin = (function($) { CopyToClipboard: CopyToClipboard, Controller: Controller }; -})(jQuery); +})(jQuery); \ No newline at end of file diff --git a/lib/Configuration.php b/lib/Configuration.php index 92095561..b4101387 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -122,7 +122,7 @@ class Configuration 'js/kjua-0.10.0.js' => 'sha512-BYj4xggowR7QD150VLSTRlzH62YPfhpIM+b/1EUEr7RQpdWAGKulxWnOvjFx1FUlba4m6ihpNYuQab51H6XlYg==', 'js/legacy.js' => 'sha512-RQEo1hxpNc37i+jz/D9/JiAZhG8GFx3+SNxjYnI7jUgirDIqrCSj6QPAAZeaidditcWzsJ3jxfEj5lVm7ZwTRQ==', 'js/prettify.js' => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==', - 'js/privatebin.js' => 'sha512-U52jFm7xsPJsJ7M/sFF6GoEBLTQ4pcvNDN5z3JcH0P2xHokiV3YrhfSi6XgTnBANdnjAXmg9GfJf3Bs2yKAiug==', + 'js/privatebin.js' => 'sha512-LgKu4erWrsmN7bYLwo1I2cptFa+RUY33oufmu0Psdp2vmGA4SAqA7sml0kIFCP/44Yd4VILvr/4fCRVSQ48GNQ==', 'js/purify-3.4.1.js' => 'sha512-280a/Vb6fVFsYaeRrkuDp4EDmdYlt2XS+dlDEO/U9qljPrAraA2bIzHTNmP+9dpwPDDwTML+RS+h5iaagPwTzA==', 'js/showdown-2.1.0.js' => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==', 'js/zlib-1.3.2.js' => 'sha512-RAhJgxg9siMIA8ky4c10Rc2zUgnK80olHB8Tt1IOYWY4Eh1WmrviQkDn+sgBlb38ZHq3tzufGC41kP360gmosQ==',